From 610957f7035242f15743c399ffd429b92bc36206 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Dec 2018 20:24:11 +0100 Subject: cpp conversion: minimal steps to fix compilation errors, not warnings --- src/Makefile.am | 106 +-- src/PJ_aea.c | 222 ----- src/PJ_aea.cpp | 222 +++++ src/PJ_aeqd.c | 323 ------- src/PJ_aeqd.cpp | 323 +++++++ src/PJ_affine.c | 246 ----- src/PJ_affine.cpp | 246 +++++ src/PJ_airy.c | 151 ---- src/PJ_airy.cpp | 151 ++++ src/PJ_aitoff.c | 197 ---- src/PJ_aitoff.cpp | 197 ++++ src/PJ_august.c | 34 - src/PJ_august.cpp | 34 + src/PJ_axisswap.c | 300 ------- src/PJ_axisswap.cpp | 300 +++++++ src/PJ_bacon.c | 79 -- src/PJ_bacon.cpp | 79 ++ src/PJ_bertin1953.c | 94 -- src/PJ_bertin1953.cpp | 94 ++ src/PJ_bipc.c | 174 ---- src/PJ_bipc.cpp | 174 ++++ src/PJ_boggs.c | 43 - src/PJ_boggs.cpp | 43 + src/PJ_bonne.c | 134 --- src/PJ_bonne.cpp | 134 +++ src/PJ_calcofi.c | 163 ---- src/PJ_calcofi.cpp | 163 ++++ src/PJ_cart.c | 219 ----- src/PJ_cart.cpp | 219 +++++ src/PJ_cass.c | 121 --- src/PJ_cass.cpp | 121 +++ src/PJ_cc.c | 41 - src/PJ_cc.cpp | 41 + src/PJ_ccon.c | 107 --- src/PJ_ccon.cpp | 107 +++ src/PJ_cea.c | 101 --- src/PJ_cea.cpp | 101 +++ src/PJ_chamb.c | 139 --- src/PJ_chamb.cpp | 139 +++ src/PJ_collg.c | 53 -- src/PJ_collg.cpp | 53 ++ src/PJ_comill.c | 84 -- src/PJ_comill.cpp | 84 ++ src/PJ_crast.c | 40 - src/PJ_crast.cpp | 40 + src/PJ_deformation.c | 323 ------- src/PJ_deformation.cpp | 323 +++++++ src/PJ_denoy.c | 32 - src/PJ_denoy.cpp | 32 + src/PJ_eck1.c | 41 - src/PJ_eck1.cpp | 41 + src/PJ_eck2.c | 56 -- src/PJ_eck2.cpp | 56 ++ src/PJ_eck3.c | 110 --- src/PJ_eck3.cpp | 110 +++ src/PJ_eck4.c | 63 -- src/PJ_eck4.cpp | 63 ++ src/PJ_eck5.c | 40 - src/PJ_eck5.cpp | 40 + src/PJ_eqc.c | 52 -- src/PJ_eqc.cpp | 52 ++ src/PJ_eqdc.c | 120 --- src/PJ_eqdc.cpp | 120 +++ src/PJ_eqearth.c | 162 ---- src/PJ_eqearth.cpp | 162 ++++ src/PJ_fahey.c | 41 - src/PJ_fahey.cpp | 41 + src/PJ_fouc_s.c | 70 -- src/PJ_fouc_s.cpp | 70 ++ src/PJ_gall.c | 44 - src/PJ_gall.cpp | 44 + src/PJ_geoc.c | 59 -- src/PJ_geoc.cpp | 59 ++ src/PJ_geos.c | 236 ----- src/PJ_geos.cpp | 236 +++++ src/PJ_gins8.c | 33 - src/PJ_gins8.cpp | 33 + src/PJ_gn_sinu.c | 187 ---- src/PJ_gn_sinu.cpp | 187 ++++ src/PJ_gnom.c | 143 --- src/PJ_gnom.cpp | 143 +++ src/PJ_goode.c | 82 -- src/PJ_goode.cpp | 82 ++ src/PJ_gstmerc.c | 72 -- src/PJ_gstmerc.cpp | 72 ++ src/PJ_hammer.c | 75 -- src/PJ_hammer.cpp | 75 ++ src/PJ_hatano.c | 83 -- src/PJ_hatano.cpp | 83 ++ src/PJ_healpix.c | 672 -------------- src/PJ_healpix.cpp | 672 ++++++++++++++ src/PJ_helmert.c | 753 ---------------- src/PJ_helmert.cpp | 753 ++++++++++++++++ src/PJ_hgridshift.c | 131 --- src/PJ_hgridshift.cpp | 131 +++ src/PJ_horner.c | 513 ----------- src/PJ_horner.cpp | 513 +++++++++++ src/PJ_igh.c | 225 ----- src/PJ_igh.cpp | 225 +++++ src/PJ_imw_p.c | 213 ----- src/PJ_imw_p.cpp | 213 +++++ src/PJ_isea.c | 1082 ---------------------- src/PJ_isea.cpp | 1082 ++++++++++++++++++++++ src/PJ_krovak.c | 220 ----- src/PJ_krovak.cpp | 220 +++++ src/PJ_labrd.c | 130 --- src/PJ_labrd.cpp | 130 +++ src/PJ_laea.c | 296 ------ src/PJ_laea.cpp | 296 ++++++ src/PJ_lagrng.c | 96 -- src/PJ_lagrng.cpp | 96 ++ src/PJ_larr.c | 28 - src/PJ_larr.cpp | 28 + src/PJ_lask.c | 39 - src/PJ_lask.cpp | 39 + src/PJ_latlong.c | 124 --- src/PJ_latlong.cpp | 124 +++ src/PJ_lcc.c | 128 --- src/PJ_lcc.cpp | 128 +++ src/PJ_lcca.c | 162 ---- src/PJ_lcca.cpp | 162 ++++ src/PJ_loxim.c | 75 -- src/PJ_loxim.cpp | 75 ++ src/PJ_lsat.c | 210 ----- src/PJ_lsat.cpp | 210 +++++ src/PJ_mbt_fps.c | 57 -- src/PJ_mbt_fps.cpp | 57 ++ src/PJ_mbtfpp.c | 65 -- src/PJ_mbtfpp.cpp | 65 ++ src/PJ_mbtfpq.c | 74 -- src/PJ_mbtfpq.cpp | 74 ++ src/PJ_merc.c | 101 --- src/PJ_merc.cpp | 101 +++ src/PJ_mill.c | 37 - src/PJ_mill.cpp | 37 + src/PJ_misrsom.c | 217 ----- src/PJ_misrsom.cpp | 217 +++++ src/PJ_mod_ster.c | 280 ------ src/PJ_mod_ster.cpp | 280 ++++++ src/PJ_moll.c | 110 --- src/PJ_moll.cpp | 110 +++ src/PJ_molodensky.c | 327 ------- src/PJ_molodensky.cpp | 327 +++++++ src/PJ_natearth.c | 100 --- src/PJ_natearth.cpp | 100 +++ src/PJ_natearth2.c | 97 -- src/PJ_natearth2.cpp | 97 ++ src/PJ_nell.c | 51 -- src/PJ_nell.cpp | 51 ++ src/PJ_nell_h.c | 53 -- src/PJ_nell_h.cpp | 53 ++ src/PJ_nocol.c | 54 -- src/PJ_nocol.cpp | 54 ++ src/PJ_nsper.c | 198 ---- src/PJ_nsper.cpp | 198 ++++ src/PJ_nzmg.c | 123 --- src/PJ_nzmg.cpp | 123 +++ src/PJ_ob_tran.c | 243 ----- src/PJ_ob_tran.cpp | 243 +++++ src/PJ_ocea.c | 100 --- src/PJ_ocea.cpp | 100 +++ src/PJ_oea.c | 85 -- src/PJ_oea.cpp | 85 ++ src/PJ_omerc.c | 227 ----- src/PJ_omerc.cpp | 227 +++++ src/PJ_ortho.c | 139 --- src/PJ_ortho.cpp | 139 +++ src/PJ_patterson.c | 117 --- src/PJ_patterson.cpp | 117 +++ src/PJ_pipeline.c | 501 ----------- src/PJ_pipeline.cpp | 501 +++++++++++ src/PJ_poly.c | 169 ---- src/PJ_poly.cpp | 169 ++++ src/PJ_putp2.c | 61 -- src/PJ_putp2.cpp | 61 ++ src/PJ_putp3.c | 65 -- src/PJ_putp3.cpp | 65 ++ src/PJ_putp4p.c | 74 -- src/PJ_putp4p.cpp | 74 ++ src/PJ_putp5.c | 73 -- src/PJ_putp5.cpp | 73 ++ src/PJ_putp6.c | 95 -- src/PJ_putp6.cpp | 95 ++ src/PJ_qsc.c | 402 --------- src/PJ_qsc.cpp | 402 +++++++++ src/PJ_robin.c | 159 ---- src/PJ_robin.cpp | 159 ++++ src/PJ_rpoly.c | 56 -- src/PJ_rpoly.cpp | 56 ++ src/PJ_sch.c | 230 ----- src/PJ_sch.cpp | 230 +++++ src/PJ_sconics.c | 216 ----- src/PJ_sconics.cpp | 216 +++++ src/PJ_somerc.c | 92 -- src/PJ_somerc.cpp | 92 ++ src/PJ_stere.c | 316 ------- src/PJ_stere.cpp | 316 +++++++ src/PJ_sterea.c | 115 --- src/PJ_sterea.cpp | 115 +++ src/PJ_sts.c | 107 --- src/PJ_sts.cpp | 107 +++ src/PJ_tcc.c | 34 - src/PJ_tcc.cpp | 34 + src/PJ_tcea.c | 36 - src/PJ_tcea.cpp | 36 + src/PJ_times.c | 79 -- src/PJ_times.cpp | 79 ++ src/PJ_tmerc.c | 208 ----- src/PJ_tmerc.cpp | 208 +++++ src/PJ_tobmerc.c | 51 -- src/PJ_tobmerc.cpp | 51 ++ src/PJ_tpeqd.c | 107 --- src/PJ_tpeqd.cpp | 107 +++ src/PJ_unitconvert.c | 548 ----------- src/PJ_unitconvert.cpp | 548 +++++++++++ src/PJ_urm5.c | 54 -- src/PJ_urm5.cpp | 54 ++ src/PJ_urmfps.c | 74 -- src/PJ_urmfps.cpp | 74 ++ src/PJ_vandg.c | 106 --- src/PJ_vandg.cpp | 106 +++ src/PJ_vandg2.c | 74 -- src/PJ_vandg2.cpp | 74 ++ src/PJ_vandg4.c | 55 -- src/PJ_vandg4.cpp | 55 ++ src/PJ_vgridshift.c | 142 --- src/PJ_vgridshift.cpp | 142 +++ src/PJ_wag2.c | 38 - src/PJ_wag2.cpp | 38 + src/PJ_wag3.c | 48 - src/PJ_wag3.cpp | 48 + src/PJ_wag7.c | 30 - src/PJ_wag7.cpp | 30 + src/PJ_wink1.c | 44 - src/PJ_wink1.cpp | 44 + src/PJ_wink2.c | 52 -- src/PJ_wink2.cpp | 52 ++ src/aasincos.c | 38 - src/aasincos.cpp | 38 + src/adjlon.c | 20 - src/adjlon.cpp | 20 + src/bch2bps.c | 152 ---- src/bch2bps.cpp | 152 ++++ src/bchgen.c | 58 -- src/bchgen.cpp | 58 ++ src/bin_cct.cmake | 2 +- src/bin_cs2cs.cmake | 4 +- src/bin_geod.cmake | 4 +- src/bin_geodtest.cmake | 2 +- src/bin_gie.cmake | 2 +- src/bin_nad2bin.cmake | 2 +- src/bin_proj.cmake | 6 +- src/biveval.c | 87 -- src/biveval.cpp | 87 ++ src/cct.c | 472 ---------- src/cct.cpp | 472 ++++++++++ src/dmstor.c | 121 --- src/dmstor.cpp | 122 +++ src/emess.c | 68 -- src/emess.cpp | 68 ++ src/gen_cheb.c | 78 -- src/gen_cheb.cpp | 78 ++ src/geocent.c | 436 --------- src/geocent.cpp | 436 +++++++++ src/geod.c | 239 ----- src/geod.cpp | 239 +++++ src/geod_interface.c | 33 - src/geod_interface.cpp | 33 + src/geod_set.c | 75 -- src/geod_set.cpp | 75 ++ src/geodesic.c | 2100 ------------------------------------------- src/geodesic.cpp | 2100 +++++++++++++++++++++++++++++++++++++++++++ src/geodtest.c | 1069 ---------------------- src/geodtest.cpp | 1069 ++++++++++++++++++++++ src/gie.c | 1456 ------------------------------ src/gie.cpp | 1456 ++++++++++++++++++++++++++++++ src/jniproj.c | 472 ---------- src/jniproj.cpp | 472 ++++++++++ src/lib_proj.cmake | 364 ++++---- src/mk_cheby.c | 192 ---- src/mk_cheby.cpp | 192 ++++ src/multistresstest.c | 488 ---------- src/multistresstest.cpp | 488 ++++++++++ src/nad2bin.c | 382 -------- src/nad2bin.cpp | 382 ++++++++ src/nad_cvt.c | 76 -- src/nad_cvt.cpp | 76 ++ src/nad_init.c | 305 ------- src/nad_init.cpp | 305 +++++++ src/nad_intr.c | 68 -- src/nad_intr.cpp | 68 ++ src/p_series.c | 42 - src/p_series.cpp | 42 + src/pj_apply_gridshift.c | 356 -------- src/pj_apply_gridshift.cpp | 356 ++++++++ src/pj_apply_vgridshift.c | 331 ------- src/pj_apply_vgridshift.cpp | 331 +++++++ src/pj_auth.c | 36 - src/pj_auth.cpp | 36 + src/pj_ctx.c | 233 ----- src/pj_ctx.cpp | 233 +++++ src/pj_datum_set.c | 169 ---- src/pj_datum_set.cpp | 169 ++++ src/pj_datums.c | 99 -- src/pj_datums.cpp | 99 ++ src/pj_deriv.c | 70 -- src/pj_deriv.cpp | 70 ++ src/pj_ell_set.c | 727 --------------- src/pj_ell_set.cpp | 727 +++++++++++++++ src/pj_ellps.c | 62 -- src/pj_ellps.cpp | 62 ++ src/pj_errno.c | 17 - src/pj_errno.cpp | 17 + src/pj_factors.c | 107 --- src/pj_factors.cpp | 107 +++ src/pj_fileapi.c | 213 ----- src/pj_fileapi.cpp | 213 +++++ src/pj_fwd.c | 261 ------ src/pj_fwd.cpp | 261 ++++++ src/pj_gauss.c | 101 --- src/pj_gauss.cpp | 101 +++ src/pj_gc_reader.c | 247 ----- src/pj_gc_reader.cpp | 247 +++++ src/pj_geocent.c | 62 -- src/pj_geocent.cpp | 62 ++ src/pj_gridcatalog.c | 295 ------ src/pj_gridcatalog.cpp | 295 ++++++ src/pj_gridinfo.c | 991 -------------------- src/pj_gridinfo.cpp | 991 ++++++++++++++++++++ src/pj_gridlist.c | 219 ----- src/pj_gridlist.cpp | 219 +++++ src/pj_init.c | 870 ------------------ src/pj_init.cpp | 888 ++++++++++++++++++ src/pj_initcache.c | 184 ---- src/pj_initcache.cpp | 184 ++++ src/pj_internal.c | 445 --------- src/pj_internal.cpp | 445 +++++++++ src/pj_inv.c | 238 ----- src/pj_inv.cpp | 238 +++++ src/pj_list.c | 32 - src/pj_list.cpp | 32 + src/pj_log.c | 98 -- src/pj_log.cpp | 98 ++ src/pj_malloc.c | 239 ----- src/pj_malloc.cpp | 240 +++++ src/pj_math.c | 108 --- src/pj_math.cpp | 108 +++ src/pj_mlfn.c | 64 -- src/pj_mlfn.cpp | 64 ++ src/pj_msfn.c | 7 - src/pj_msfn.cpp | 7 + src/pj_mutex.c | 261 ------ src/pj_mutex.cpp | 261 ++++++ src/pj_open_lib.c | 247 ----- src/pj_open_lib.cpp | 247 +++++ src/pj_param.c | 189 ---- src/pj_param.cpp | 189 ++++ src/pj_phi2.c | 46 - src/pj_phi2.cpp | 46 + src/pj_pr_list.c | 104 --- src/pj_pr_list.cpp | 104 +++ src/pj_qsfn.c | 22 - src/pj_qsfn.cpp | 22 + src/pj_release.c | 18 - src/pj_release.cpp | 18 + src/pj_strerrno.c | 109 --- src/pj_strerrno.cpp | 109 +++ src/pj_strtod.c | 195 ---- src/pj_strtod.cpp | 195 ++++ src/pj_transform.c | 1060 ---------------------- src/pj_transform.cpp | 1060 ++++++++++++++++++++++ src/pj_tsfn.c | 16 - src/pj_tsfn.cpp | 16 + src/pj_units.c | 58 -- src/pj_units.cpp | 58 ++ src/pj_utils.c | 181 ---- src/pj_utils.cpp | 181 ++++ src/pj_zpoly1.c | 46 - src/pj_zpoly1.cpp | 46 + src/proj.c | 581 ------------ src/proj.cpp | 581 ++++++++++++ src/proj_4D_api.c | 1281 -------------------------- src/proj_4D_api.cpp | 1285 ++++++++++++++++++++++++++ src/proj_etmerc.c | 360 -------- src/proj_etmerc.cpp | 360 ++++++++ src/proj_mdist.c | 127 --- src/proj_mdist.cpp | 127 +++ src/proj_rouss.c | 156 ---- src/proj_rouss.cpp | 156 ++++ src/proj_strtod.c | 440 --------- src/proj_strtod.cpp | 440 +++++++++ src/projects.h | 7 +- src/rtodms.c | 86 -- src/rtodms.cpp | 86 ++ src/test228.c | 86 -- src/test228.cpp | 86 ++ src/vector1.c | 29 - src/vector1.cpp | 29 + 398 files changed, 39031 insertions(+), 39008 deletions(-) delete mode 100644 src/PJ_aea.c create mode 100644 src/PJ_aea.cpp delete mode 100644 src/PJ_aeqd.c create mode 100644 src/PJ_aeqd.cpp delete mode 100644 src/PJ_affine.c create mode 100644 src/PJ_affine.cpp delete mode 100644 src/PJ_airy.c create mode 100644 src/PJ_airy.cpp delete mode 100644 src/PJ_aitoff.c create mode 100644 src/PJ_aitoff.cpp delete mode 100644 src/PJ_august.c create mode 100644 src/PJ_august.cpp delete mode 100644 src/PJ_axisswap.c create mode 100644 src/PJ_axisswap.cpp delete mode 100644 src/PJ_bacon.c create mode 100644 src/PJ_bacon.cpp delete mode 100644 src/PJ_bertin1953.c create mode 100644 src/PJ_bertin1953.cpp delete mode 100644 src/PJ_bipc.c create mode 100644 src/PJ_bipc.cpp delete mode 100644 src/PJ_boggs.c create mode 100644 src/PJ_boggs.cpp delete mode 100644 src/PJ_bonne.c create mode 100644 src/PJ_bonne.cpp delete mode 100644 src/PJ_calcofi.c create mode 100644 src/PJ_calcofi.cpp delete mode 100644 src/PJ_cart.c create mode 100644 src/PJ_cart.cpp delete mode 100644 src/PJ_cass.c create mode 100644 src/PJ_cass.cpp delete mode 100644 src/PJ_cc.c create mode 100644 src/PJ_cc.cpp delete mode 100644 src/PJ_ccon.c create mode 100644 src/PJ_ccon.cpp delete mode 100644 src/PJ_cea.c create mode 100644 src/PJ_cea.cpp delete mode 100644 src/PJ_chamb.c create mode 100644 src/PJ_chamb.cpp delete mode 100644 src/PJ_collg.c create mode 100644 src/PJ_collg.cpp delete mode 100644 src/PJ_comill.c create mode 100644 src/PJ_comill.cpp delete mode 100644 src/PJ_crast.c create mode 100644 src/PJ_crast.cpp delete mode 100644 src/PJ_deformation.c create mode 100644 src/PJ_deformation.cpp delete mode 100644 src/PJ_denoy.c create mode 100644 src/PJ_denoy.cpp delete mode 100644 src/PJ_eck1.c create mode 100644 src/PJ_eck1.cpp delete mode 100644 src/PJ_eck2.c create mode 100644 src/PJ_eck2.cpp delete mode 100644 src/PJ_eck3.c create mode 100644 src/PJ_eck3.cpp delete mode 100644 src/PJ_eck4.c create mode 100644 src/PJ_eck4.cpp delete mode 100644 src/PJ_eck5.c create mode 100644 src/PJ_eck5.cpp delete mode 100644 src/PJ_eqc.c create mode 100644 src/PJ_eqc.cpp delete mode 100644 src/PJ_eqdc.c create mode 100644 src/PJ_eqdc.cpp delete mode 100644 src/PJ_eqearth.c create mode 100644 src/PJ_eqearth.cpp delete mode 100644 src/PJ_fahey.c create mode 100644 src/PJ_fahey.cpp delete mode 100644 src/PJ_fouc_s.c create mode 100644 src/PJ_fouc_s.cpp delete mode 100644 src/PJ_gall.c create mode 100644 src/PJ_gall.cpp delete mode 100644 src/PJ_geoc.c create mode 100644 src/PJ_geoc.cpp delete mode 100644 src/PJ_geos.c create mode 100644 src/PJ_geos.cpp delete mode 100644 src/PJ_gins8.c create mode 100644 src/PJ_gins8.cpp delete mode 100644 src/PJ_gn_sinu.c create mode 100644 src/PJ_gn_sinu.cpp delete mode 100644 src/PJ_gnom.c create mode 100644 src/PJ_gnom.cpp delete mode 100644 src/PJ_goode.c create mode 100644 src/PJ_goode.cpp delete mode 100644 src/PJ_gstmerc.c create mode 100644 src/PJ_gstmerc.cpp delete mode 100644 src/PJ_hammer.c create mode 100644 src/PJ_hammer.cpp delete mode 100644 src/PJ_hatano.c create mode 100644 src/PJ_hatano.cpp delete mode 100644 src/PJ_healpix.c create mode 100644 src/PJ_healpix.cpp delete mode 100644 src/PJ_helmert.c create mode 100644 src/PJ_helmert.cpp delete mode 100644 src/PJ_hgridshift.c create mode 100644 src/PJ_hgridshift.cpp delete mode 100644 src/PJ_horner.c create mode 100644 src/PJ_horner.cpp delete mode 100644 src/PJ_igh.c create mode 100644 src/PJ_igh.cpp delete mode 100644 src/PJ_imw_p.c create mode 100644 src/PJ_imw_p.cpp delete mode 100644 src/PJ_isea.c create mode 100644 src/PJ_isea.cpp delete mode 100644 src/PJ_krovak.c create mode 100644 src/PJ_krovak.cpp delete mode 100644 src/PJ_labrd.c create mode 100644 src/PJ_labrd.cpp delete mode 100644 src/PJ_laea.c create mode 100644 src/PJ_laea.cpp delete mode 100644 src/PJ_lagrng.c create mode 100644 src/PJ_lagrng.cpp delete mode 100644 src/PJ_larr.c create mode 100644 src/PJ_larr.cpp delete mode 100644 src/PJ_lask.c create mode 100644 src/PJ_lask.cpp delete mode 100644 src/PJ_latlong.c create mode 100644 src/PJ_latlong.cpp delete mode 100644 src/PJ_lcc.c create mode 100644 src/PJ_lcc.cpp delete mode 100644 src/PJ_lcca.c create mode 100644 src/PJ_lcca.cpp delete mode 100644 src/PJ_loxim.c create mode 100644 src/PJ_loxim.cpp delete mode 100644 src/PJ_lsat.c create mode 100644 src/PJ_lsat.cpp delete mode 100644 src/PJ_mbt_fps.c create mode 100644 src/PJ_mbt_fps.cpp delete mode 100644 src/PJ_mbtfpp.c create mode 100644 src/PJ_mbtfpp.cpp delete mode 100644 src/PJ_mbtfpq.c create mode 100644 src/PJ_mbtfpq.cpp delete mode 100644 src/PJ_merc.c create mode 100644 src/PJ_merc.cpp delete mode 100644 src/PJ_mill.c create mode 100644 src/PJ_mill.cpp delete mode 100644 src/PJ_misrsom.c create mode 100644 src/PJ_misrsom.cpp delete mode 100644 src/PJ_mod_ster.c create mode 100644 src/PJ_mod_ster.cpp delete mode 100644 src/PJ_moll.c create mode 100644 src/PJ_moll.cpp delete mode 100644 src/PJ_molodensky.c create mode 100644 src/PJ_molodensky.cpp delete mode 100644 src/PJ_natearth.c create mode 100644 src/PJ_natearth.cpp delete mode 100644 src/PJ_natearth2.c create mode 100644 src/PJ_natearth2.cpp delete mode 100644 src/PJ_nell.c create mode 100644 src/PJ_nell.cpp delete mode 100644 src/PJ_nell_h.c create mode 100644 src/PJ_nell_h.cpp delete mode 100644 src/PJ_nocol.c create mode 100644 src/PJ_nocol.cpp delete mode 100644 src/PJ_nsper.c create mode 100644 src/PJ_nsper.cpp delete mode 100644 src/PJ_nzmg.c create mode 100644 src/PJ_nzmg.cpp delete mode 100644 src/PJ_ob_tran.c create mode 100644 src/PJ_ob_tran.cpp delete mode 100644 src/PJ_ocea.c create mode 100644 src/PJ_ocea.cpp delete mode 100644 src/PJ_oea.c create mode 100644 src/PJ_oea.cpp delete mode 100644 src/PJ_omerc.c create mode 100644 src/PJ_omerc.cpp delete mode 100644 src/PJ_ortho.c create mode 100644 src/PJ_ortho.cpp delete mode 100644 src/PJ_patterson.c create mode 100644 src/PJ_patterson.cpp delete mode 100644 src/PJ_pipeline.c create mode 100644 src/PJ_pipeline.cpp delete mode 100644 src/PJ_poly.c create mode 100644 src/PJ_poly.cpp delete mode 100644 src/PJ_putp2.c create mode 100644 src/PJ_putp2.cpp delete mode 100644 src/PJ_putp3.c create mode 100644 src/PJ_putp3.cpp delete mode 100644 src/PJ_putp4p.c create mode 100644 src/PJ_putp4p.cpp delete mode 100644 src/PJ_putp5.c create mode 100644 src/PJ_putp5.cpp delete mode 100644 src/PJ_putp6.c create mode 100644 src/PJ_putp6.cpp delete mode 100644 src/PJ_qsc.c create mode 100644 src/PJ_qsc.cpp delete mode 100644 src/PJ_robin.c create mode 100644 src/PJ_robin.cpp delete mode 100644 src/PJ_rpoly.c create mode 100644 src/PJ_rpoly.cpp delete mode 100644 src/PJ_sch.c create mode 100644 src/PJ_sch.cpp delete mode 100644 src/PJ_sconics.c create mode 100644 src/PJ_sconics.cpp delete mode 100644 src/PJ_somerc.c create mode 100644 src/PJ_somerc.cpp delete mode 100644 src/PJ_stere.c create mode 100644 src/PJ_stere.cpp delete mode 100644 src/PJ_sterea.c create mode 100644 src/PJ_sterea.cpp delete mode 100644 src/PJ_sts.c create mode 100644 src/PJ_sts.cpp delete mode 100644 src/PJ_tcc.c create mode 100644 src/PJ_tcc.cpp delete mode 100644 src/PJ_tcea.c create mode 100644 src/PJ_tcea.cpp delete mode 100644 src/PJ_times.c create mode 100644 src/PJ_times.cpp delete mode 100644 src/PJ_tmerc.c create mode 100644 src/PJ_tmerc.cpp delete mode 100644 src/PJ_tobmerc.c create mode 100644 src/PJ_tobmerc.cpp delete mode 100644 src/PJ_tpeqd.c create mode 100644 src/PJ_tpeqd.cpp delete mode 100644 src/PJ_unitconvert.c create mode 100644 src/PJ_unitconvert.cpp delete mode 100644 src/PJ_urm5.c create mode 100644 src/PJ_urm5.cpp delete mode 100644 src/PJ_urmfps.c create mode 100644 src/PJ_urmfps.cpp delete mode 100644 src/PJ_vandg.c create mode 100644 src/PJ_vandg.cpp delete mode 100644 src/PJ_vandg2.c create mode 100644 src/PJ_vandg2.cpp delete mode 100644 src/PJ_vandg4.c create mode 100644 src/PJ_vandg4.cpp delete mode 100644 src/PJ_vgridshift.c create mode 100644 src/PJ_vgridshift.cpp delete mode 100644 src/PJ_wag2.c create mode 100644 src/PJ_wag2.cpp delete mode 100644 src/PJ_wag3.c create mode 100644 src/PJ_wag3.cpp delete mode 100644 src/PJ_wag7.c create mode 100644 src/PJ_wag7.cpp delete mode 100644 src/PJ_wink1.c create mode 100644 src/PJ_wink1.cpp delete mode 100644 src/PJ_wink2.c create mode 100644 src/PJ_wink2.cpp delete mode 100644 src/aasincos.c create mode 100644 src/aasincos.cpp delete mode 100644 src/adjlon.c create mode 100644 src/adjlon.cpp delete mode 100644 src/bch2bps.c create mode 100644 src/bch2bps.cpp delete mode 100644 src/bchgen.c create mode 100644 src/bchgen.cpp delete mode 100644 src/biveval.c create mode 100644 src/biveval.cpp delete mode 100644 src/cct.c create mode 100644 src/cct.cpp delete mode 100644 src/dmstor.c create mode 100644 src/dmstor.cpp delete mode 100644 src/emess.c create mode 100644 src/emess.cpp delete mode 100644 src/gen_cheb.c create mode 100644 src/gen_cheb.cpp delete mode 100644 src/geocent.c create mode 100644 src/geocent.cpp delete mode 100644 src/geod.c create mode 100644 src/geod.cpp delete mode 100644 src/geod_interface.c create mode 100644 src/geod_interface.cpp delete mode 100644 src/geod_set.c create mode 100644 src/geod_set.cpp delete mode 100644 src/geodesic.c create mode 100644 src/geodesic.cpp delete mode 100644 src/geodtest.c create mode 100644 src/geodtest.cpp delete mode 100644 src/gie.c create mode 100644 src/gie.cpp delete mode 100644 src/jniproj.c create mode 100644 src/jniproj.cpp delete mode 100644 src/mk_cheby.c create mode 100644 src/mk_cheby.cpp delete mode 100644 src/multistresstest.c create mode 100644 src/multistresstest.cpp delete mode 100644 src/nad2bin.c create mode 100644 src/nad2bin.cpp delete mode 100644 src/nad_cvt.c create mode 100644 src/nad_cvt.cpp delete mode 100644 src/nad_init.c create mode 100644 src/nad_init.cpp delete mode 100644 src/nad_intr.c create mode 100644 src/nad_intr.cpp delete mode 100644 src/p_series.c create mode 100644 src/p_series.cpp delete mode 100644 src/pj_apply_gridshift.c create mode 100644 src/pj_apply_gridshift.cpp delete mode 100644 src/pj_apply_vgridshift.c create mode 100644 src/pj_apply_vgridshift.cpp delete mode 100644 src/pj_auth.c create mode 100644 src/pj_auth.cpp delete mode 100644 src/pj_ctx.c create mode 100644 src/pj_ctx.cpp delete mode 100644 src/pj_datum_set.c create mode 100644 src/pj_datum_set.cpp delete mode 100644 src/pj_datums.c create mode 100644 src/pj_datums.cpp delete mode 100644 src/pj_deriv.c create mode 100644 src/pj_deriv.cpp delete mode 100644 src/pj_ell_set.c create mode 100644 src/pj_ell_set.cpp delete mode 100644 src/pj_ellps.c create mode 100644 src/pj_ellps.cpp delete mode 100644 src/pj_errno.c create mode 100644 src/pj_errno.cpp delete mode 100644 src/pj_factors.c create mode 100644 src/pj_factors.cpp delete mode 100644 src/pj_fileapi.c create mode 100644 src/pj_fileapi.cpp delete mode 100644 src/pj_fwd.c create mode 100644 src/pj_fwd.cpp delete mode 100644 src/pj_gauss.c create mode 100644 src/pj_gauss.cpp delete mode 100644 src/pj_gc_reader.c create mode 100644 src/pj_gc_reader.cpp delete mode 100644 src/pj_geocent.c create mode 100644 src/pj_geocent.cpp delete mode 100644 src/pj_gridcatalog.c create mode 100644 src/pj_gridcatalog.cpp delete mode 100644 src/pj_gridinfo.c create mode 100644 src/pj_gridinfo.cpp delete mode 100644 src/pj_gridlist.c create mode 100644 src/pj_gridlist.cpp delete mode 100644 src/pj_init.c create mode 100644 src/pj_init.cpp delete mode 100644 src/pj_initcache.c create mode 100644 src/pj_initcache.cpp delete mode 100644 src/pj_internal.c create mode 100644 src/pj_internal.cpp delete mode 100644 src/pj_inv.c create mode 100644 src/pj_inv.cpp delete mode 100644 src/pj_list.c create mode 100644 src/pj_list.cpp delete mode 100644 src/pj_log.c create mode 100644 src/pj_log.cpp delete mode 100644 src/pj_malloc.c create mode 100644 src/pj_malloc.cpp delete mode 100644 src/pj_math.c create mode 100644 src/pj_math.cpp delete mode 100644 src/pj_mlfn.c create mode 100644 src/pj_mlfn.cpp delete mode 100644 src/pj_msfn.c create mode 100644 src/pj_msfn.cpp delete mode 100644 src/pj_mutex.c create mode 100644 src/pj_mutex.cpp delete mode 100644 src/pj_open_lib.c create mode 100644 src/pj_open_lib.cpp delete mode 100644 src/pj_param.c create mode 100644 src/pj_param.cpp delete mode 100644 src/pj_phi2.c create mode 100644 src/pj_phi2.cpp delete mode 100644 src/pj_pr_list.c create mode 100644 src/pj_pr_list.cpp delete mode 100644 src/pj_qsfn.c create mode 100644 src/pj_qsfn.cpp delete mode 100644 src/pj_release.c create mode 100644 src/pj_release.cpp delete mode 100644 src/pj_strerrno.c create mode 100644 src/pj_strerrno.cpp delete mode 100644 src/pj_strtod.c create mode 100644 src/pj_strtod.cpp delete mode 100644 src/pj_transform.c create mode 100644 src/pj_transform.cpp delete mode 100644 src/pj_tsfn.c create mode 100644 src/pj_tsfn.cpp delete mode 100644 src/pj_units.c create mode 100644 src/pj_units.cpp delete mode 100644 src/pj_utils.c create mode 100644 src/pj_utils.cpp delete mode 100644 src/pj_zpoly1.c create mode 100644 src/pj_zpoly1.cpp delete mode 100644 src/proj.c create mode 100644 src/proj.cpp delete mode 100644 src/proj_4D_api.c create mode 100644 src/proj_4D_api.cpp delete mode 100644 src/proj_etmerc.c create mode 100644 src/proj_etmerc.cpp delete mode 100644 src/proj_mdist.c create mode 100644 src/proj_mdist.cpp delete mode 100644 src/proj_rouss.c create mode 100644 src/proj_rouss.cpp delete mode 100644 src/proj_strtod.c create mode 100644 src/proj_strtod.cpp delete mode 100644 src/rtodms.c create mode 100644 src/rtodms.cpp delete mode 100644 src/test228.c create mode 100644 src/test228.cpp delete mode 100644 src/vector1.c create mode 100644 src/vector1.cpp (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index f484acd8..990ca74d 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.c \ + lib_proj.cmake CMakeLists.txt bin_geodtest.cmake geodtest.cpp \ pj_wkt1_grammar.y pj_wkt2_grammar.y -proj_SOURCES = proj.c gen_cheb.c p_series.c +proj_SOURCES = proj.cpp gen_cheb.cpp p_series.cpp projinfo_SOURCES = projinfo.cpp -cs2cs_SOURCES = cs2cs.cpp gen_cheb.c p_series.c -cct_SOURCES = cct.c proj_strtod.c proj_strtod.h optargpm.h -nad2bin_SOURCES = nad2bin.c -geod_SOURCES = geod.c geod_set.c geod_interface.c geod_interface.h +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 -gie_SOURCES = gie.c proj_strtod.c proj_strtod.h optargpm.h -multistresstest_SOURCES = multistresstest.c -test228_SOURCES = test228.c -geodtest_SOURCES = geodtest.c +gie_SOURCES = gie.cpp proj_strtod.cpp proj_strtod.h optargpm.h +multistresstest_SOURCES = multistresstest.cpp +test228_SOURCES = test228.cpp +geodtest_SOURCES = geodtest.cpp cct_LDADD = libproj.la cs2cs_LDADD = libproj.la @@ -51,51 +51,51 @@ 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.c PJ_gnom.c PJ_laea.c PJ_mod_ster.c \ - PJ_nsper.c PJ_nzmg.c PJ_ortho.c PJ_stere.c PJ_sterea.c \ - PJ_aea.c PJ_bipc.c PJ_bonne.c PJ_eqdc.c PJ_isea.c PJ_ccon.c\ - PJ_imw_p.c PJ_krovak.c PJ_lcc.c PJ_poly.c \ - PJ_rpoly.c PJ_sconics.c proj_rouss.c \ - PJ_cass.c PJ_cc.c PJ_cea.c PJ_eqc.c PJ_gall.c PJ_geoc.c \ - PJ_labrd.c PJ_lsat.c PJ_misrsom.c PJ_merc.c \ - PJ_mill.c PJ_ocea.c PJ_omerc.c PJ_somerc.c \ - PJ_tcc.c PJ_tcea.c PJ_times.c PJ_tmerc.c PJ_tobmerc.c \ - PJ_airy.c PJ_aitoff.c PJ_august.c PJ_bacon.c \ - PJ_bertin1953.c PJ_chamb.c PJ_hammer.c PJ_lagrng.c PJ_larr.c \ - PJ_lask.c PJ_latlong.c PJ_nocol.c PJ_ob_tran.c PJ_oea.c \ - PJ_tpeqd.c PJ_vandg.c PJ_vandg2.c PJ_vandg4.c \ - PJ_wag7.c PJ_lcca.c PJ_geos.c proj_etmerc.c \ - PJ_boggs.c PJ_collg.c PJ_comill.c PJ_crast.c PJ_denoy.c \ - PJ_eck1.c PJ_eck2.c PJ_eck3.c PJ_eck4.c \ - PJ_eck5.c PJ_fahey.c PJ_fouc_s.c PJ_gins8.c PJ_gstmerc.c \ - PJ_gn_sinu.c PJ_goode.c PJ_igh.c PJ_hatano.c PJ_loxim.c \ - PJ_mbt_fps.c PJ_mbtfpp.c PJ_mbtfpq.c PJ_moll.c \ - PJ_nell.c PJ_nell_h.c PJ_patterson.c PJ_putp2.c PJ_putp3.c \ - PJ_putp4p.c PJ_putp5.c PJ_putp6.c PJ_qsc.c PJ_robin.c \ - PJ_sch.c PJ_sts.c PJ_urm5.c PJ_urmfps.c PJ_wag2.c \ - PJ_wag3.c PJ_wink1.c PJ_wink2.c pj_geocent.c \ - aasincos.c adjlon.c bch2bps.c bchgen.c \ - biveval.c dmstor.c mk_cheby.c pj_auth.c \ - pj_deriv.c pj_ell_set.c pj_ellps.c pj_errno.c \ - pj_factors.c pj_fwd.c pj_init.c pj_inv.c \ - pj_list.c pj_malloc.c pj_mlfn.c pj_msfn.c proj_mdist.c \ - pj_open_lib.c pj_param.c pj_phi2.c pj_pr_list.c \ - pj_qsfn.c pj_strerrno.c \ - pj_tsfn.c pj_units.c pj_ctx.c pj_log.c pj_zpoly1.c rtodms.c \ - vector1.c pj_release.c pj_gauss.c \ - PJ_healpix.c PJ_natearth.c PJ_natearth2.c PJ_calcofi.c pj_fileapi.c \ - PJ_eqearth.c \ + 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 \ + 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_healpix.cpp PJ_natearth.cpp PJ_natearth2.cpp PJ_calcofi.cpp pj_fileapi.cpp \ + PJ_eqearth.cpp \ \ - pj_gc_reader.c pj_gridcatalog.c \ - nad_cvt.c nad_init.c nad_intr.c emess.c emess.h \ - pj_apply_gridshift.c pj_datums.c pj_datum_set.c pj_transform.c \ - geocent.c geocent.h pj_utils.c pj_gridinfo.c pj_gridlist.c \ - jniproj.c pj_mutex.c pj_initcache.c pj_apply_vgridshift.c geodesic.c \ - pj_strtod.c pj_math.c\ + pj_gc_reader.cpp pj_gridcatalog.cpp \ + nad_cvt.cpp nad_init.cpp nad_intr.cpp emess.cpp emess.h \ + 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.c PJ_cart.c PJ_pipeline.c PJ_horner.c PJ_helmert.c \ - PJ_vgridshift.c PJ_hgridshift.c PJ_unitconvert.c PJ_molodensky.c \ - PJ_deformation.c pj_internal.c PJ_axisswap.c PJ_affine.c \ + 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 \ 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.c b/src/PJ_aea.c deleted file mode 100644 index 640f1013..00000000 --- a/src/PJ_aea.c +++ /dev/null @@ -1,222 +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 ); -} - - -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; -}; - - -static void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_aea.cpp b/src/PJ_aea.cpp new file mode 100644 index 00000000..e4d1dc4f --- /dev/null +++ b/src/PJ_aea.cpp @@ -0,0 +1,222 @@ +/****************************************************************************** + * 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 ); +} + + +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; +}; + + +static PJ *destructor (PJ *P, int errlev) { /* Destructor */ + if (0==P) + return 0; + + if (0==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 (0==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 (0==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.c b/src/PJ_aeqd.c deleted file mode 100644 index 5d8c3d38..00000000 --- a/src/PJ_aeqd.c +++ /dev/null @@ -1,323 +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" - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -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; -}; - -PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; - -#define EPS10 1.e-10 -#define TOL 1.e-14 - - -static void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (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 = 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 = 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 = 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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_aeqd.cpp b/src/PJ_aeqd.cpp new file mode 100644 index 00000000..72c7ae95 --- /dev/null +++ b/src/PJ_aeqd.cpp @@ -0,0 +1,323 @@ +/****************************************************************************** + * 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" + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +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; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_affine.c deleted file mode 100644 index 0d8b6374..00000000 --- a/src/PJ_affine.c +++ /dev/null @@ -1,246 +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"); - -struct pj_affine_coeffs { - double s11; - double s12; - double s13; - double s21; - double s22; - double s23; - double s31; - double s32; - double s33; - double tscale; -}; - -struct pj_opaque_affine { - double xoff; - double yoff; - double zoff; - double toff; - struct pj_affine_coeffs forward; - struct pj_affine_coeffs reverse; -}; - - -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 = pj_calloc(1, sizeof(struct pj_opaque_affine)); - if (0==Q) - return 0; - - /* 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 = NULL; - P->inv3d = NULL; - P->inv = NULL; - } 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 (0==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 (0==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_affine.cpp b/src/PJ_affine.cpp new file mode 100644 index 00000000..b8fa4c68 --- /dev/null +++ b/src/PJ_affine.cpp @@ -0,0 +1,246 @@ +/************************************************************************ +* 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"); + +struct pj_affine_coeffs { + double s11; + double s12; + double s13; + double s21; + double s22; + double s23; + double s31; + double s32; + double s33; + double tscale; +}; + +struct pj_opaque_affine { + double xoff; + double yoff; + double zoff; + double toff; + struct pj_affine_coeffs forward; + struct pj_affine_coeffs reverse; +}; + + +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 (0==Q) + return 0; + + /* 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 = NULL; + P->inv3d = NULL; + P->inv = NULL; + } 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 (0==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 (0==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.c b/src/PJ_airy.c deleted file mode 100644 index 75f95780..00000000 --- a/src/PJ_airy.c +++ /dev/null @@ -1,151 +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="; - - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -struct pj_opaque { - double p_halfpi; - double sinph0; - double cosph0; - double Cb; - enum Mode mode; - int no_cut; /* do not cut at hemisphere limit */ -}; - - -# 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_airy.cpp b/src/PJ_airy.cpp new file mode 100644 index 00000000..037362b3 --- /dev/null +++ b/src/PJ_airy.cpp @@ -0,0 +1,151 @@ +/****************************************************************************** + * 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="; + + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +struct pj_opaque { + double p_halfpi; + double sinph0; + double cosph0; + double Cb; + enum Mode mode; + int no_cut; /* do not cut at hemisphere limit */ +}; + + +# 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 (0==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.c b/src/PJ_aitoff.c deleted file mode 100644 index 82c981d5..00000000 --- a/src/PJ_aitoff.c +++ /dev/null @@ -1,197 +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" - - -enum Mode { - AITOFF = 0, - WINKEL_TRIPEL = 1 -}; - -struct pj_opaque { - double cosphi1; - enum Mode mode; -}; - - -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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - Q->mode = AITOFF; - return setup(P); -} - - -PJ *PROJECTION(wintri) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_aitoff.cpp b/src/PJ_aitoff.cpp new file mode 100644 index 00000000..1af75469 --- /dev/null +++ b/src/PJ_aitoff.cpp @@ -0,0 +1,197 @@ +/****************************************************************************** + * 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" + + +enum Mode { + AITOFF = 0, + WINKEL_TRIPEL = 1 +}; + +struct pj_opaque { + double cosphi1; + enum Mode mode; +}; + + +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 (0==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 (0==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.c b/src/PJ_august.c deleted file mode 100644 index e891e84e..00000000 --- a/src/PJ_august.c +++ /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 = 0; - P->fwd = s_forward; - P->es = 0.; - return P; -} diff --git a/src/PJ_august.cpp b/src/PJ_august.cpp new file mode 100644 index 00000000..e891e84e --- /dev/null +++ b/src/PJ_august.cpp @@ -0,0 +1,34 @@ +#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 = 0; + P->fwd = s_forward; + P->es = 0.; + return P; +} diff --git a/src/PJ_axisswap.c b/src/PJ_axisswap.c deleted file mode 100644 index 6db4a7d2..00000000 --- a/src/PJ_axisswap.c +++ /dev/null @@ -1,300 +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"); - -struct pj_opaque { - unsigned int axis[4]; - int sign[4]; -}; - -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 = pj_calloc (1, sizeof (struct pj_opaque)); - char *s; - unsigned int i, j, n = 0; - - if (0==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 == NULL && P->fwd3d == NULL && P->fwd == NULL) { - 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_axisswap.cpp b/src/PJ_axisswap.cpp new file mode 100644 index 00000000..d21eddb1 --- /dev/null +++ b/src/PJ_axisswap.cpp @@ -0,0 +1,300 @@ +/*********************************************************************** + + 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"); + +struct pj_opaque { + unsigned int axis[4]; + int sign[4]; +}; + +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 (0==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 == NULL && P->fwd3d == NULL && P->fwd == NULL) { + 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.c b/src/PJ_bacon.c deleted file mode 100644 index f0f11c9d..00000000 --- a/src/PJ_bacon.c +++ /dev/null @@ -1,79 +0,0 @@ -# define HLFPI2 2.46740110027233965467 /* (pi/2)^2 */ -# define EPS 1e-10 -#define PJ_LIB__ -#include -#include - -#include "projects.h" - - -struct pj_opaque { - int bacn; - int ortl; -}; - -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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_bacon.cpp b/src/PJ_bacon.cpp new file mode 100644 index 00000000..f995e420 --- /dev/null +++ b/src/PJ_bacon.cpp @@ -0,0 +1,79 @@ +# define HLFPI2 2.46740110027233965467 /* (pi/2)^2 */ +# define EPS 1e-10 +#define PJ_LIB__ +#include +#include + +#include "projects.h" + + +struct pj_opaque { + int bacn; + int ortl; +}; + +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 (0==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 (0==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 (0==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.c b/src/PJ_bertin1953.c deleted file mode 100644 index 5a027da2..00000000 --- a/src/PJ_bertin1953.c +++ /dev/null @@ -1,94 +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."; - -struct pj_opaque { - double cos_delta_phi, sin_delta_phi, cos_delta_gamma, sin_delta_gamma, deltaLambda; -}; - - -static XY s_forward (LP lp, PJ *P) { - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_bertin1953.cpp b/src/PJ_bertin1953.cpp new file mode 100644 index 00000000..46420314 --- /dev/null +++ b/src/PJ_bertin1953.cpp @@ -0,0 +1,94 @@ +/* + 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."; + +struct pj_opaque { + double cos_delta_phi, sin_delta_phi, cos_delta_gamma, sin_delta_gamma, deltaLambda; +}; + + +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 (0==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.c b/src/PJ_bipc.c deleted file mode 100644 index e4a69077..00000000 --- a/src/PJ_bipc.c +++ /dev/null @@ -1,174 +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 - - -struct pj_opaque { - int noskew; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_bipc.cpp b/src/PJ_bipc.cpp new file mode 100644 index 00000000..243ecccd --- /dev/null +++ b/src/PJ_bipc.cpp @@ -0,0 +1,174 @@ +#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 + + +struct pj_opaque { + int noskew; +}; + + +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 (0==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.c b/src/PJ_boggs.c deleted file mode 100644 index 119357c0..00000000 --- a/src/PJ_boggs.c +++ /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_boggs.cpp b/src/PJ_boggs.cpp new file mode 100644 index 00000000..119357c0 --- /dev/null +++ b/src/PJ_boggs.cpp @@ -0,0 +1,43 @@ +#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.c b/src/PJ_bonne.c deleted file mode 100644 index d3d5b757..00000000 --- a/src/PJ_bonne.c +++ /dev/null @@ -1,134 +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 - -struct pj_opaque { - double phi1; - double cphi1; - double am1; - double m1; - double *en; -}; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(bonne) { - double c; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0==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_bonne.cpp b/src/PJ_bonne.cpp new file mode 100644 index 00000000..6c51af7c --- /dev/null +++ b/src/PJ_bonne.cpp @@ -0,0 +1,134 @@ +#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 + +struct pj_opaque { + double phi1; + double cphi1; + double am1; + double m1; + double *en; +}; + + +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 (0==P) + return 0; + + if (0==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 (0==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 (0==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.c b/src/PJ_calcofi.c deleted file mode 100644 index ed4cfe86..00000000 --- a/src/PJ_calcofi.c +++ /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 = 0; - - /* 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_calcofi.cpp b/src/PJ_calcofi.cpp new file mode 100644 index 00000000..ed4cfe86 --- /dev/null +++ b/src/PJ_calcofi.cpp @@ -0,0 +1,163 @@ +#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 = 0; + + /* 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.c b/src/PJ_cart.c deleted file mode 100644 index 6fed9985..00000000 --- a/src/PJ_cart.c +++ /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_cart.cpp b/src/PJ_cart.cpp new file mode 100644 index 00000000..6fed9985 --- /dev/null +++ b/src/PJ_cart.cpp @@ -0,0 +1,219 @@ +/****************************************************************************** + * 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.c b/src/PJ_cass.c deleted file mode 100644 index 4e3b4251..00000000 --- a/src/PJ_cass.c +++ /dev/null @@ -1,121 +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 - - -struct pj_opaque { - double *en; - double m0; -}; - - - -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 = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==P->opaque) - return pj_default_destructor (P, ENOMEM); - P->destructor = destructor; - - P->opaque->en = pj_enfn (P->es); - if (0==P->opaque->en) - return pj_default_destructor (P, ENOMEM); - - P->opaque->m0 = pj_mlfn (P->phi0, sin (P->phi0), cos (P->phi0), P->opaque->en); - P->inv = e_inverse; - P->fwd = e_forward; - - return P; -} diff --git a/src/PJ_cass.cpp b/src/PJ_cass.cpp new file mode 100644 index 00000000..cc610940 --- /dev/null +++ b/src/PJ_cass.cpp @@ -0,0 +1,121 @@ +#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 + + +struct pj_opaque { + double *en; + double m0; +}; + + + +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 (0==P) + return 0; + + if (0==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 (0==P->opaque) + return pj_default_destructor (P, ENOMEM); + P->destructor = destructor; + + static_cast(P->opaque)->en = pj_enfn (P->es); + if (0==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.c b/src/PJ_cc.c deleted file mode 100644 index 152e6e4a..00000000 --- a/src/PJ_cc.c +++ /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_cc.cpp b/src/PJ_cc.cpp new file mode 100644 index 00000000..152e6e4a --- /dev/null +++ b/src/PJ_cc.cpp @@ -0,0 +1,41 @@ +#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.c b/src/PJ_ccon.c deleted file mode 100644 index 0b8b70cb..00000000 --- a/src/PJ_ccon.c +++ /dev/null @@ -1,107 +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 - -struct pj_opaque { - double phi1; - double ctgphi1; - double sinphi1; - double cosphi1; - double *en; -}; - -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 = 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 = 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 void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(ccon) { - - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_ccon.cpp b/src/PJ_ccon.cpp new file mode 100644 index 00000000..6ff2d3b1 --- /dev/null +++ b/src/PJ_ccon.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * 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 + +struct pj_opaque { + double phi1; + double ctgphi1; + double sinphi1; + double cosphi1; + double *en; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_cea.c deleted file mode 100644 index e05e524b..00000000 --- a/src/PJ_cea.c +++ /dev/null @@ -1,101 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -struct pj_opaque { - double qp; - double *apa; -}; - -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 / P->opaque->qp), 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(cea) { - double t = 0.0; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_cea.cpp b/src/PJ_cea.cpp new file mode 100644 index 00000000..ee7eebf5 --- /dev/null +++ b/src/PJ_cea.cpp @@ -0,0 +1,101 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +struct pj_opaque { + double qp; + double *apa; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_chamb.c deleted file mode 100644 index 6951d6a1..00000000 --- a/src/PJ_chamb.c +++ /dev/null @@ -1,139 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -typedef struct { double r, Az; } VECT; -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; -}; - -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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_chamb.cpp b/src/PJ_chamb.cpp new file mode 100644 index 00000000..8d8f05ee --- /dev/null +++ b/src/PJ_chamb.cpp @@ -0,0 +1,139 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +typedef struct { double r, Az; } VECT; +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; +}; + +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 (0==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.c b/src/PJ_collg.c deleted file mode 100644 index 7904de29..00000000 --- a/src/PJ_collg.c +++ /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_collg.cpp b/src/PJ_collg.cpp new file mode 100644 index 00000000..7904de29 --- /dev/null +++ b/src/PJ_collg.cpp @@ -0,0 +1,53 @@ +#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.c b/src/PJ_comill.c deleted file mode 100644 index b6e0192e..00000000 --- a/src/PJ_comill.c +++ /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_comill.cpp b/src/PJ_comill.cpp new file mode 100644 index 00000000..b6e0192e --- /dev/null +++ b/src/PJ_comill.cpp @@ -0,0 +1,84 @@ +/* +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.c b/src/PJ_crast.c deleted file mode 100644 index 4e4dee8b..00000000 --- a/src/PJ_crast.c +++ /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_crast.cpp b/src/PJ_crast.cpp new file mode 100644 index 00000000..4e4dee8b --- /dev/null +++ b/src/PJ_crast.cpp @@ -0,0 +1,40 @@ +#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.c b/src/PJ_deformation.c deleted file mode 100644 index 5511eed4..00000000 --- a/src/PJ_deformation.c +++ /dev/null @@ -1,323 +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 - -struct pj_opaque { - double t_obs; - double t_epoch; - PJ *cart; -}; - -/********************************************************************************/ -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, 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 void *destructor(PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - if (P->opaque->cart) - P->opaque->cart->destructor (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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return destructor(P, ENOMEM); - P->opaque = (void *) Q; - - Q->cart = proj_create(P->ctx, "+proj=cart"); - if (Q->cart == 0) - 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 = 0; - P->inv = 0; - - P->left = PJ_IO_UNITS_CARTESIAN; - P->right = PJ_IO_UNITS_CARTESIAN; - P->destructor = destructor; - - return P; -} - diff --git a/src/PJ_deformation.cpp b/src/PJ_deformation.cpp new file mode 100644 index 00000000..1398f5fd --- /dev/null +++ b/src/PJ_deformation.cpp @@ -0,0 +1,323 @@ +/*********************************************************************** + + 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 + +struct pj_opaque { + double t_obs; + double t_epoch; + PJ *cart; +}; + +/********************************************************************************/ +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 (0==P) + return 0; + + if (0==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 (0==Q) + return destructor(P, ENOMEM); + P->opaque = (void *) Q; + + Q->cart = proj_create(P->ctx, "+proj=cart"); + if (Q->cart == 0) + 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 = 0; + P->inv = 0; + + P->left = PJ_IO_UNITS_CARTESIAN; + P->right = PJ_IO_UNITS_CARTESIAN; + P->destructor = destructor; + + return P; +} + diff --git a/src/PJ_denoy.c b/src/PJ_denoy.c deleted file mode 100644 index 5c337c45..00000000 --- a/src/PJ_denoy.c +++ /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_denoy.cpp b/src/PJ_denoy.cpp new file mode 100644 index 00000000..5c337c45 --- /dev/null +++ b/src/PJ_denoy.cpp @@ -0,0 +1,32 @@ +#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.c b/src/PJ_eck1.c deleted file mode 100644 index 88a7430c..00000000 --- a/src/PJ_eck1.c +++ /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_eck1.cpp b/src/PJ_eck1.cpp new file mode 100644 index 00000000..88a7430c --- /dev/null +++ b/src/PJ_eck1.cpp @@ -0,0 +1,41 @@ +#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.c b/src/PJ_eck2.c deleted file mode 100644 index f76ab4ec..00000000 --- a/src/PJ_eck2.c +++ /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_eck2.cpp b/src/PJ_eck2.cpp new file mode 100644 index 00000000..f76ab4ec --- /dev/null +++ b/src/PJ_eck2.cpp @@ -0,0 +1,56 @@ +#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.c b/src/PJ_eck3.c deleted file mode 100644 index 8f91c8bb..00000000 --- a/src/PJ_eck3.c +++ /dev/null @@ -1,110 +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"; - -struct pj_opaque { - double C_x, C_y, A, B; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_eck3.cpp b/src/PJ_eck3.cpp new file mode 100644 index 00000000..7993d781 --- /dev/null +++ b/src/PJ_eck3.cpp @@ -0,0 +1,110 @@ +#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"; + +struct pj_opaque { + double C_x, C_y, A, B; +}; + + +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 (0==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 (0==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 (0==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 (0==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.c b/src/PJ_eck4.c deleted file mode 100644 index 4fa4c21f..00000000 --- a/src/PJ_eck4.c +++ /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_eck4.cpp b/src/PJ_eck4.cpp new file mode 100644 index 00000000..4fa4c21f --- /dev/null +++ b/src/PJ_eck4.cpp @@ -0,0 +1,63 @@ +#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.c b/src/PJ_eck5.c deleted file mode 100644 index f9f28460..00000000 --- a/src/PJ_eck5.c +++ /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_eck5.cpp b/src/PJ_eck5.cpp new file mode 100644 index 00000000..f9f28460 --- /dev/null +++ b/src/PJ_eck5.cpp @@ -0,0 +1,40 @@ +#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.c b/src/PJ_eqc.c deleted file mode 100644 index 07d6141c..00000000 --- a/src/PJ_eqc.c +++ /dev/null @@ -1,52 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -struct pj_opaque { - double rc; -}; - -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 = 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 = P->opaque; - - lp.lam = xy.x / Q->rc; - lp.phi = xy.y + P->phi0; - - return lp; -} - - -PJ *PROJECTION(eqc) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_eqc.cpp b/src/PJ_eqc.cpp new file mode 100644 index 00000000..b95471f7 --- /dev/null +++ b/src/PJ_eqc.cpp @@ -0,0 +1,52 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +struct pj_opaque { + double rc; +}; + +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 (0==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.c b/src/PJ_eqdc.c deleted file mode 100644 index 8caca87a..00000000 --- a/src/PJ_eqdc.c +++ /dev/null @@ -1,120 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -struct pj_opaque { - double phi1; - double phi2; - double n; - double rho; - double rho0; - double c; - double *en; - int ellips; -}; - -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 = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(eqdc) { - double cosphi, sinphi; - int secant; - - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_eqdc.cpp b/src/PJ_eqdc.cpp new file mode 100644 index 00000000..468b16df --- /dev/null +++ b/src/PJ_eqdc.cpp @@ -0,0 +1,120 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" +#include "proj_math.h" + +struct pj_opaque { + double phi1; + double phi2; + double n; + double rho; + double rho0; + double c; + double *en; + int ellips; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_eqearth.c deleted file mode 100644 index a0d4467b..00000000 --- a/src/PJ_eqearth.c +++ /dev/null @@ -1,162 +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 - -struct pj_opaque { - double qp; - double rqda; - double *apa; -}; - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal/spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(eqearth) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0 == 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_eqearth.cpp b/src/PJ_eqearth.cpp new file mode 100644 index 00000000..34d85fa5 --- /dev/null +++ b/src/PJ_eqearth.cpp @@ -0,0 +1,162 @@ +/* +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 + +struct pj_opaque { + double qp; + double rqda; + double *apa; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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 (0 == 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.c b/src/PJ_fahey.c deleted file mode 100644 index 85e0ab69..00000000 --- a/src/PJ_fahey.c +++ /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_fahey.cpp b/src/PJ_fahey.cpp new file mode 100644 index 00000000..85e0ab69 --- /dev/null +++ b/src/PJ_fahey.cpp @@ -0,0 +1,41 @@ +#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.c b/src/PJ_fouc_s.c deleted file mode 100644 index 6efe34bd..00000000 --- a/src/PJ_fouc_s.c +++ /dev/null @@ -1,70 +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 - -struct pj_opaque { - double n, n1; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_fouc_s.cpp b/src/PJ_fouc_s.cpp new file mode 100644 index 00000000..8c223336 --- /dev/null +++ b/src/PJ_fouc_s.cpp @@ -0,0 +1,70 @@ +#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 + +struct pj_opaque { + double n, n1; +}; + + +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 (0==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.c b/src/PJ_gall.c deleted file mode 100644 index a8697482..00000000 --- a/src/PJ_gall.c +++ /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_gall.cpp b/src/PJ_gall.cpp new file mode 100644 index 00000000..a8697482 --- /dev/null +++ b/src/PJ_gall.cpp @@ -0,0 +1,44 @@ +#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.c b/src/PJ_geoc.c deleted file mode 100644 index 0455fada..00000000 --- a/src/PJ_geoc.c +++ /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_geoc.cpp b/src/PJ_geoc.cpp new file mode 100644 index 00000000..0455fada --- /dev/null +++ b/src/PJ_geoc.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * 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.c b/src/PJ_geos.c deleted file mode 100644 index 0eb25610..00000000 --- a/src/PJ_geos.c +++ /dev/null @@ -1,236 +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" - -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; -}; - -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 = 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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 == NULL) - 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_geos.cpp b/src/PJ_geos.cpp new file mode 100644 index 00000000..ffe0771c --- /dev/null +++ b/src/PJ_geos.cpp @@ -0,0 +1,236 @@ +/* +** 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" + +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; +}; + +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 (0==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 == NULL) + 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.c b/src/PJ_gins8.c deleted file mode 100644 index c1a2fff0..00000000 --- a/src/PJ_gins8.c +++ /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 = 0; - P->fwd = s_forward; - - return P; -} - - diff --git a/src/PJ_gins8.cpp b/src/PJ_gins8.cpp new file mode 100644 index 00000000..c1a2fff0 --- /dev/null +++ b/src/PJ_gins8.cpp @@ -0,0 +1,33 @@ +#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 = 0; + P->fwd = s_forward; + + return P; +} + + diff --git a/src/PJ_gn_sinu.c b/src/PJ_gn_sinu.c deleted file mode 100644 index 2ef0f263..00000000 --- a/src/PJ_gn_sinu.c +++ /dev/null @@ -1,187 +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 - -struct pj_opaque { - double *en; - double m, n, C_x, C_y; -}; - - -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), 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, 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 = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - - -/* for spheres, only */ -static void setup(PJ *P) { - struct pj_opaque *Q = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_gn_sinu.cpp b/src/PJ_gn_sinu.cpp new file mode 100644 index 00000000..2c7824ac --- /dev/null +++ b/src/PJ_gn_sinu.cpp @@ -0,0 +1,187 @@ +#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 + +struct pj_opaque { + double *en; + double m, n, C_x, C_y; +}; + + +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 (0==P) + return 0; + + if (0==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 (0==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 (0==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 (0==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 (0==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.c b/src/PJ_gnom.c deleted file mode 100644 index 635ae49e..00000000 --- a/src/PJ_gnom.c +++ /dev/null @@ -1,143 +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 - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -struct pj_opaque { - double sinph0; - double cosph0; - enum Mode mode; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_gnom.cpp b/src/PJ_gnom.cpp new file mode 100644 index 00000000..7313643f --- /dev/null +++ b/src/PJ_gnom.cpp @@ -0,0 +1,143 @@ +#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 + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +struct pj_opaque { + double sinph0; + double cosph0; + enum Mode mode; +}; + + +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 (0==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.c b/src/PJ_goode.c deleted file mode 100644 index 8d8ac24d..00000000 --- a/src/PJ_goode.c +++ /dev/null @@ -1,82 +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 *); - -struct pj_opaque { - PJ *sinu; - PJ *moll; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - if (0==P->opaque) - return pj_default_destructor (P, errlev); - pj_free (P->opaque->sinu); - pj_free (P->opaque->moll); - return pj_default_destructor (P, errlev); -} - - - -PJ *PROJECTION(goode) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - P->destructor = destructor; - - P->es = 0.; - if (!(Q->sinu = pj_sinu(0)) || !(Q->moll = pj_moll(0))) - 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_goode.cpp b/src/PJ_goode.cpp new file mode 100644 index 00000000..3f5a4f8c --- /dev/null +++ b/src/PJ_goode.cpp @@ -0,0 +1,82 @@ +#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 *); + +struct pj_opaque { + PJ *sinu; + PJ *moll; +}; + + +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 (0==P) + return 0; + if (0==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 (0==Q) + return pj_default_destructor (P, ENOMEM); + P->opaque = Q; + P->destructor = destructor; + + P->es = 0.; + if (!(Q->sinu = pj_sinu(0)) || !(Q->moll = pj_moll(0))) + 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.c b/src/PJ_gstmerc.c deleted file mode 100644 index 5b5109b0..00000000 --- a/src/PJ_gstmerc.c +++ /dev/null @@ -1,72 +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="; - -struct pj_opaque { - double lamc; - double phic; - double c; - double n1; - double n2; - double XS; - double YS; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_gstmerc.cpp b/src/PJ_gstmerc.cpp new file mode 100644 index 00000000..6475f972 --- /dev/null +++ b/src/PJ_gstmerc.cpp @@ -0,0 +1,72 @@ +#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="; + +struct pj_opaque { + double lamc; + double phic; + double c; + double n1; + double n2; + double XS; + double YS; +}; + + +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 (0==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.c b/src/PJ_hammer.c deleted file mode 100644 index f3e0d64e..00000000 --- a/src/PJ_hammer.c +++ /dev/null @@ -1,75 +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 - -struct pj_opaque { - double w; - double m, rm; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_hammer.cpp b/src/PJ_hammer.cpp new file mode 100644 index 00000000..474d44ca --- /dev/null +++ b/src/PJ_hammer.cpp @@ -0,0 +1,75 @@ +#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 + +struct pj_opaque { + double w; + double m, rm; +}; + + +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 (0==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.c b/src/PJ_hatano.c deleted file mode 100644 index 019671cc..00000000 --- a/src/PJ_hatano.c +++ /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_hatano.cpp b/src/PJ_hatano.cpp new file mode 100644 index 00000000..019671cc --- /dev/null +++ b/src/PJ_hatano.cpp @@ -0,0 +1,83 @@ +#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.c b/src/PJ_healpix.c deleted file mode 100644 index 3a88695f..00000000 --- a/src/PJ_healpix.c +++ /dev/null @@ -1,672 +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 - -struct pj_opaque { - int north_square; - int south_square; - double qp; - double *apa; -}; - -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 = 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 = north; - c = M_HALFPI; - } else if (y < -M_FORTPI) { - capmap.region = south; - c = -M_HALFPI; - } else { - capmap.region = 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 = 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 = south; - capmap.x = -3*M_FORTPI + south_square*M_HALFPI; - capmap.y = -M_HALFPI; - x = x - south_square*M_HALFPI; - } else { - capmap.region = 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 == 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 == 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 == 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 == 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 == 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 == 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 = 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 = 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 = 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 = 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 void *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->apa); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(healpix) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0==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_healpix.cpp b/src/PJ_healpix.cpp new file mode 100644 index 00000000..a7d42398 --- /dev/null +++ b/src/PJ_healpix.cpp @@ -0,0 +1,672 @@ +/****************************************************************************** + * 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 + +struct pj_opaque { + int north_square; + int south_square; + double qp; + double *apa; +}; + +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 (0==P) + return 0; + + if (0==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 (0==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 (0==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 (0==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 (0==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.c b/src/PJ_helmert.c deleted file mode 100644 index 757cf950..00000000 --- a/src/PJ_helmert.c +++ /dev/null @@ -1,753 +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); - - - -/***********************************************************************/ -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 */ -}; - - -/* 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 = pj_calloc (1, sizeof (struct pj_opaque_helmert)); - if (0==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 0; - } - - /* 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 0; - } - - /* 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 0; - } - - 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 0; - } - - /* 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_helmert.cpp b/src/PJ_helmert.cpp new file mode 100644 index 00000000..c19422cb --- /dev/null +++ b/src/PJ_helmert.cpp @@ -0,0 +1,753 @@ +/*********************************************************************** + + 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); + + + +/***********************************************************************/ +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 */ +}; + + +/* 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 (0==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 0; + } + + /* 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 0; + } + + /* 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 0; + } + + 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 0; + } + + /* 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.c b/src/PJ_hgridshift.c deleted file mode 100644 index 2919a85c..00000000 --- a/src/PJ_hgridshift.c +++ /dev/null @@ -1,131 +0,0 @@ -#define PJ_LIB__ - -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(hgridshift, "Horizontal grid shift"); - -struct pj_opaque_hgridshift { - double t_final; - double t_epoch; -}; - -static XYZ forward_3d(LPZ lpz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - point.lpz = lpz; - - if (P->gridlist != NULL) { - /* 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 != NULL) { - /* 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 = pj_calloc (1, sizeof (struct pj_opaque_hgridshift)); - if (0==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 = 0; - P->inv = 0; - - 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_hgridshift.cpp b/src/PJ_hgridshift.cpp new file mode 100644 index 00000000..86c6cdee --- /dev/null +++ b/src/PJ_hgridshift.cpp @@ -0,0 +1,131 @@ +#define PJ_LIB__ + +#include +#include +#include +#include + +#include "proj_internal.h" +#include "projects.h" + +PROJ_HEAD(hgridshift, "Horizontal grid shift"); + +struct pj_opaque_hgridshift { + double t_final; + double t_epoch; +}; + +static XYZ forward_3d(LPZ lpz, PJ *P) { + PJ_COORD point = {{0,0,0,0}}; + point.lpz = lpz; + + if (P->gridlist != NULL) { + /* 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 != NULL) { + /* 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 (0==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 = 0; + P->inv = 0; + + 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.c b/src/PJ_horner.c deleted file mode 100644 index a6a26e52..00000000 --- a/src/PJ_horner.c +++ /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) - - -struct horner; -typedef struct horner HORNER; -static UV horner (const HORNER *transformation, PJ_DIRECTION direction, UV position); -static HORNER *horner_alloc (size_t order, int complex_polynomia); -static void horner_free (HORNER *h); - -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 */ -}; - -/* 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 = horner_calloc (1, sizeof (HORNER)); - - if (0==h) - return 0; - - if (complex_polynomia) - n = 2*(int)order + 2; - h->order = (int)order; - h->coefs = n; - - if (complex_polynomia) { - h->fwd_c = horner_calloc (n, sizeof(double)); - h->inv_c = horner_calloc (n, sizeof(double)); - if (h->fwd_c && h->inv_c) - polynomia_ok = 1; - } - else { - h->fwd_u = horner_calloc (n, sizeof(double)); - h->fwd_v = horner_calloc (n, sizeof(double)); - h->inv_u = horner_calloc (n, sizeof(double)); - h->inv_v = horner_calloc (n, sizeof(double)); - if (h->fwd_u && h->fwd_v && h->inv_u && h->inv_v) - polynomia_ok = 1; - } - - h->fwd_origin = horner_calloc (1, sizeof(UV)); - h->inv_origin = 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 0; -} - - - - -/**********************************************************************/ -static UV horner (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 (0==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 ((HORNER *) P->opaque, 1, point.uv); - return point; -} - -static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) { - point.uv = horner ((HORNER *) P->opaque, -1, 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 (0==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 void *horner_freeup (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - if (0==P->opaque) - return pj_default_destructor (P, errlev); - horner_free ((HORNER *) P->opaque); - P->opaque = 0; - return pj_default_destructor (P, errlev); -} - - -static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) { - char *buf, *init, *next = 0; - int i; - - buf = pj_calloc (strlen (param) + 2, sizeof(char)); - if (0==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 == 0 || ','!=*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 = 0; - P->inv3d = 0; - P->fwd = 0; - P->inv = 0; - 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 == 0) - return horner_freeup (P, ENOMEM); - P->opaque = (void *) 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_horner.cpp b/src/PJ_horner.cpp new file mode 100644 index 00000000..49e108c8 --- /dev/null +++ b/src/PJ_horner.cpp @@ -0,0 +1,513 @@ +/*********************************************************************** + + 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) + + +struct horner; +typedef struct horner HORNER; +static UV horner (const HORNER *transformation, PJ_DIRECTION direction, UV position); +static HORNER *horner_alloc (size_t order, int complex_polynomia); +static void horner_free (HORNER *h); + +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 */ +}; + +/* 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 (0==h) + return 0; + + 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 0; +} + + + + +/**********************************************************************/ +static UV horner (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 (0==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 ((HORNER *) P->opaque, PJ_FWD, point.uv); + return point; +} + +static PJ_COORD horner_reverse_4d (PJ_COORD point, PJ *P) { + point.uv = horner ((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 (0==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 (0==P) + return 0; + if (0==P->opaque) + return pj_default_destructor (P, errlev); + horner_free ((HORNER *) P->opaque); + P->opaque = 0; + return pj_default_destructor (P, errlev); +} + + +static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) { + char *buf, *init, *next = 0; + int i; + + buf = static_cast(pj_calloc (strlen (param) + 2, sizeof(char))); + if (0==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 == 0 || ','!=*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 = 0; + P->inv3d = 0; + P->fwd = 0; + P->inv = 0; + 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 == 0) + 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.c b/src/PJ_igh.c deleted file mode 100644 index c991649a..00000000 --- a/src/PJ_igh.c +++ /dev/null @@ -1,225 +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 */ - -struct pj_opaque { - struct PJconsts* pj[12]; \ - double dy0; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy; - struct pj_opaque *Q = 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 = 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 void *destructor (PJ *P, int errlev) { - int i; - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - for (i = 0; i < 12; ++i) { - if (P->opaque->pj[i]) - P->opaque->pj[i]->destructor(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(0))) 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_igh.cpp b/src/PJ_igh.cpp new file mode 100644 index 00000000..476d1c6b --- /dev/null +++ b/src/PJ_igh.cpp @@ -0,0 +1,225 @@ +#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 */ + +struct pj_opaque { + struct PJconsts* pj[12]; \ + double dy0; +}; + + +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 (0==P) + return 0; + + if (0==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(0))) 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 (0==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.c b/src/PJ_imw_p.c deleted file mode 100644 index abed5006..00000000 --- a/src/PJ_imw_p.c +++ /dev/null @@ -1,213 +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 - -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 */ -}; - -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; -}; - - -static int phi12(PJ *P, double *del, double *sig) { - struct pj_opaque *Q = 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 = 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 = 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 = P->opaque->lam_1 * *sp; - *y = *R * (1 - cos(F)); - *x = *R * sin(F); -} - - -static void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - if( P->opaque->en ) - pj_dealloc (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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_imw_p.cpp b/src/PJ_imw_p.cpp new file mode 100644 index 00000000..7bf9405a --- /dev/null +++ b/src/PJ_imw_p.cpp @@ -0,0 +1,213 @@ +#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 + +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 */ +}; + +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; +}; + + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_isea.c deleted file mode 100644 index 4ffd2983..00000000 --- a/src/PJ_isea.c +++ /dev/null @@ -1,1082 +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 - -struct hex { - int iso; - long x, y, z; -}; - -/* 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; -} - -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 -}; - -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; -}; - -struct isea_pt { - double x, y; -}; - -struct isea_geo { - double lon, lat; -}; - -/* ENDINC */ - -enum snyder_polyhedron { - SNYDER_POLY_HEXAGON, SNYDER_POLY_PENTAGON, - SNYDER_POLY_TETRAHEDRON, SNYDER_POLY_CUBE, - SNYDER_POLY_OCTAHEDRON, SNYDER_POLY_DODECAHEDRON, - SNYDER_POLY_ICOSAHEDRON -}; - -struct snyder_constants { - double g, G, theta; - /* cppcheck-suppress unusedStructMember */ - double ea_w, ea_a, ea_b, g_w, g_a, g_b; -}; - -/* 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"; - -struct pj_opaque { - struct isea_dgg dgg; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_isea.cpp b/src/PJ_isea.cpp new file mode 100644 index 00000000..6170b8a1 --- /dev/null +++ b/src/PJ_isea.cpp @@ -0,0 +1,1082 @@ +/* + * 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 + +struct hex { + int iso; + long x, y, z; +}; + +/* 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; +} + +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 +}; + +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; +}; + +struct isea_pt { + double x, y; +}; + +struct isea_geo { + double lon, lat; +}; + +/* ENDINC */ + +enum snyder_polyhedron { + SNYDER_POLY_HEXAGON, SNYDER_POLY_PENTAGON, + SNYDER_POLY_TETRAHEDRON, SNYDER_POLY_CUBE, + SNYDER_POLY_OCTAHEDRON, SNYDER_POLY_DODECAHEDRON, + SNYDER_POLY_ICOSAHEDRON +}; + +struct snyder_constants { + double g, G, theta; + /* cppcheck-suppress unusedStructMember */ + double ea_w, ea_a, ea_b, g_w, g_a, g_b; +}; + +/* 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"; + +struct pj_opaque { + struct isea_dgg dgg; +}; + + +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 (0==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.c b/src/PJ_krovak.c deleted file mode 100644 index 5ca21214..00000000 --- a/src/PJ_krovak.c +++ /dev/null @@ -1,220 +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 - -struct pj_opaque { - double alpha; - double k; - double n; - double rho0; - double ad; - int czech; -}; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_krovak.cpp b/src/PJ_krovak.cpp new file mode 100644 index 00000000..7e0488f0 --- /dev/null +++ b/src/PJ_krovak.cpp @@ -0,0 +1,220 @@ + /* + * 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 + +struct pj_opaque { + double alpha; + double k; + double n; + double rho0; + double ad; + int czech; +}; + + +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 (0==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.c b/src/PJ_labrd.c deleted file mode 100644 index 769dc620..00000000 --- a/src/PJ_labrd.c +++ /dev/null @@ -1,130 +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 - -struct pj_opaque { - double kRg, p0s, A, C, Ca, Cb, Cc, Cd; -}; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_labrd.cpp b/src/PJ_labrd.cpp new file mode 100644 index 00000000..e40bbcbd --- /dev/null +++ b/src/PJ_labrd.cpp @@ -0,0 +1,130 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +PROJ_HEAD(labrd, "Laborde") "\n\tCyl, Sph\n\tSpecial for Madagascar"; +#define EPS 1.e-10 + +struct pj_opaque { + double kRg, p0s, A, C, Ca, Cb, Cc, Cd; +}; + + +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 (0==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.c b/src/PJ_laea.c deleted file mode 100644 index bcf9c44d..00000000 --- a/src/PJ_laea.c +++ /dev/null @@ -1,296 +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"; - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -struct pj_opaque { - double sinb1; - double cosb1; - double xmf; - double ymf; - double mmf; - double qp; - double dd; - double rq; - double *apa; - enum Mode mode; -}; - -#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 = 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 = 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 = 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 = 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 void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->apa); - - return pj_default_destructor(P, errlev); -} - - -PJ *PROJECTION(laea) { - double t; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0==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_laea.cpp b/src/PJ_laea.cpp new file mode 100644 index 00000000..02b34858 --- /dev/null +++ b/src/PJ_laea.cpp @@ -0,0 +1,296 @@ +#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"; + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +struct pj_opaque { + double sinb1; + double cosb1; + double xmf; + double ymf; + double mmf; + double qp; + double dd; + double rq; + double *apa; + enum Mode mode; +}; + +#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 (0==P) + return 0; + + if (0==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 (0==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 (0==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.c b/src/PJ_lagrng.c deleted file mode 100644 index 3856fcdc..00000000 --- a/src/PJ_lagrng.c +++ /dev/null @@ -1,96 +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 - -struct pj_opaque { - double a1; - double a2; - double hrw; - double hw; - double rw; - double w; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_lagrng.cpp b/src/PJ_lagrng.cpp new file mode 100644 index 00000000..f5363287 --- /dev/null +++ b/src/PJ_lagrng.cpp @@ -0,0 +1,96 @@ +#define PJ_LIB__ +#include +#include + +#include "proj.h" +#include "projects.h" + +PROJ_HEAD(lagrng, "Lagrange") "\n\tMisc Sph\n\tW="; + +#define TOL 1e-10 + +struct pj_opaque { + double a1; + double a2; + double hrw; + double hw; + double rw; + double w; +}; + + +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 (0==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.c b/src/PJ_larr.c deleted file mode 100644 index e4d5d240..00000000 --- a/src/PJ_larr.c +++ /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_larr.cpp b/src/PJ_larr.cpp new file mode 100644 index 00000000..e4d5d240 --- /dev/null +++ b/src/PJ_larr.cpp @@ -0,0 +1,28 @@ +#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.c b/src/PJ_lask.c deleted file mode 100644 index 46f23edb..00000000 --- a/src/PJ_lask.c +++ /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_lask.cpp b/src/PJ_lask.cpp new file mode 100644 index 00000000..46f23edb --- /dev/null +++ b/src/PJ_lask.cpp @@ -0,0 +1,39 @@ +#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.c b/src/PJ_latlong.c deleted file mode 100644 index 1331d59a..00000000 --- a/src/PJ_latlong.c +++ /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_latlong.cpp b/src/PJ_latlong.cpp new file mode 100644 index 00000000..1331d59a --- /dev/null +++ b/src/PJ_latlong.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * 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.c b/src/PJ_lcc.c deleted file mode 100644 index 96aa3f2a..00000000 --- a/src/PJ_lcc.c +++ /dev/null @@ -1,128 +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 - -struct pj_opaque { - double phi1; - double phi2; - double n; - double rho0; - double c; -}; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0., 0.}; - struct pj_opaque *Q = 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 = 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 = pj_calloc(1, sizeof (struct pj_opaque)); - - if (0 == 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_lcc.cpp b/src/PJ_lcc.cpp new file mode 100644 index 00000000..5c430ea0 --- /dev/null +++ b/src/PJ_lcc.cpp @@ -0,0 +1,128 @@ +#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 + +struct pj_opaque { + double phi1; + double phi2; + double n; + double rho0; + double c; +}; + + +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 (0 == 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.c b/src/PJ_lcca.c deleted file mode 100644 index f0f256b1..00000000 --- a/src/PJ_lcca.c +++ /dev/null @@ -1,162 +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 - -struct pj_opaque { - double *en; - double r0, l, M0; - double C; -}; - - -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 = 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 = 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 void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(lcca) { - double s2p0, N0, R0, tan0; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_lcca.cpp b/src/PJ_lcca.cpp new file mode 100644 index 00000000..cbb18709 --- /dev/null +++ b/src/PJ_lcca.cpp @@ -0,0 +1,162 @@ +/***************************************************************************** + + 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 + +struct pj_opaque { + double *en; + double r0, l, M0; + double C; +}; + + +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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_loxim.c deleted file mode 100644 index 512bfc23..00000000 --- a/src/PJ_loxim.c +++ /dev/null @@ -1,75 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(loxim, "Loximuthal") "\n\tPCyl Sph"; - -#define EPS 1e-8 - -struct pj_opaque { - double phi1; - double cosphi1; - double tanphi1; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_loxim.cpp b/src/PJ_loxim.cpp new file mode 100644 index 00000000..28e955d9 --- /dev/null +++ b/src/PJ_loxim.cpp @@ -0,0 +1,75 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +PROJ_HEAD(loxim, "Loximuthal") "\n\tPCyl Sph"; + +#define EPS 1e-8 + +struct pj_opaque { + double phi1; + double cosphi1; + double tanphi1; +}; + + +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 (0==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.c b/src/PJ_lsat.c deleted file mode 100644 index 810a1cba..00000000 --- a/src/PJ_lsat.c +++ /dev/null @@ -1,210 +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 - -struct pj_opaque { - double a2, a4, b, c1, c3; - double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; -}; - -static void seraz0(double lam, double mult, PJ *P) { - struct pj_opaque *Q = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_lsat.cpp b/src/PJ_lsat.cpp new file mode 100644 index 00000000..e3e7e026 --- /dev/null +++ b/src/PJ_lsat.cpp @@ -0,0 +1,210 @@ +/* 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 + +struct pj_opaque { + double a2, a4, b, c1, c3; + double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; +}; + +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 (0==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.c b/src/PJ_mbt_fps.c deleted file mode 100644 index 66ed9458..00000000 --- a/src/PJ_mbt_fps.c +++ /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_mbt_fps.cpp b/src/PJ_mbt_fps.cpp new file mode 100644 index 00000000..66ed9458 --- /dev/null +++ b/src/PJ_mbt_fps.cpp @@ -0,0 +1,57 @@ +#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.c b/src/PJ_mbtfpp.c deleted file mode 100644 index 276a43eb..00000000 --- a/src/PJ_mbtfpp.c +++ /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_mbtfpp.cpp b/src/PJ_mbtfpp.cpp new file mode 100644 index 00000000..276a43eb --- /dev/null +++ b/src/PJ_mbtfpp.cpp @@ -0,0 +1,65 @@ +#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.c b/src/PJ_mbtfpq.c deleted file mode 100644 index b7c0eb16..00000000 --- a/src/PJ_mbtfpq.c +++ /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_mbtfpq.cpp b/src/PJ_mbtfpq.cpp new file mode 100644 index 00000000..b7c0eb16 --- /dev/null +++ b/src/PJ_mbtfpq.cpp @@ -0,0 +1,74 @@ +#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.c b/src/PJ_merc.c deleted file mode 100644 index 1998234e..00000000 --- a/src/PJ_merc.c +++ /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_merc.cpp b/src/PJ_merc.cpp new file mode 100644 index 00000000..1998234e --- /dev/null +++ b/src/PJ_merc.cpp @@ -0,0 +1,101 @@ +#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.c b/src/PJ_mill.c deleted file mode 100644 index 3ea9636f..00000000 --- a/src/PJ_mill.c +++ /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_mill.cpp b/src/PJ_mill.cpp new file mode 100644 index 00000000..3ea9636f --- /dev/null +++ b/src/PJ_mill.cpp @@ -0,0 +1,37 @@ +#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.c b/src/PJ_misrsom.c deleted file mode 100644 index 0308fc42..00000000 --- a/src/PJ_misrsom.c +++ /dev/null @@ -1,217 +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 - -struct pj_opaque { - double a2, a4, b, c1, c3; - double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; -}; - -static void seraz0(double lam, double mult, PJ *P) { - struct pj_opaque *Q = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_misrsom.cpp b/src/PJ_misrsom.cpp new file mode 100644 index 00000000..537172c1 --- /dev/null +++ b/src/PJ_misrsom.cpp @@ -0,0 +1,217 @@ +/****************************************************************************** + * 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 + +struct pj_opaque { + double a2, a4, b, c1, c3; + double q, t, u, w, p22, sa, ca, xj, rlm, rlm2; +}; + +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 (0==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.c b/src/PJ_mod_ster.c deleted file mode 100644 index 5e6ce136..00000000 --- a/src/PJ_mod_ster.c +++ /dev/null @@ -1,280 +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 - -struct pj_opaque { - const COMPLEX *zcoeff; \ - double cchio, schio; \ - int n; -}; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_mod_ster.cpp b/src/PJ_mod_ster.cpp new file mode 100644 index 00000000..ad5c9cdb --- /dev/null +++ b/src/PJ_mod_ster.cpp @@ -0,0 +1,280 @@ +/* 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 + +struct pj_opaque { + const COMPLEX *zcoeff; \ + double cchio, schio; \ + int n; +}; + + +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 (0==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 (0==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 (0==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 (0==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 (0==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.c b/src/PJ_moll.c deleted file mode 100644 index 4ac73841..00000000 --- a/src/PJ_moll.c +++ /dev/null @@ -1,110 +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 - -struct pj_opaque { - double C_x, C_y, C_p; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - return setup(P, M_HALFPI); -} - - -PJ *PROJECTION(wag4) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - return setup(P, M_PI/3.); -} - -PJ *PROJECTION(wag5) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_moll.cpp b/src/PJ_moll.cpp new file mode 100644 index 00000000..ed7e946d --- /dev/null +++ b/src/PJ_moll.cpp @@ -0,0 +1,110 @@ +#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 + +struct pj_opaque { + double C_x, C_y, C_p; +}; + + +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 (0==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 (0==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 (0==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.c b/src/PJ_molodensky.c deleted file mode 100644 index dbc83768..00000000 --- a/src/PJ_molodensky.c +++ /dev/null @@ -1,327 +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); - -struct pj_opaque_molodensky { - double dx; - double dy; - double dz; - double da; - double df; - int abridged; -}; - - -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 = pj_calloc(1, sizeof(struct pj_opaque_molodensky)); - if (0==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_molodensky.cpp b/src/PJ_molodensky.cpp new file mode 100644 index 00000000..6b231081 --- /dev/null +++ b/src/PJ_molodensky.cpp @@ -0,0 +1,327 @@ +/*********************************************************************** + + (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); + +struct pj_opaque_molodensky { + double dx; + double dy; + double dz; + double da; + double df; + int abridged; +}; + + +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 (0==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.c b/src/PJ_natearth.c deleted file mode 100644 index 27a6b137..00000000 --- a/src/PJ_natearth.c +++ /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_natearth.cpp b/src/PJ_natearth.cpp new file mode 100644 index 00000000..27a6b137 --- /dev/null +++ b/src/PJ_natearth.cpp @@ -0,0 +1,100 @@ +/* +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.c b/src/PJ_natearth2.c deleted file mode 100644 index f6aba671..00000000 --- a/src/PJ_natearth2.c +++ /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_natearth2.cpp b/src/PJ_natearth2.cpp new file mode 100644 index 00000000..f6aba671 --- /dev/null +++ b/src/PJ_natearth2.cpp @@ -0,0 +1,97 @@ +/* +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.c b/src/PJ_nell.c deleted file mode 100644 index 2a7ea32c..00000000 --- a/src/PJ_nell.c +++ /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.cpp b/src/PJ_nell.cpp new file mode 100644 index 00000000..2a7ea32c --- /dev/null +++ b/src/PJ_nell.cpp @@ -0,0 +1,51 @@ +#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.c b/src/PJ_nell_h.c deleted file mode 100644 index 28c3ace7..00000000 --- a/src/PJ_nell_h.c +++ /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_nell_h.cpp b/src/PJ_nell_h.cpp new file mode 100644 index 00000000..28c3ace7 --- /dev/null +++ b/src/PJ_nell_h.cpp @@ -0,0 +1,53 @@ +#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.c b/src/PJ_nocol.c deleted file mode 100644 index 541d08b2..00000000 --- a/src/PJ_nocol.c +++ /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_nocol.cpp b/src/PJ_nocol.cpp new file mode 100644 index 00000000..541d08b2 --- /dev/null +++ b/src/PJ_nocol.cpp @@ -0,0 +1,54 @@ +#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.c b/src/PJ_nsper.c deleted file mode 100644 index 223cd75b..00000000 --- a/src/PJ_nsper.c +++ /dev/null @@ -1,198 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -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; -}; - -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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_nsper.cpp b/src/PJ_nsper.cpp new file mode 100644 index 00000000..e6ecb852 --- /dev/null +++ b/src/PJ_nsper.cpp @@ -0,0 +1,198 @@ +#define PJ_LIB__ +#include +#include "proj.h" +#include "projects.h" +#include "proj_math.h" + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +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; +}; + +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 (0==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 (0==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.c b/src/PJ_nzmg.c deleted file mode 100644 index bf0862fb..00000000 --- a/src/PJ_nzmg.c +++ /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_nzmg.cpp b/src/PJ_nzmg.cpp new file mode 100644 index 00000000..bf0862fb --- /dev/null +++ b/src/PJ_nzmg.cpp @@ -0,0 +1,123 @@ +/****************************************************************************** + * 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.c b/src/PJ_ob_tran.c deleted file mode 100644 index f5a05cf9..00000000 --- a/src/PJ_ob_tran.c +++ /dev/null @@ -1,243 +0,0 @@ -#define PJ_LIB__ -#include -#include -#include -#include - -#include "proj.h" -#include "projects.h" - -struct pj_opaque { - struct PJconsts *link; - double lamp; - double cphip, sphip; -}; - -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 = 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 = 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 = 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 = 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 void *destructor(PJ *P, int errlev) { - if (0==P) - return 0; - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - if (P->opaque->link) - P->opaque->link->destructor (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 != 0; 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, 0}; - size_t argc = paralist_params_argc (params); - if (argc < 2) - return args; - - /* all args except the proj_ob_tran */ - args.argv = pj_calloc (argc - 1, sizeof (char *)); - if (0==args.argv) - return args; - - /* Copy all args *except* the proj=ob_tran arg to the argv array */ - for (i = 0; params != 0; 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 (0==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 : 0; - P->inv = Q->link->inv ? o_inverse : 0; - } else { /* transverse */ - P->fwd = Q->link->fwd ? t_forward : 0; - P->inv = Q->link->inv ? t_inverse : 0; - } - - /* 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_ob_tran.cpp b/src/PJ_ob_tran.cpp new file mode 100644 index 00000000..1a9417b8 --- /dev/null +++ b/src/PJ_ob_tran.cpp @@ -0,0 +1,243 @@ +#define PJ_LIB__ +#include +#include +#include +#include + +#include "proj.h" +#include "projects.h" + +struct pj_opaque { + struct PJconsts *link; + double lamp; + double cphip, sphip; +}; + +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 (0==P) + return 0; + if (0==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 != 0; 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, 0}; + 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 (0==args.argv) + return args; + + /* Copy all args *except* the proj=ob_tran arg to the argv array */ + for (i = 0; params != 0; 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 (0==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 (0==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 : 0; + P->inv = Q->link->inv ? o_inverse : 0; + } else { /* transverse */ + P->fwd = Q->link->fwd ? t_forward : 0; + P->inv = Q->link->inv ? t_inverse : 0; + } + + /* 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.c b/src/PJ_ocea.c deleted file mode 100644 index 7a9353a6..00000000 --- a/src/PJ_ocea.c +++ /dev/null @@ -1,100 +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="; - -struct pj_opaque { - double rok; - double rtk; - double sinphi; - double cosphi; - double singam; - double cosgam; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_ocea.cpp b/src/PJ_ocea.cpp new file mode 100644 index 00000000..81c506fe --- /dev/null +++ b/src/PJ_ocea.cpp @@ -0,0 +1,100 @@ +#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="; + +struct pj_opaque { + double rok; + double rtk; + double sinphi; + double cosphi; + double singam; + double cosgam; +}; + + +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 (0==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.c b/src/PJ_oea.c deleted file mode 100644 index 2bfeffa8..00000000 --- a/src/PJ_oea.c +++ /dev/null @@ -1,85 +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="; - -struct pj_opaque { - double theta; - double m, n; - double two_r_m, two_r_n, rm, rn, hm, hn; - double cp0, sp0; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_oea.cpp b/src/PJ_oea.cpp new file mode 100644 index 00000000..b39e8d5a --- /dev/null +++ b/src/PJ_oea.cpp @@ -0,0 +1,85 @@ +#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="; + +struct pj_opaque { + double theta; + double m, n; + double two_r_m, two_r_n, rm, rn, hm, hn; + double cp0, sp0; +}; + + +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 (0==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.c b/src/PJ_omerc.c deleted file mode 100644 index 70c12e27..00000000 --- a/src/PJ_omerc.c +++ /dev/null @@ -1,227 +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="; - -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; -}; - -#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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_omerc.cpp b/src/PJ_omerc.cpp new file mode 100644 index 00000000..27b65cc7 --- /dev/null +++ b/src/PJ_omerc.cpp @@ -0,0 +1,227 @@ +/* +** 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="; + +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; +}; + +#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 (0==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.c b/src/PJ_ortho.c deleted file mode 100644 index d442aa8a..00000000 --- a/src/PJ_ortho.c +++ /dev/null @@ -1,139 +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"; - -enum Mode { - N_POLE = 0, - S_POLE = 1, - EQUIT = 2, - OBLIQ = 3 -}; - -struct pj_opaque { - double sinph0; - double cosph0; - enum Mode mode; -}; - -#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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_ortho.cpp b/src/PJ_ortho.cpp new file mode 100644 index 00000000..0e3641c0 --- /dev/null +++ b/src/PJ_ortho.cpp @@ -0,0 +1,139 @@ +#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"; + +enum Mode { + N_POLE = 0, + S_POLE = 1, + EQUIT = 2, + OBLIQ = 3 +}; + +struct pj_opaque { + double sinph0; + double cosph0; + enum Mode mode; +}; + +#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 (0==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.c b/src/PJ_patterson.c deleted file mode 100644 index 0d19414e..00000000 --- a/src/PJ_patterson.c +++ /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_patterson.cpp b/src/PJ_patterson.cpp new file mode 100644 index 00000000..0d19414e --- /dev/null +++ b/src/PJ_patterson.cpp @@ -0,0 +1,117 @@ +/* + * 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_pipeline.c b/src/PJ_pipeline.c deleted file mode 100644 index 618d4688..00000000 --- a/src/PJ_pipeline.c +++ /dev/null @@ -1,501 +0,0 @@ -/******************************************************************************* - - Transformation pipeline manager - - Thomas Knudsen, 2016-05-20/2016-11-20 - -******************************************************************************** - - Geodetic transformations are typically organized in a number of - steps. For example, a datum shift could be carried out through - these steps: - - 1. Convert (latitude, longitude, ellipsoidal height) to - 3D geocentric cartesian coordinates (X, Y, Z) - 2. Transform the (X, Y, Z) coordinates to the new datum, using a - 7 parameter Helmert transformation. - 3. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) - - If the height system used is orthometric, rather than ellipsoidal, - another step is needed at each end of the process: - - 1. Add the local geoid undulation (N) to the orthometric height - to obtain the ellipsoidal (i.e. geometric) height. - 2. Convert (latitude, longitude, ellipsoidal height) to - 3D geocentric cartesian coordinates (X, Y, Z) - 3. Transform the (X, Y, Z) coordinates to the new datum, using a - 7 parameter Helmert transformation. - 4. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) - 5. Subtract the local geoid undulation (N) from the ellipsoidal height - to obtain the orthometric height. - - Additional steps can be added for e.g. change of vertical datum, so the - list can grow fairly long. None of the steps are, however, particularly - complex, and data flow is strictly from top to bottom. - - Hence, in principle, the first example above could be implemented using - Unix pipelines: - - cat my_coordinates | geographic_to_xyz | helmert | xyz_to_geographic > my_transformed_coordinates - - in the grand tradition of Software Tools [1]. - - The proj pipeline driver implements a similar concept: Stringing together - a number of steps, feeding the output of one step to the input of the next. - - It is a very powerful concept, that increases the range of relevance of the - proj.4 system substantially. It is, however, not a particularly intrusive - addition to the PROJ.4 code base: The implementation is by and large completed - by adding an extra projection called "pipeline" (i.e. this file), which - handles all business, and a small amount of added functionality in the - pj_init code, implementing support for multilevel, embedded pipelines. - - Syntactically, the pipeline system introduces the "+step" keyword (which - indicates the start of each transformation step), and reintroduces the +inv - keyword (indicating that a given transformation step should run in reverse, i.e. - forward, when the pipeline is executed in inverse direction, and vice versa). - - Hence, the first transformation example above, can be implemented as: - - +proj=pipeline +step proj=cart +step proj=helmert +step proj=cart +inv - - Where indicate the Helmert arguments: 3 translations (+x=..., +y=..., - +z=...), 3 rotations (+rx=..., +ry=..., +rz=...) and a scale factor (+s=...). - Following geodetic conventions, the rotations are given in arcseconds, - and the scale factor is given as parts-per-million. - - [1] B. W. Kernighan & P. J. Plauger: Software tools. - Reading, Massachusetts, Addison-Wesley, 1976, 338 pp. - -******************************************************************************** - -Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 - -******************************************************************************** -* Copyright (c) 2016, 2017, 2018 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 -#include - -#include "geodesic.h" -#include "proj.h" -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(pipeline, "Transformation pipeline manager"); - -/* Projection specific elements for the PJ object */ -struct pj_opaque { - int steps; - char **argv; - char **current_argv; - PJ **pipeline; -}; - - - -static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P); -static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P); -static XYZ pipeline_forward_3d (LPZ lpz, PJ *P); -static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P); -static XY pipeline_forward (LP lp, PJ *P); -static LP pipeline_reverse (XY xy, PJ *P); - - - - -static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P) { - int i, first_step, last_step; - - first_step = 1; - last_step = P->opaque->steps + 1; - - for (i = first_step; i != last_step; i++) - point = proj_trans (P->opaque->pipeline[i], 1, point); - - return point; -} - - -static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P) { - int i, first_step, last_step; - - first_step = P->opaque->steps; - last_step = 0; - - for (i = first_step; i != last_step; i--) - point = proj_trans (P->opaque->pipeline[i], -1, point); - - return point; -} - - - - -static XYZ pipeline_forward_3d (LPZ lpz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - int i; - point.lpz = lpz; - - for (i = 1; i <= P->opaque->steps; i++) - point = pj_approx_3D_trans (P->opaque->pipeline[i], 1, point); - - return point.xyz; -} - - -static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - int i; - point.xyz = xyz; - - for (i = P->opaque->steps; i > 0 ; i--) - point = pj_approx_3D_trans (P->opaque->pipeline[i], -1, point); - - return point.lpz; -} - - - - -static XY pipeline_forward (LP lp, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - int i; - point.lp = lp; - - for (i = 1; i <= P->opaque->steps; i++) - point = pj_approx_2D_trans (P->opaque->pipeline[i], 1, point); - - return point.xy; -} - - -static LP pipeline_reverse (XY xy, PJ *P) { - PJ_COORD point = {{0,0,0,0}}; - int i; - point.xy = xy; - for (i = P->opaque->steps; i > 0 ; i--) - point = pj_approx_2D_trans (P->opaque->pipeline[i], -1, point); - - return point.lp; -} - - - - -static void *destructor (PJ *P, int errlev) { - int i; - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - /* Deallocate each pipeine step, then pipeline array */ - if (0!=P->opaque->pipeline) - for (i = 0; i < P->opaque->steps; i++) - proj_destroy (P->opaque->pipeline[i+1]); - pj_dealloc (P->opaque->pipeline); - - pj_dealloc (P->opaque->argv); - pj_dealloc (P->opaque->current_argv); - - return pj_default_destructor(P, errlev); -} - - -static PJ *pj_create_pipeline (PJ *P, size_t steps) { - - /* Room for the pipeline: An array of PJ * with room for sentinels at both ends */ - P->opaque->pipeline = pj_calloc (steps + 2, sizeof(PJ *)); - if (0==P->opaque->pipeline) - return 0; - - P->opaque->steps = (int)steps; - - return P; -} - - - - -/* count the number of args in pipeline definition, and mark all args as used */ -static size_t argc_params (paralist *params) { - size_t argc = 0; - for (; params != 0; params = params->next) { - argc++; - params->used = 1; - } - return ++argc; /* one extra for the sentinel */ -} - -/* Sentinel for argument list */ -static char *argv_sentinel = "step"; - -/* turn paralist into argc/argv style argument list */ -static char **argv_params (paralist *params, size_t argc) { - char **argv; - size_t i = 0; - argv = pj_calloc (argc, sizeof (char *)); - if (0==argv) - return 0; - for (; params != 0; params = params->next) - argv[i++] = params->param; - argv[i++] = argv_sentinel; - return argv; -} - - - - -/* Being the special operator that the pipeline is, we have to handle the */ -/* ellipsoid differently than usual. In general, the pipeline operation does */ -/* not need an ellipsoid, but in some cases it is beneficial nonetheless. */ -/* Unfortunately we can't use the normal ellipsoid setter in pj_init, since */ -/* it adds a +ellps parameter to the global args if nothing else is specified*/ -/* This is problematic since that ellipsoid spec is then passed on to the */ -/* pipeline children. This is rarely what we want, so here we implement our */ -/* own logic instead. If an ellipsoid is set in the global args, it is used */ -/* as the pipeline ellipsoid. Otherwise we use WGS84 parameters as default. */ -/* At last we calculate the rest of the ellipsoid parameters and */ -/* re-initialize P->geod. */ -static void set_ellipsoid(PJ *P) { - paralist *cur, *attachment; - int err = proj_errno_reset (P); - - /* Break the linked list after the global args */ - attachment = 0; - for (cur = P->params; cur != 0; cur = cur->next) - /* cur->next will always be non 0 given argv_sentinel presence, */ - /* but this is far from being obvious for a static analyzer */ - if (cur->next != 0 && strcmp(argv_sentinel, cur->next->param) == 0) { - attachment = cur->next; - cur->next = 0; - break; - } - - /* Check if there's any ellipsoid specification in the global params. */ - /* If not, use WGS84 as default */ - if (0 != pj_ellipsoid (P)) { - P->a = 6378137.0; - P->es = .00669438002290341575; - - /* reset an "unerror": In this special use case, the errno is */ - /* not an error signal, but just a reply from pj_ellipsoid, */ - /* telling us that "No - there was no ellipsoid definition in */ - /* the PJ you provided". */ - proj_errno_reset (P); - } - P->a_orig = P->a; - P->es_orig = P->es; - - pj_calc_ellipsoid_params (P, P->a, P->es); - - geod_init(P->geod, P->a, (1 - sqrt (1 - P->es))); - - /* Re-attach the dangling list */ - /* Note: cur will always be non 0 given argv_sentinel presence, */ - /* but this is far from being obvious for a static analyzer */ - if( cur != 0 ) - cur->next = attachment; - proj_errno_restore (P, err); -} - - - - -PJ *OPERATION(pipeline,0) { - int i, nsteps = 0, argc; - int i_pipeline = -1, i_first_step = -1, i_current_step; - char **argv, **current_argv; - - P->fwd4d = pipeline_forward_4d; - P->inv4d = pipeline_reverse_4d; - P->fwd3d = pipeline_forward_3d; - P->inv3d = pipeline_reverse_3d; - P->fwd = pipeline_forward; - P->inv = pipeline_reverse; - P->destructor = destructor; - P->is_pipeline = 1; - - /* Currently, the pipeline driver is a raw bit mover, enabling other operations */ - /* to collaborate efficiently. All prep/fin stuff is done at the step levels. */ - P->skip_fwd_prepare = 1; - P->skip_fwd_finalize = 1; - P->skip_inv_prepare = 1; - P->skip_inv_finalize = 1; - - - P->opaque = pj_calloc (1, sizeof(struct pj_opaque)); - if (0==P->opaque) - return destructor(P, ENOMEM); - - argc = (int)argc_params (P->params); - P->opaque->argv = argv = argv_params (P->params, argc); - if (0==argv) - return destructor (P, ENOMEM); - - P->opaque->current_argv = current_argv = pj_calloc (argc, sizeof (char *)); - if (0==current_argv) - return destructor (P, ENOMEM); - - /* Do some syntactical sanity checking */ - for (i = 0; i < argc; i++) { - if (0==strcmp (argv_sentinel, argv[i])) { - if (-1==i_pipeline) { - proj_log_error (P, "Pipeline: +step before +proj=pipeline"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); - } - if (0==nsteps) - i_first_step = i; - nsteps++; - continue; - } - - if (0==strcmp ("proj=pipeline", argv[i])) { - if (-1 != i_pipeline) { - proj_log_error (P, "Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */ - } - i_pipeline = i; - } - } - nsteps--; /* Last instance of +step is just a sentinel */ - P->opaque->steps = nsteps; - - if (-1==i_pipeline) - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ - - if (0==nsteps) - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ - - /* Make room for the pipeline and execution indicators */ - if (0==pj_create_pipeline (P, nsteps)) - return destructor (P, ENOMEM); - - set_ellipsoid(P); - - /* Now loop over all steps, building a new set of arguments for each init */ - i_current_step = i_first_step; - for (i = 0; i < nsteps; i++) { - int j; - int current_argc = 0; - int err; - PJ *next_step = 0; - - /* Build a set of setup args for the current step */ - proj_log_trace (P, "Pipeline: Building arg list for step no. %d", i); - - /* First add the step specific args */ - for (j = i_current_step + 1; 0 != strcmp ("step", argv[j]); j++) - current_argv[current_argc++] = argv[j]; - - i_current_step = j; - - /* Then add the global args */ - for (j = i_pipeline + 1; 0 != strcmp ("step", argv[j]); j++) - current_argv[current_argc++] = argv[j]; - - proj_log_trace (P, "Pipeline: init - %s, %d", current_argv[0], current_argc); - for (j = 1; j < current_argc; j++) - proj_log_trace (P, " %s", current_argv[j]); - - err = proj_errno_reset (P); - - next_step = proj_create_argv (P->ctx, current_argc, current_argv); - proj_log_trace (P, "Pipeline: Step %d (%s) at %p", i, current_argv[0], next_step); - - if (0==next_step) { - /* The step init failed, but possibly without setting errno. If so, we say "malformed" */ - int err_to_report = proj_errno(P); - if (0==err_to_report) - err_to_report = PJD_ERR_MALFORMED_PIPELINE; - proj_log_error (P, "Pipeline: Bad step definition: %s (%s)", current_argv[0], pj_strerrno (err_to_report)); - return destructor (P, err_to_report); /* ERROR: bad pipeline def */ - } - - proj_errno_restore (P, err); - - /* Is this step inverted? */ - for (j = 0; j < current_argc; j++) - if (0==strcmp("inv", current_argv[j])) { - /* if +inv exists in both global and local args the forward operation should be used */ - next_step->inverted = next_step->inverted == 0 ? 1 : 0; - } - - P->opaque->pipeline[i+1] = next_step; - - proj_log_trace (P, "Pipeline at [%p]: step at [%p] (%s) done", P, next_step, current_argv[0]); - } - - /* Require a forward path through the pipeline */ - for (i = 1; i <= nsteps; i++) { - PJ *Q = P->opaque->pipeline[i]; - if ( ( Q->inverted && (Q->inv || Q->inv3d || Q->fwd4d) ) || - (!Q->inverted && (Q->fwd || Q->fwd3d || Q->fwd4d) ) ) { - continue; - } else { - proj_log_error (P, "Pipeline: A forward operation couldn't be constructed"); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); - } - } - - /* determine if an inverse operation is possible */ - for (i = 1; i <= nsteps; i++) { - PJ *Q = P->opaque->pipeline[i]; - if ( pj_has_inverse(Q) ) { - continue; - } else { - P->inv = 0; - P->inv3d = 0; - P->inv4d = 0; - break; - } - } - - /* Check that output units from step i are compatible with expected units in step i+1 */ - for (i = 1; i < nsteps; i++) { - enum pj_io_units unit_returned = pj_right (P->opaque->pipeline[i]); - enum pj_io_units unit_expected = pj_left (P->opaque->pipeline[i+1]); - - if ( unit_returned == PJ_IO_UNITS_WHATEVER || unit_expected == PJ_IO_UNITS_WHATEVER ) - continue; - if ( unit_returned != unit_expected ) { - proj_log_error (P, "Pipeline: Mismatched units between step %d and %d", i, i+1); - return destructor (P, PJD_ERR_MALFORMED_PIPELINE); - } - } - - proj_log_trace (P, "Pipeline: %d steps built. Determining i/o characteristics", nsteps); - - /* Determine forward input (= reverse output) data type */ - P->left = pj_left (P->opaque->pipeline[1]); - - /* Now, correspondingly determine forward output (= reverse input) data type */ - P->right = pj_right (P->opaque->pipeline[nsteps]); - return P; -} diff --git a/src/PJ_pipeline.cpp b/src/PJ_pipeline.cpp new file mode 100644 index 00000000..c20454df --- /dev/null +++ b/src/PJ_pipeline.cpp @@ -0,0 +1,501 @@ +/******************************************************************************* + + Transformation pipeline manager + + Thomas Knudsen, 2016-05-20/2016-11-20 + +******************************************************************************** + + Geodetic transformations are typically organized in a number of + steps. For example, a datum shift could be carried out through + these steps: + + 1. Convert (latitude, longitude, ellipsoidal height) to + 3D geocentric cartesian coordinates (X, Y, Z) + 2. Transform the (X, Y, Z) coordinates to the new datum, using a + 7 parameter Helmert transformation. + 3. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) + + If the height system used is orthometric, rather than ellipsoidal, + another step is needed at each end of the process: + + 1. Add the local geoid undulation (N) to the orthometric height + to obtain the ellipsoidal (i.e. geometric) height. + 2. Convert (latitude, longitude, ellipsoidal height) to + 3D geocentric cartesian coordinates (X, Y, Z) + 3. Transform the (X, Y, Z) coordinates to the new datum, using a + 7 parameter Helmert transformation. + 4. Convert (X, Y, Z) back to (latitude, longitude, ellipsoidal height) + 5. Subtract the local geoid undulation (N) from the ellipsoidal height + to obtain the orthometric height. + + Additional steps can be added for e.g. change of vertical datum, so the + list can grow fairly long. None of the steps are, however, particularly + complex, and data flow is strictly from top to bottom. + + Hence, in principle, the first example above could be implemented using + Unix pipelines: + + cat my_coordinates | geographic_to_xyz | helmert | xyz_to_geographic > my_transformed_coordinates + + in the grand tradition of Software Tools [1]. + + The proj pipeline driver implements a similar concept: Stringing together + a number of steps, feeding the output of one step to the input of the next. + + It is a very powerful concept, that increases the range of relevance of the + proj.4 system substantially. It is, however, not a particularly intrusive + addition to the PROJ.4 code base: The implementation is by and large completed + by adding an extra projection called "pipeline" (i.e. this file), which + handles all business, and a small amount of added functionality in the + pj_init code, implementing support for multilevel, embedded pipelines. + + Syntactically, the pipeline system introduces the "+step" keyword (which + indicates the start of each transformation step), and reintroduces the +inv + keyword (indicating that a given transformation step should run in reverse, i.e. + forward, when the pipeline is executed in inverse direction, and vice versa). + + Hence, the first transformation example above, can be implemented as: + + +proj=pipeline +step proj=cart +step proj=helmert +step proj=cart +inv + + Where indicate the Helmert arguments: 3 translations (+x=..., +y=..., + +z=...), 3 rotations (+rx=..., +ry=..., +rz=...) and a scale factor (+s=...). + Following geodetic conventions, the rotations are given in arcseconds, + and the scale factor is given as parts-per-million. + + [1] B. W. Kernighan & P. J. Plauger: Software tools. + Reading, Massachusetts, Addison-Wesley, 1976, 338 pp. + +******************************************************************************** + +Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 + +******************************************************************************** +* Copyright (c) 2016, 2017, 2018 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 +#include + +#include "geodesic.h" +#include "proj.h" +#include "proj_internal.h" +#include "projects.h" + +PROJ_HEAD(pipeline, "Transformation pipeline manager"); + +/* Projection specific elements for the PJ object */ +struct pj_opaque { + int steps; + char **argv; + char **current_argv; + PJ **pipeline; +}; + + + +static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P); +static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P); +static XYZ pipeline_forward_3d (LPZ lpz, PJ *P); +static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P); +static XY pipeline_forward (LP lp, PJ *P); +static LP pipeline_reverse (XY xy, PJ *P); + + + + +static PJ_COORD pipeline_forward_4d (PJ_COORD point, PJ *P) { + int i, first_step, last_step; + + first_step = 1; + last_step = static_cast(P->opaque)->steps + 1; + + for (i = first_step; i != last_step; i++) + point = proj_trans (static_cast(P->opaque)->pipeline[i], PJ_FWD, point); + + return point; +} + + +static PJ_COORD pipeline_reverse_4d (PJ_COORD point, PJ *P) { + int i, first_step, last_step; + + first_step = static_cast(P->opaque)->steps; + last_step = 0; + + for (i = first_step; i != last_step; i--) + point = proj_trans (static_cast(P->opaque)->pipeline[i], PJ_INV, point); + + return point; +} + + + + +static XYZ pipeline_forward_3d (LPZ lpz, PJ *P) { + PJ_COORD point = {{0,0,0,0}}; + int i; + point.lpz = lpz; + + for (i = 1; i <= static_cast(P->opaque)->steps; i++) + point = pj_approx_3D_trans (static_cast(P->opaque)->pipeline[i], PJ_FWD, point); + + return point.xyz; +} + + +static LPZ pipeline_reverse_3d (XYZ xyz, PJ *P) { + PJ_COORD point = {{0,0,0,0}}; + int i; + point.xyz = xyz; + + for (i = static_cast(P->opaque)->steps; i > 0 ; i--) + point = pj_approx_3D_trans (static_cast(P->opaque)->pipeline[i], PJ_INV, point); + + return point.lpz; +} + + + + +static XY pipeline_forward (LP lp, PJ *P) { + PJ_COORD point = {{0,0,0,0}}; + int i; + point.lp = lp; + + for (i = 1; i <= static_cast(P->opaque)->steps; i++) + point = pj_approx_2D_trans (static_cast(P->opaque)->pipeline[i], PJ_FWD, point); + + return point.xy; +} + + +static LP pipeline_reverse (XY xy, PJ *P) { + PJ_COORD point = {{0,0,0,0}}; + int i; + point.xy = xy; + for (i = static_cast(P->opaque)->steps; i > 0 ; i--) + point = pj_approx_2D_trans (static_cast(P->opaque)->pipeline[i], PJ_INV, point); + + return point.lp; +} + + + + +static PJ *destructor (PJ *P, int errlev) { + int i; + if (0==P) + return 0; + + if (0==P->opaque) + return pj_default_destructor (P, errlev); + + /* Deallocate each pipeine step, then pipeline array */ + if (0!=static_cast(P->opaque)->pipeline) + for (i = 0; i < static_cast(P->opaque)->steps; i++) + proj_destroy (static_cast(P->opaque)->pipeline[i+1]); + pj_dealloc (static_cast(P->opaque)->pipeline); + + pj_dealloc (static_cast(P->opaque)->argv); + pj_dealloc (static_cast(P->opaque)->current_argv); + + return pj_default_destructor(P, errlev); +} + + +static PJ *pj_create_pipeline (PJ *P, size_t steps) { + + /* Room for the pipeline: An array of PJ * with room for sentinels at both ends */ + static_cast(P->opaque)->pipeline = static_cast(pj_calloc (steps + 2, sizeof(PJ *))); + if (0==static_cast(P->opaque)->pipeline) + return 0; + + static_cast(P->opaque)->steps = (int)steps; + + return P; +} + + + + +/* count the number of args in pipeline definition, and mark all args as used */ +static size_t argc_params (paralist *params) { + size_t argc = 0; + for (; params != 0; params = params->next) { + argc++; + params->used = 1; + } + return ++argc; /* one extra for the sentinel */ +} + +/* Sentinel for argument list */ +static char *argv_sentinel = "step"; + +/* turn paralist into argc/argv style argument list */ +static char **argv_params (paralist *params, size_t argc) { + char **argv; + size_t i = 0; + argv = static_cast(pj_calloc (argc, sizeof (char *))); + if (0==argv) + return 0; + for (; params != 0; params = params->next) + argv[i++] = params->param; + argv[i++] = argv_sentinel; + return argv; +} + + + + +/* Being the special operator that the pipeline is, we have to handle the */ +/* ellipsoid differently than usual. In general, the pipeline operation does */ +/* not need an ellipsoid, but in some cases it is beneficial nonetheless. */ +/* Unfortunately we can't use the normal ellipsoid setter in pj_init, since */ +/* it adds a +ellps parameter to the global args if nothing else is specified*/ +/* This is problematic since that ellipsoid spec is then passed on to the */ +/* pipeline children. This is rarely what we want, so here we implement our */ +/* own logic instead. If an ellipsoid is set in the global args, it is used */ +/* as the pipeline ellipsoid. Otherwise we use WGS84 parameters as default. */ +/* At last we calculate the rest of the ellipsoid parameters and */ +/* re-initialize P->geod. */ +static void set_ellipsoid(PJ *P) { + paralist *cur, *attachment; + int err = proj_errno_reset (P); + + /* Break the linked list after the global args */ + attachment = 0; + for (cur = P->params; cur != 0; cur = cur->next) + /* cur->next will always be non 0 given argv_sentinel presence, */ + /* but this is far from being obvious for a static analyzer */ + if (cur->next != 0 && strcmp(argv_sentinel, cur->next->param) == 0) { + attachment = cur->next; + cur->next = 0; + break; + } + + /* Check if there's any ellipsoid specification in the global params. */ + /* If not, use WGS84 as default */ + if (0 != pj_ellipsoid (P)) { + P->a = 6378137.0; + P->es = .00669438002290341575; + + /* reset an "unerror": In this special use case, the errno is */ + /* not an error signal, but just a reply from pj_ellipsoid, */ + /* telling us that "No - there was no ellipsoid definition in */ + /* the PJ you provided". */ + proj_errno_reset (P); + } + P->a_orig = P->a; + P->es_orig = P->es; + + pj_calc_ellipsoid_params (P, P->a, P->es); + + geod_init(P->geod, P->a, (1 - sqrt (1 - P->es))); + + /* Re-attach the dangling list */ + /* Note: cur will always be non 0 given argv_sentinel presence, */ + /* but this is far from being obvious for a static analyzer */ + if( cur != 0 ) + cur->next = attachment; + proj_errno_restore (P, err); +} + + + + +PJ *OPERATION(pipeline,0) { + int i, nsteps = 0, argc; + int i_pipeline = -1, i_first_step = -1, i_current_step; + char **argv, **current_argv; + + P->fwd4d = pipeline_forward_4d; + P->inv4d = pipeline_reverse_4d; + P->fwd3d = pipeline_forward_3d; + P->inv3d = pipeline_reverse_3d; + P->fwd = pipeline_forward; + P->inv = pipeline_reverse; + P->destructor = destructor; + P->is_pipeline = 1; + + /* Currently, the pipeline driver is a raw bit mover, enabling other operations */ + /* to collaborate efficiently. All prep/fin stuff is done at the step levels. */ + P->skip_fwd_prepare = 1; + P->skip_fwd_finalize = 1; + P->skip_inv_prepare = 1; + P->skip_inv_finalize = 1; + + + P->opaque = static_cast(pj_calloc (1, sizeof(struct pj_opaque))); + if (0==P->opaque) + return destructor(P, ENOMEM); + + argc = (int)argc_params (P->params); + static_cast(P->opaque)->argv = argv = argv_params (P->params, argc); + if (0==argv) + return destructor (P, ENOMEM); + + static_cast(P->opaque)->current_argv = current_argv = static_cast(pj_calloc (argc, sizeof (char *))); + if (0==current_argv) + return destructor (P, ENOMEM); + + /* Do some syntactical sanity checking */ + for (i = 0; i < argc; i++) { + if (0==strcmp (argv_sentinel, argv[i])) { + if (-1==i_pipeline) { + proj_log_error (P, "Pipeline: +step before +proj=pipeline"); + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + } + if (0==nsteps) + i_first_step = i; + nsteps++; + continue; + } + + if (0==strcmp ("proj=pipeline", argv[i])) { + if (-1 != i_pipeline) { + proj_log_error (P, "Pipeline: Nesting only allowed when child pipelines are wrapped in '+init's"); + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: nested pipelines */ + } + i_pipeline = i; + } + } + nsteps--; /* Last instance of +step is just a sentinel */ + static_cast(P->opaque)->steps = nsteps; + + if (-1==i_pipeline) + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ + + if (0==nsteps) + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ + + /* Make room for the pipeline and execution indicators */ + if (0==pj_create_pipeline (P, nsteps)) + return destructor (P, ENOMEM); + + set_ellipsoid(P); + + /* Now loop over all steps, building a new set of arguments for each init */ + i_current_step = i_first_step; + for (i = 0; i < nsteps; i++) { + int j; + int current_argc = 0; + int err; + PJ *next_step = 0; + + /* Build a set of setup args for the current step */ + proj_log_trace (P, "Pipeline: Building arg list for step no. %d", i); + + /* First add the step specific args */ + for (j = i_current_step + 1; 0 != strcmp ("step", argv[j]); j++) + current_argv[current_argc++] = argv[j]; + + i_current_step = j; + + /* Then add the global args */ + for (j = i_pipeline + 1; 0 != strcmp ("step", argv[j]); j++) + current_argv[current_argc++] = argv[j]; + + proj_log_trace (P, "Pipeline: init - %s, %d", current_argv[0], current_argc); + for (j = 1; j < current_argc; j++) + proj_log_trace (P, " %s", current_argv[j]); + + err = proj_errno_reset (P); + + next_step = proj_create_argv (P->ctx, current_argc, current_argv); + proj_log_trace (P, "Pipeline: Step %d (%s) at %p", i, current_argv[0], next_step); + + if (0==next_step) { + /* The step init failed, but possibly without setting errno. If so, we say "malformed" */ + int err_to_report = proj_errno(P); + if (0==err_to_report) + err_to_report = PJD_ERR_MALFORMED_PIPELINE; + proj_log_error (P, "Pipeline: Bad step definition: %s (%s)", current_argv[0], pj_strerrno (err_to_report)); + return destructor (P, err_to_report); /* ERROR: bad pipeline def */ + } + + proj_errno_restore (P, err); + + /* Is this step inverted? */ + for (j = 0; j < current_argc; j++) + if (0==strcmp("inv", current_argv[j])) { + /* if +inv exists in both global and local args the forward operation should be used */ + next_step->inverted = next_step->inverted == 0 ? 1 : 0; + } + + static_cast(P->opaque)->pipeline[i+1] = next_step; + + proj_log_trace (P, "Pipeline at [%p]: step at [%p] (%s) done", P, next_step, current_argv[0]); + } + + /* Require a forward path through the pipeline */ + for (i = 1; i <= nsteps; i++) { + PJ *Q = static_cast(P->opaque)->pipeline[i]; + if ( ( Q->inverted && (Q->inv || Q->inv3d || Q->fwd4d) ) || + (!Q->inverted && (Q->fwd || Q->fwd3d || Q->fwd4d) ) ) { + continue; + } else { + proj_log_error (P, "Pipeline: A forward operation couldn't be constructed"); + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + } + } + + /* determine if an inverse operation is possible */ + for (i = 1; i <= nsteps; i++) { + PJ *Q = static_cast(P->opaque)->pipeline[i]; + if ( pj_has_inverse(Q) ) { + continue; + } else { + P->inv = 0; + P->inv3d = 0; + P->inv4d = 0; + break; + } + } + + /* Check that output units from step i are compatible with expected units in step i+1 */ + for (i = 1; i < nsteps; i++) { + enum pj_io_units unit_returned = pj_right (static_cast(P->opaque)->pipeline[i]); + enum pj_io_units unit_expected = pj_left (static_cast(P->opaque)->pipeline[i+1]); + + if ( unit_returned == PJ_IO_UNITS_WHATEVER || unit_expected == PJ_IO_UNITS_WHATEVER ) + continue; + if ( unit_returned != unit_expected ) { + proj_log_error (P, "Pipeline: Mismatched units between step %d and %d", i, i+1); + return destructor (P, PJD_ERR_MALFORMED_PIPELINE); + } + } + + proj_log_trace (P, "Pipeline: %d steps built. Determining i/o characteristics", nsteps); + + /* Determine forward input (= reverse output) data type */ + P->left = pj_left (static_cast(P->opaque)->pipeline[1]); + + /* Now, correspondingly determine forward output (= reverse input) data type */ + P->right = pj_right (static_cast(P->opaque)->pipeline[nsteps]); + return P; +} diff --git a/src/PJ_poly.c b/src/PJ_poly.c deleted file mode 100644 index b5f78fdf..00000000 --- a/src/PJ_poly.c +++ /dev/null @@ -1,169 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(poly, "Polyconic (American)") - "\n\tConic, Sph&Ell"; - -struct pj_opaque { - double ml0; \ - double *en; -}; - -#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 = 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 = 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 = 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 void *destructor(PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - if (P->opaque->en) - pj_dealloc (P->opaque->en); - - return pj_default_destructor(P, errlev); -} - - -PJ *PROJECTION(poly) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_poly.cpp b/src/PJ_poly.cpp new file mode 100644 index 00000000..3bf7a8dd --- /dev/null +++ b/src/PJ_poly.cpp @@ -0,0 +1,169 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +PROJ_HEAD(poly, "Polyconic (American)") + "\n\tConic, Sph&Ell"; + +struct pj_opaque { + double ml0; \ + double *en; +}; + +#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 (0==P) + return 0; + + if (0==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 (0==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.c b/src/PJ_putp2.c deleted file mode 100644 index d7a847c8..00000000 --- a/src/PJ_putp2.c +++ /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_putp2.cpp b/src/PJ_putp2.cpp new file mode 100644 index 00000000..d7a847c8 --- /dev/null +++ b/src/PJ_putp2.cpp @@ -0,0 +1,61 @@ +#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.c b/src/PJ_putp3.c deleted file mode 100644 index 695ea877..00000000 --- a/src/PJ_putp3.c +++ /dev/null @@ -1,65 +0,0 @@ -#define PJ_LIB__ -#include -#include "projects.h" - -struct pj_opaque { - double A; -}; - -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. - 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. - P->opaque->A * lp.phi * lp.phi)); - - return lp; -} - - -PJ *PROJECTION(putp3) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_putp3.cpp b/src/PJ_putp3.cpp new file mode 100644 index 00000000..6e85d35f --- /dev/null +++ b/src/PJ_putp3.cpp @@ -0,0 +1,65 @@ +#define PJ_LIB__ +#include +#include "projects.h" + +struct pj_opaque { + double A; +}; + +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 (0==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 (0==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.c b/src/PJ_putp4p.c deleted file mode 100644 index 6448dc19..00000000 --- a/src/PJ_putp4p.c +++ /dev/null @@ -1,74 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -struct pj_opaque { - double C_x, C_y; -}; - -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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_putp4p.cpp b/src/PJ_putp4p.cpp new file mode 100644 index 00000000..77a18651 --- /dev/null +++ b/src/PJ_putp4p.cpp @@ -0,0 +1,74 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +struct pj_opaque { + double C_x, C_y; +}; + +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 (0==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 (0==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.c b/src/PJ_putp5.c deleted file mode 100644 index 96b0670d..00000000 --- a/src/PJ_putp5.c +++ /dev/null @@ -1,73 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -struct pj_opaque { - double A, B; -}; - -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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_putp5.cpp b/src/PJ_putp5.cpp new file mode 100644 index 00000000..d73e9368 --- /dev/null +++ b/src/PJ_putp5.cpp @@ -0,0 +1,73 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +struct pj_opaque { + double A, B; +}; + +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 (0==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 (0==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.c b/src/PJ_putp6.c deleted file mode 100644 index fa9290b4..00000000 --- a/src/PJ_putp6.c +++ /dev/null @@ -1,95 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -struct pj_opaque { - double C_x, C_y, A, B, D; -}; - -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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_putp6.cpp b/src/PJ_putp6.cpp new file mode 100644 index 00000000..fcd8146f --- /dev/null +++ b/src/PJ_putp6.cpp @@ -0,0 +1,95 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +struct pj_opaque { + double C_x, C_y, A, B, D; +}; + +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 (0==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 (0==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.c b/src/PJ_qsc.c deleted file mode 100644 index 3b8b7fe2..00000000 --- a/src/PJ_qsc.c +++ /dev/null @@ -1,402 +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. */ -enum Face { - FACE_FRONT = 0, - FACE_RIGHT = 1, - FACE_BACK = 2, - FACE_LEFT = 3, - FACE_TOP = 4, - FACE_BOTTOM = 5 -}; - -struct pj_opaque { - enum Face face; - double a_squared; - double b; - double one_minus_f; - double one_minus_f_squared; -}; -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. */ -enum Area { - AREA_0 = 0, - AREA_1 = 1, - AREA_2 = 2, - AREA_3 = 3 -}; - -/* 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_qsc.cpp b/src/PJ_qsc.cpp new file mode 100644 index 00000000..767ed4a8 --- /dev/null +++ b/src/PJ_qsc.cpp @@ -0,0 +1,402 @@ +/* + * 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. */ +enum Face { + FACE_FRONT = 0, + FACE_RIGHT = 1, + FACE_BACK = 2, + FACE_LEFT = 3, + FACE_TOP = 4, + FACE_BOTTOM = 5 +}; + +struct pj_opaque { + enum Face face; + double a_squared; + double b; + double one_minus_f; + double one_minus_f_squared; +}; +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. */ +enum Area { + AREA_0 = 0, + AREA_1 = 1, + AREA_2 = 2, + AREA_3 = 3 +}; + +/* 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 (0==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.c b/src/PJ_robin.c deleted file mode 100644 index 19bdc2dc..00000000 --- a/src/PJ_robin.c +++ /dev/null @@ -1,159 +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 -*/ - -struct COEFS { - float c0, c1, c2, c3; -}; - -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_robin.cpp b/src/PJ_robin.cpp new file mode 100644 index 00000000..19bdc2dc --- /dev/null +++ b/src/PJ_robin.cpp @@ -0,0 +1,159 @@ +#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 +*/ + +struct COEFS { + float c0, c1, c2, c3; +}; + +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.c b/src/PJ_rpoly.c deleted file mode 100644 index 03c2b161..00000000 --- a/src/PJ_rpoly.c +++ /dev/null @@ -1,56 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -struct pj_opaque { - double phi1; - double fxa; - double fxb; - int mode; -}; - -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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_rpoly.cpp b/src/PJ_rpoly.cpp new file mode 100644 index 00000000..24360965 --- /dev/null +++ b/src/PJ_rpoly.cpp @@ -0,0 +1,56 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +struct pj_opaque { + double phi1; + double fxa; + double fxb; + int mode; +}; + +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 (0==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.c b/src/PJ_sch.c deleted file mode 100644 index 64e5cdb8..00000000 --- a/src/PJ_sch.c +++ /dev/null @@ -1,230 +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" - -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; -}; - -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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_sch.cpp b/src/PJ_sch.cpp new file mode 100644 index 00000000..4a0b1fb8 --- /dev/null +++ b/src/PJ_sch.cpp @@ -0,0 +1,230 @@ +/****************************************************************************** + * 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" + +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; +}; + +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 (0==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.c b/src/PJ_sconics.c deleted file mode 100644 index ce044c24..00000000 --- a/src/PJ_sconics.c +++ /dev/null @@ -1,216 +0,0 @@ -#define PJ_LIB__ -#include -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - - -enum Type { - EULER = 0, - MURD1 = 1, - MURD2 = 2, - MURD3 = 3, - PCONIC = 4, - TISSOT = 5, - VITK1 = 6 -}; - -struct pj_opaque { - double n; - double rho_c; - double rho_0; - double sig; - double c1, c2; - enum Type type; -}; - - -#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); - P->opaque->sig = 0.5 * (p2 + p1); - err = (fabs(*del) < EPS || fabs(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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_sconics.cpp b/src/PJ_sconics.cpp new file mode 100644 index 00000000..4efec86f --- /dev/null +++ b/src/PJ_sconics.cpp @@ -0,0 +1,216 @@ +#define PJ_LIB__ +#include +#include "proj.h" +#include "projects.h" +#include "proj_math.h" + + +enum Type { + EULER = 0, + MURD1 = 1, + MURD2 = 2, + MURD3 = 3, + PCONIC = 4, + TISSOT = 5, + VITK1 = 6 +}; + +struct pj_opaque { + double n; + double rho_c; + double rho_0; + double sig; + double c1, c2; + enum Type type; +}; + + +#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 (0==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.c b/src/PJ_somerc.c deleted file mode 100644 index c6c3ff21..00000000 --- a/src/PJ_somerc.c +++ /dev/null @@ -1,92 +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"; - -struct pj_opaque { - double K, c, hlf_e, kR, cosp0, sinp0; -}; - -#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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_somerc.cpp b/src/PJ_somerc.cpp new file mode 100644 index 00000000..6a98f76f --- /dev/null +++ b/src/PJ_somerc.cpp @@ -0,0 +1,92 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +PROJ_HEAD(somerc, "Swiss. Obl. Mercator") "\n\tCyl, Ell\n\tFor CH1903"; + +struct pj_opaque { + double K, c, hlf_e, kR, cosp0, sinp0; +}; + +#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 (0==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.c b/src/PJ_stere.c deleted file mode 100644 index 82fd5c07..00000000 --- a/src/PJ_stere.c +++ /dev/null @@ -1,316 +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"; - - -enum Mode { - S_POLE = 0, - N_POLE = 1, - OBLIQ = 2, - EQUIT = 3 -}; - -struct pj_opaque { - double phits; - double sinX1; - double cosX1; - double akm1; - enum Mode mode; -}; - -#define sinph0 P->opaque->sinX1 -#define cosph0 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 = 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 = 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 = 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 = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_stere.cpp b/src/PJ_stere.cpp new file mode 100644 index 00000000..94e7f91d --- /dev/null +++ b/src/PJ_stere.cpp @@ -0,0 +1,316 @@ +#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"; + + +enum Mode { + S_POLE = 0, + N_POLE = 1, + OBLIQ = 2, + EQUIT = 3 +}; + +struct pj_opaque { + double phits; + double sinX1; + double cosX1; + double akm1; + enum Mode mode; +}; + +#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 (0==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 (0==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.c b/src/PJ_sterea.c deleted file mode 100644 index eb4c9f2c..00000000 --- a/src/PJ_sterea.c +++ /dev/null @@ -1,115 +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" - - -struct pj_opaque { - double phic0; - double cosc0, sinc0; - double R2; - void *en; -}; - - -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 = 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 = 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 void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor (P, errlev); -} - - -PJ *PROJECTION(sterea) { - double R; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R); - if (0==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_sterea.cpp b/src/PJ_sterea.cpp new file mode 100644 index 00000000..4c2fe2a3 --- /dev/null +++ b/src/PJ_sterea.cpp @@ -0,0 +1,115 @@ +/* +** 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" + + +struct pj_opaque { + double phic0; + double cosc0, sinc0; + double R2; + void *en; +}; + + +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 (0==P) + return 0; + + if (0==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 (0==Q) + return pj_default_destructor (P, ENOMEM); + P->opaque = Q; + + Q->en = pj_gauss_ini(P->e, P->phi0, &(Q->phic0), &R); + if (0==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.c b/src/PJ_sts.c deleted file mode 100644 index 66094178..00000000 --- a/src/PJ_sts.c +++ /dev/null @@ -1,107 +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"; - - -struct pj_opaque { - double C_x, C_y, C_p; - int tan_mode; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = 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 = 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; - P->opaque->C_x = q / p; - P->opaque->C_y = p; - P->opaque->C_p = 1/ q; - P->opaque->tan_mode = mode; - return P; -} - - - -PJ *PROJECTION(fouc) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 2., 2., 1); -} - - - -PJ *PROJECTION(kav5) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 2., 2., 0); -} - - - -PJ *PROJECTION(mbt_s) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - return setup(P, 1.48875, 1.36509, 0); -} diff --git a/src/PJ_sts.cpp b/src/PJ_sts.cpp new file mode 100644 index 00000000..4aece68e --- /dev/null +++ b/src/PJ_sts.cpp @@ -0,0 +1,107 @@ +#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"; + + +struct pj_opaque { + double C_x, C_y, C_p; + int tan_mode; +}; + + +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 (0==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 (0==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 (0==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 (0==Q) + return pj_default_destructor(P, ENOMEM); + P->opaque = Q; + return setup(P, 1.48875, 1.36509, 0); +} diff --git a/src/PJ_tcc.c b/src/PJ_tcc.c deleted file mode 100644 index 60ded63e..00000000 --- a/src/PJ_tcc.c +++ /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 = 0; - - return P; -} diff --git a/src/PJ_tcc.cpp b/src/PJ_tcc.cpp new file mode 100644 index 00000000..60ded63e --- /dev/null +++ b/src/PJ_tcc.cpp @@ -0,0 +1,34 @@ +#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 = 0; + + return P; +} diff --git a/src/PJ_tcea.c b/src/PJ_tcea.c deleted file mode 100644 index d30f3df0..00000000 --- a/src/PJ_tcea.c +++ /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_tcea.cpp b/src/PJ_tcea.cpp new file mode 100644 index 00000000..d30f3df0 --- /dev/null +++ b/src/PJ_tcea.cpp @@ -0,0 +1,36 @@ +#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.c b/src/PJ_times.c deleted file mode 100644 index e8b4499f..00000000 --- a/src/PJ_times.c +++ /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_times.cpp b/src/PJ_times.cpp new file mode 100644 index 00000000..e8b4499f --- /dev/null +++ b/src/PJ_times.cpp @@ -0,0 +1,79 @@ +/****************************************************************************** + * 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.c b/src/PJ_tmerc.c deleted file mode 100644 index 069cdc2c..00000000 --- a/src/PJ_tmerc.c +++ /dev/null @@ -1,208 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "proj.h" -#include "projects.h" - -PROJ_HEAD(tmerc, "Transverse Mercator") "\n\tCyl, Sph&Ell"; - - -struct pj_opaque { - double esp; - double ml0; - double *en; -}; - -#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 = 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 = 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 = 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 = 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 / P->opaque->esp); - g = .5 * (h - 1. / h); - h = cos (P->phi0 + xy.y / 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 void *destructor(PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor(P, errlev); - - pj_dealloc (P->opaque->en); - return pj_default_destructor(P, errlev); -} - - -static PJ *setup(PJ *P) { /* general initialization */ - struct pj_opaque *Q = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - - P->opaque = Q; - P->destructor = destructor; - - return setup(P); -} diff --git a/src/PJ_tmerc.cpp b/src/PJ_tmerc.cpp new file mode 100644 index 00000000..55f878c9 --- /dev/null +++ b/src/PJ_tmerc.cpp @@ -0,0 +1,208 @@ +#define PJ_LIB__ + +#include +#include + +#include "proj.h" +#include "projects.h" + +PROJ_HEAD(tmerc, "Transverse Mercator") "\n\tCyl, Sph&Ell"; + + +struct pj_opaque { + double esp; + double ml0; + double *en; +}; + +#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 (0==P) + return 0; + + if (0==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 (0==Q) + return pj_default_destructor (P, ENOMEM); + + P->opaque = Q; + P->destructor = destructor; + + return setup(P); +} diff --git a/src/PJ_tobmerc.c b/src/PJ_tobmerc.c deleted file mode 100644 index 9c939f0b..00000000 --- a/src/PJ_tobmerc.c +++ /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_tobmerc.cpp b/src/PJ_tobmerc.cpp new file mode 100644 index 00000000..9c939f0b --- /dev/null +++ b/src/PJ_tobmerc.cpp @@ -0,0 +1,51 @@ +#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.c b/src/PJ_tpeqd.c deleted file mode 100644 index 87877ec1..00000000 --- a/src/PJ_tpeqd.c +++ /dev/null @@ -1,107 +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="; - -struct pj_opaque { - double cp1, sp1, cp2, sp2, ccs, cs, sc, r2z0, z02, dlam2; - double hz0, thz0, rhshz0, ca, sa, lp, lamc; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = 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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_tpeqd.cpp b/src/PJ_tpeqd.cpp new file mode 100644 index 00000000..5691cd7b --- /dev/null +++ b/src/PJ_tpeqd.cpp @@ -0,0 +1,107 @@ +#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="; + +struct pj_opaque { + double cp1, sp1, cp2, sp2, ccs, cs, sc, r2z0, z02, dlam2; + double hz0, thz0, rhshz0, ca, sa, lp, lamc; +}; + + +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 (0==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.c b/src/PJ_unitconvert.c deleted file mode 100644 index 402941a4..00000000 --- a/src/PJ_unitconvert.c +++ /dev/null @@ -1,548 +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); - -struct TIME_UNITS { - char *id; /* units keyword */ - tconvert t_in; /* unit -> mod. julian date function pointer */ - tconvert t_out; /* mod. julian date > unit function pointer */ - char *name; /* comments */ -}; - -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 */ -}; - - -/***********************************************************************/ -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"}, - {NULL, NULL, NULL, NULL} -}; - - -/***********************************************************************/ -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 = NULL; - } - if( p_is_linear ) { - *p_is_linear = -1; - } - return 0.0; -} - -/***********************************************************************/ -PJ *CONVERSION(unitconvert,0) { -/***********************************************************************/ - struct pj_opaque_unitconvert *Q = pj_calloc (1, sizeof (struct pj_opaque_unitconvert)); - 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 (0==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) != NULL) { - const char* normalized_name = NULL; - 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) != NULL) { - const char* normalized_name = NULL; - 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) != NULL) { - const char* normalized_name = NULL; - 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) != NULL) { - const char* normalized_name = NULL; - 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) != NULL) { - 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 = 0; - if ((name = pj_param (P->ctx, P->params, "st_out").s) != NULL) { - 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_unitconvert.cpp b/src/PJ_unitconvert.cpp new file mode 100644 index 00000000..7476620e --- /dev/null +++ b/src/PJ_unitconvert.cpp @@ -0,0 +1,548 @@ +/*********************************************************************** + + 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); + +struct TIME_UNITS { + char *id; /* units keyword */ + tconvert t_in; /* unit -> mod. julian date function pointer */ + tconvert t_out; /* mod. julian date > unit function pointer */ + char *name; /* comments */ +}; + +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 */ +}; + + +/***********************************************************************/ +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"}, + {NULL, NULL, NULL, NULL} +}; + + +/***********************************************************************/ +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 = NULL; + } + 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))); + 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 (0==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) != NULL) { + const char* normalized_name = NULL; + 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) != NULL) { + const char* normalized_name = NULL; + 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) != NULL) { + const char* normalized_name = NULL; + 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) != NULL) { + const char* normalized_name = NULL; + 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) != NULL) { + 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 = 0; + if ((name = pj_param (P->ctx, P->params, "st_out").s) != NULL) { + 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.c b/src/PJ_urm5.c deleted file mode 100644 index 293de8cf..00000000 --- a/src/PJ_urm5.c +++ /dev/null @@ -1,54 +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="; - -struct pj_opaque { - double m, rmn, q3, n; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0, 0.0}; - struct pj_opaque *Q = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = 0; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_urm5.cpp b/src/PJ_urm5.cpp new file mode 100644 index 00000000..6a208647 --- /dev/null +++ b/src/PJ_urm5.cpp @@ -0,0 +1,54 @@ +#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="; + +struct pj_opaque { + double m, rmn, q3, n; +}; + + +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 (0==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 = 0; + P->fwd = s_forward; + + return P; +} diff --git a/src/PJ_urmfps.c b/src/PJ_urmfps.c deleted file mode 100644 index 467fc9bc..00000000 --- a/src/PJ_urmfps.c +++ /dev/null @@ -1,74 +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"; - -struct pj_opaque { - double n, C_y; -}; - -#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,P->opaque->n * sin (lp.phi)); - xy.x = C_x * lp.lam * cos (lp.phi); - xy.y = 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 /= P->opaque->C_y; - lp.phi = aasin(P->ctx, sin (xy.y) / P->opaque->n); - lp.lam = xy.x / (C_x * cos (xy.y)); - return lp; -} - - -static PJ *setup(PJ *P) { - P->opaque->C_y = Cy / P->opaque->n; - P->es = 0.; - P->inv = s_inverse; - P->fwd = s_forward; - return P; -} - - -PJ *PROJECTION(urmfps) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - - P->opaque = Q; - - if (pj_param(P->ctx, P->params, "tn").i) { - P->opaque->n = pj_param(P->ctx, P->params, "dn").f; - if (P->opaque->n <= 0. || 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - P->opaque->n = 0.8660254037844386467637231707; - return setup(P); -} diff --git a/src/PJ_urmfps.cpp b/src/PJ_urmfps.cpp new file mode 100644 index 00000000..1d147b9c --- /dev/null +++ b/src/PJ_urmfps.cpp @@ -0,0 +1,74 @@ +#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"; + +struct pj_opaque { + double n, C_y; +}; + +#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 (0==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 (0==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.c b/src/PJ_vandg.c deleted file mode 100644 index d148e210..00000000 --- a/src/PJ_vandg.c +++ /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_vandg.cpp b/src/PJ_vandg.cpp new file mode 100644 index 00000000..d148e210 --- /dev/null +++ b/src/PJ_vandg.cpp @@ -0,0 +1,106 @@ +#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.c b/src/PJ_vandg2.c deleted file mode 100644 index 447cb782..00000000 --- a/src/PJ_vandg2.c +++ /dev/null @@ -1,74 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -struct pj_opaque { - int vdg3; -}; - -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 = 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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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 = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==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_vandg2.cpp b/src/PJ_vandg2.cpp new file mode 100644 index 00000000..588366cf --- /dev/null +++ b/src/PJ_vandg2.cpp @@ -0,0 +1,74 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +struct pj_opaque { + int vdg3; +}; + +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 (0==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 (0==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.c b/src/PJ_vandg4.c deleted file mode 100644 index d9a53c87..00000000 --- a/src/PJ_vandg4.c +++ /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_vandg4.cpp b/src/PJ_vandg4.cpp new file mode 100644 index 00000000..d9a53c87 --- /dev/null +++ b/src/PJ_vandg4.cpp @@ -0,0 +1,55 @@ +#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.c b/src/PJ_vgridshift.c deleted file mode 100644 index ac5d26ef..00000000 --- a/src/PJ_vgridshift.c +++ /dev/null @@ -1,142 +0,0 @@ -#define PJ_LIB__ - -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -PROJ_HEAD(vgridshift, "Vertical grid shift"); - -struct pj_opaque_vgridshift { - double t_final; - double t_epoch; - double forward_multiplier; -}; - -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 != NULL) { - /* 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 != NULL) { - /* 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 = pj_calloc (1, sizeof (struct pj_opaque_vgridshift)); - if (0==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 = 0; - P->inv = 0; - - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_ANGULAR; - - return P; -} diff --git a/src/PJ_vgridshift.cpp b/src/PJ_vgridshift.cpp new file mode 100644 index 00000000..8aad3777 --- /dev/null +++ b/src/PJ_vgridshift.cpp @@ -0,0 +1,142 @@ +#define PJ_LIB__ + +#include +#include +#include +#include + +#include "proj_internal.h" +#include "projects.h" + +PROJ_HEAD(vgridshift, "Vertical grid shift"); + +struct pj_opaque_vgridshift { + double t_final; + double t_epoch; + double forward_multiplier; +}; + +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 != NULL) { + /* 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 != NULL) { + /* 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 (0==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 = 0; + P->inv = 0; + + P->left = PJ_IO_UNITS_ANGULAR; + P->right = PJ_IO_UNITS_ANGULAR; + + return P; +} diff --git a/src/PJ_wag2.c b/src/PJ_wag2.c deleted file mode 100644 index 1bee737a..00000000 --- a/src/PJ_wag2.c +++ /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_wag2.cpp b/src/PJ_wag2.cpp new file mode 100644 index 00000000..1bee737a --- /dev/null +++ b/src/PJ_wag2.cpp @@ -0,0 +1,38 @@ +#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.c b/src/PJ_wag3.c deleted file mode 100644 index ccd19d4d..00000000 --- a/src/PJ_wag3.c +++ /dev/null @@ -1,48 +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 - -struct pj_opaque { - double C_x; -}; - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = 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 / (P->opaque->C_x * cos(TWOTHIRD * lp.phi)); - return lp; -} - - -PJ *PROJECTION(wag3) { - double ts; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - - P->opaque = Q; - - ts = pj_param (P->ctx, P->params, "rlat_ts").f; - 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_wag3.cpp b/src/PJ_wag3.cpp new file mode 100644 index 00000000..0eeb73df --- /dev/null +++ b/src/PJ_wag3.cpp @@ -0,0 +1,48 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +PROJ_HEAD(wag3, "Wagner III") "\n\tPCyl, Sph\n\tlat_ts="; + +#define TWOTHIRD 0.6666666666666666666667 + +struct pj_opaque { + double C_x; +}; + + +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 (0==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.c b/src/PJ_wag7.c deleted file mode 100644 index 2009e672..00000000 --- a/src/PJ_wag7.c +++ /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 = 0; - P->es = 0.; - return P; -} diff --git a/src/PJ_wag7.cpp b/src/PJ_wag7.cpp new file mode 100644 index 00000000..2009e672 --- /dev/null +++ b/src/PJ_wag7.cpp @@ -0,0 +1,30 @@ +#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 = 0; + P->es = 0.; + return P; +} diff --git a/src/PJ_wink1.c b/src/PJ_wink1.c deleted file mode 100644 index 865cc6ac..00000000 --- a/src/PJ_wink1.c +++ /dev/null @@ -1,44 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(wink1, "Winkel I") "\n\tPCyl, Sph\n\tlat_ts="; - -struct pj_opaque { - double cosphi1; -}; - - - -static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ - XY xy = {0.0,0.0}; - xy.x = .5 * lp.lam * (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 / (P->opaque->cosphi1 + cos(lp.phi)); - return (lp); -} - - -PJ *PROJECTION(wink1) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - 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_wink1.cpp b/src/PJ_wink1.cpp new file mode 100644 index 00000000..6640f995 --- /dev/null +++ b/src/PJ_wink1.cpp @@ -0,0 +1,44 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +PROJ_HEAD(wink1, "Winkel I") "\n\tPCyl, Sph\n\tlat_ts="; + +struct pj_opaque { + double cosphi1; +}; + + + +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 (0==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.c b/src/PJ_wink2.c deleted file mode 100644 index f8e9af09..00000000 --- a/src/PJ_wink2.c +++ /dev/null @@ -1,52 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -PROJ_HEAD(wink2, "Winkel II") "\n\tPCyl, Sph, no inv\n\tlat_1="; - -struct pj_opaque { double cosphi1; }; - -#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) + P->opaque->cosphi1); - xy.y = M_FORTPI * (sin (lp.phi) + xy.y); - return xy; -} - - -PJ *PROJECTION(wink2) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - P->opaque->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f); - P->es = 0.; - P->inv = 0; - P->fwd = s_forward; - - return P; -} diff --git a/src/PJ_wink2.cpp b/src/PJ_wink2.cpp new file mode 100644 index 00000000..9da65eaa --- /dev/null +++ b/src/PJ_wink2.cpp @@ -0,0 +1,52 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +PROJ_HEAD(wink2, "Winkel II") "\n\tPCyl, Sph, no inv\n\tlat_1="; + +struct pj_opaque { double cosphi1; }; + +#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 (0==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 = 0; + P->fwd = s_forward; + + return P; +} diff --git a/src/aasincos.c b/src/aasincos.c deleted file mode 100644 index 4f9ea25f..00000000 --- a/src/aasincos.c +++ /dev/null @@ -1,38 +0,0 @@ -/* arc sin, cosine, tan2 and sqrt that will NOT fail */ - -#include - -#include "projects.h" - -#define ONE_TOL 1.00000000000001 -#define ATOL 1e-50 - - double -aasin(projCtx ctx,double v) { - double av; - - if ((av = fabs(v)) >= 1.) { - if (av > ONE_TOL) - pj_ctx_set_errno( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); - return (v < 0. ? -M_HALFPI : M_HALFPI); - } - return asin(v); -} - - double -aacos(projCtx ctx, double v) { - double av; - - if ((av = fabs(v)) >= 1.) { - if (av > ONE_TOL) - pj_ctx_set_errno( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); - return (v < 0. ? M_PI : 0.); - } - return acos(v); -} - double -asqrt(double v) { return ((v <= 0) ? 0. : sqrt(v)); } - double -aatan2(double n, double d) { - return ((fabs(n) < ATOL && fabs(d) < ATOL) ? 0. : atan2(n,d)); -} diff --git a/src/aasincos.cpp b/src/aasincos.cpp new file mode 100644 index 00000000..4f9ea25f --- /dev/null +++ b/src/aasincos.cpp @@ -0,0 +1,38 @@ +/* arc sin, cosine, tan2 and sqrt that will NOT fail */ + +#include + +#include "projects.h" + +#define ONE_TOL 1.00000000000001 +#define ATOL 1e-50 + + double +aasin(projCtx ctx,double v) { + double av; + + if ((av = fabs(v)) >= 1.) { + if (av > ONE_TOL) + pj_ctx_set_errno( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); + return (v < 0. ? -M_HALFPI : M_HALFPI); + } + return asin(v); +} + + double +aacos(projCtx ctx, double v) { + double av; + + if ((av = fabs(v)) >= 1.) { + if (av > ONE_TOL) + pj_ctx_set_errno( ctx, PJD_ERR_ACOS_ASIN_ARG_TOO_LARGE ); + return (v < 0. ? M_PI : 0.); + } + return acos(v); +} + double +asqrt(double v) { return ((v <= 0) ? 0. : sqrt(v)); } + double +aatan2(double n, double d) { + return ((fabs(n) < ATOL && fabs(d) < ATOL) ? 0. : atan2(n,d)); +} diff --git a/src/adjlon.c b/src/adjlon.c deleted file mode 100644 index 784a90aa..00000000 --- a/src/adjlon.c +++ /dev/null @@ -1,20 +0,0 @@ -/* reduce argument to range +/- PI */ -#include -#include "projects.h" - -double adjlon (double lon) { - /* Let lon slightly overshoot, to avoid spurious sign switching at the date line */ - if (fabs (lon) < M_PI + 1e-12) - return lon; - - /* adjust to 0..2pi range */ - lon += M_PI; - - /* remove integral # of 'revolutions'*/ - lon -= M_TWOPI * floor(lon / M_TWOPI); - - /* adjust back to -pi..pi range */ - lon -= M_PI; - - return lon; -} diff --git a/src/adjlon.cpp b/src/adjlon.cpp new file mode 100644 index 00000000..784a90aa --- /dev/null +++ b/src/adjlon.cpp @@ -0,0 +1,20 @@ +/* reduce argument to range +/- PI */ +#include +#include "projects.h" + +double adjlon (double lon) { + /* Let lon slightly overshoot, to avoid spurious sign switching at the date line */ + if (fabs (lon) < M_PI + 1e-12) + return lon; + + /* adjust to 0..2pi range */ + lon += M_PI; + + /* remove integral # of 'revolutions'*/ + lon -= M_TWOPI * floor(lon / M_TWOPI); + + /* adjust back to -pi..pi range */ + lon -= M_PI; + + return lon; +} diff --git a/src/bch2bps.c b/src/bch2bps.c deleted file mode 100644 index 3ee56993..00000000 --- a/src/bch2bps.c +++ /dev/null @@ -1,152 +0,0 @@ -/* convert bivariate w Chebyshev series to w Power series */ -#include "projects.h" -/* basic support procedures */ - static void /* clear vector to zero */ -clear(projUV *p, int n) { static const projUV c = {0., 0.}; while (n--) *p++ = c; } - static void /* clear matrix rows to zero */ -bclear(projUV **p, int n, int m) { while (n--) clear(*p++, m); } - static void /* move vector */ -bmove(projUV *a, projUV *b, int n) { while (n--) *a++ = *b++; } - static void /* a <- m * b - c */ -submop(projUV *a, double m, projUV *b, projUV *c, int n) { - while (n--) { - a->u = m * b->u - c->u; - a++->v = m * b++->v - c++->v; - } -} - static void /* a <- b - c */ -subop(projUV *a, projUV *b, projUV *c, int n) { - while (n--) { - a->u = b->u - c->u; - a++->v = b++->v - c++->v; - } -} - static void /* multiply vector a by scalar m */ -dmult(projUV *a, double m, int n) { while(n--) { a->u *= m; a->v *= m; ++a; } } - static void /* row adjust a[] <- a[] - m * b[] */ -dadd(projUV *a, projUV *b, double m, int n) { - while(n--) { - a->u -= m * b->u; - a++->v -= m * b++->v; - } -} - static int /* convert row to power series */ -rows(projUV *c, projUV *d, int n) { - projUV sv, *dd; - int j, k; - - dd = (projUV *)vector1(n-1, sizeof(projUV)); - if (!dd) - return 0; - sv.u = sv.v = 0.; - for (j = 0; j < n; ++j) d[j] = dd[j] = sv; - d[0] = c[n-1]; - for (j = n-2; j >= 1; --j) { - for (k = n-j; k >= 1; --k) { - sv = d[k]; - d[k].u = 2. * d[k-1].u - dd[k].u; - d[k].v = 2. * d[k-1].v - dd[k].v; - dd[k] = sv; - } - sv = d[0]; - d[0].u = -dd[0].u + c[j].u; - d[0].v = -dd[0].v + c[j].v; - dd[0] = sv; - } - for (j = n-1; j >= 1; --j) { - d[j].u = d[j-1].u - dd[j].u; - d[j].v = d[j-1].v - dd[j].v; - } - d[0].u = -dd[0].u + .5 * c[0].u; - d[0].v = -dd[0].v + .5 * c[0].v; - pj_dalloc(dd); - return 1; -} - static int /* convert columns to power series */ -cols(projUV **c, projUV **d, int nu, int nv) { - projUV *sv, **dd; - int j, k; - - dd = (projUV **)vector2(nu, nv, sizeof(projUV)); - if (!dd) - return 0; - sv = (projUV *)vector1(nv, sizeof(projUV)); - if (!sv) { - freev2((void **)dd, nu); - return 0; - } - bclear(d, nu, nv); - bclear(dd, nu, nv); - bmove(d[0], c[nu-1], nv); - for (j = nu-2; j >= 1; --j) { - for (k = nu-j; k >= 1; --k) { - bmove(sv, d[k], nv); - submop(d[k], 2., d[k-1], dd[k], nv); - bmove(dd[k], sv, nv); - } - bmove(sv, d[0], nv); - subop(d[0], c[j], dd[0], nv); - bmove(dd[0], sv, nv); - } - for (j = nu-1; j >= 1; --j) - subop(d[j], d[j-1], dd[j], nv); - submop(d[0], .5, c[0], dd[0], nv); - freev2((void **) dd, nu); - pj_dalloc(sv); - return 1; -} - static void /* row adjust for range -1 to 1 to a to b */ -rowshft(double a, double b, projUV *d, int n) { - int k, j; - double fac, cnst; - - cnst = 2. / (b - a); - fac = cnst; - for (j = 1; j < n; ++j) { - d[j].u *= fac; - d[j].v *= fac; - fac *= cnst; - } - cnst = .5 * (a + b); - for (j = 0; j <= n-2; ++j) - for (k = n - 2; k >= j; --k) { - d[k].u -= cnst * d[k+1].u; - d[k].v -= cnst * d[k+1].v; - } -} - static void /* column adjust for range -1 to 1 to a to b */ -colshft(double a, double b, projUV **d, int n, int m) { - int k, j; - double fac, cnst; - - cnst = 2. / (b - a); - fac = cnst; - for (j = 1; j < n; ++j) { - dmult(d[j], fac, m); - fac *= cnst; - } - cnst = .5 * (a + b); - for (j = 0; j <= n-2; ++j) - for (k = n - 2; k >= j; --k) - dadd(d[k], d[k+1], cnst, m); -} - int /* entry point */ -bch2bps(projUV a, projUV b, projUV **c, int nu, int nv) { - projUV **d; - int i; - - if (nu < 1 || nv < 1 || !(d = (projUV **)vector2(nu, nv, sizeof(projUV)))) - return 0; - /* do rows to power series */ - for (i = 0; i < nu; ++i) { - if (!rows(c[i], d[i], nv)) - return 0; - rowshft(a.v, b.v, d[i], nv); - } - /* do columns to power series */ - if (!cols(d, c, nu, nv)) - return 0; - colshft(a.u, b.u, c, nu, nv); - freev2((void **) d, nu); - return 1; -} diff --git a/src/bch2bps.cpp b/src/bch2bps.cpp new file mode 100644 index 00000000..3ee56993 --- /dev/null +++ b/src/bch2bps.cpp @@ -0,0 +1,152 @@ +/* convert bivariate w Chebyshev series to w Power series */ +#include "projects.h" +/* basic support procedures */ + static void /* clear vector to zero */ +clear(projUV *p, int n) { static const projUV c = {0., 0.}; while (n--) *p++ = c; } + static void /* clear matrix rows to zero */ +bclear(projUV **p, int n, int m) { while (n--) clear(*p++, m); } + static void /* move vector */ +bmove(projUV *a, projUV *b, int n) { while (n--) *a++ = *b++; } + static void /* a <- m * b - c */ +submop(projUV *a, double m, projUV *b, projUV *c, int n) { + while (n--) { + a->u = m * b->u - c->u; + a++->v = m * b++->v - c++->v; + } +} + static void /* a <- b - c */ +subop(projUV *a, projUV *b, projUV *c, int n) { + while (n--) { + a->u = b->u - c->u; + a++->v = b++->v - c++->v; + } +} + static void /* multiply vector a by scalar m */ +dmult(projUV *a, double m, int n) { while(n--) { a->u *= m; a->v *= m; ++a; } } + static void /* row adjust a[] <- a[] - m * b[] */ +dadd(projUV *a, projUV *b, double m, int n) { + while(n--) { + a->u -= m * b->u; + a++->v -= m * b++->v; + } +} + static int /* convert row to power series */ +rows(projUV *c, projUV *d, int n) { + projUV sv, *dd; + int j, k; + + dd = (projUV *)vector1(n-1, sizeof(projUV)); + if (!dd) + return 0; + sv.u = sv.v = 0.; + for (j = 0; j < n; ++j) d[j] = dd[j] = sv; + d[0] = c[n-1]; + for (j = n-2; j >= 1; --j) { + for (k = n-j; k >= 1; --k) { + sv = d[k]; + d[k].u = 2. * d[k-1].u - dd[k].u; + d[k].v = 2. * d[k-1].v - dd[k].v; + dd[k] = sv; + } + sv = d[0]; + d[0].u = -dd[0].u + c[j].u; + d[0].v = -dd[0].v + c[j].v; + dd[0] = sv; + } + for (j = n-1; j >= 1; --j) { + d[j].u = d[j-1].u - dd[j].u; + d[j].v = d[j-1].v - dd[j].v; + } + d[0].u = -dd[0].u + .5 * c[0].u; + d[0].v = -dd[0].v + .5 * c[0].v; + pj_dalloc(dd); + return 1; +} + static int /* convert columns to power series */ +cols(projUV **c, projUV **d, int nu, int nv) { + projUV *sv, **dd; + int j, k; + + dd = (projUV **)vector2(nu, nv, sizeof(projUV)); + if (!dd) + return 0; + sv = (projUV *)vector1(nv, sizeof(projUV)); + if (!sv) { + freev2((void **)dd, nu); + return 0; + } + bclear(d, nu, nv); + bclear(dd, nu, nv); + bmove(d[0], c[nu-1], nv); + for (j = nu-2; j >= 1; --j) { + for (k = nu-j; k >= 1; --k) { + bmove(sv, d[k], nv); + submop(d[k], 2., d[k-1], dd[k], nv); + bmove(dd[k], sv, nv); + } + bmove(sv, d[0], nv); + subop(d[0], c[j], dd[0], nv); + bmove(dd[0], sv, nv); + } + for (j = nu-1; j >= 1; --j) + subop(d[j], d[j-1], dd[j], nv); + submop(d[0], .5, c[0], dd[0], nv); + freev2((void **) dd, nu); + pj_dalloc(sv); + return 1; +} + static void /* row adjust for range -1 to 1 to a to b */ +rowshft(double a, double b, projUV *d, int n) { + int k, j; + double fac, cnst; + + cnst = 2. / (b - a); + fac = cnst; + for (j = 1; j < n; ++j) { + d[j].u *= fac; + d[j].v *= fac; + fac *= cnst; + } + cnst = .5 * (a + b); + for (j = 0; j <= n-2; ++j) + for (k = n - 2; k >= j; --k) { + d[k].u -= cnst * d[k+1].u; + d[k].v -= cnst * d[k+1].v; + } +} + static void /* column adjust for range -1 to 1 to a to b */ +colshft(double a, double b, projUV **d, int n, int m) { + int k, j; + double fac, cnst; + + cnst = 2. / (b - a); + fac = cnst; + for (j = 1; j < n; ++j) { + dmult(d[j], fac, m); + fac *= cnst; + } + cnst = .5 * (a + b); + for (j = 0; j <= n-2; ++j) + for (k = n - 2; k >= j; --k) + dadd(d[k], d[k+1], cnst, m); +} + int /* entry point */ +bch2bps(projUV a, projUV b, projUV **c, int nu, int nv) { + projUV **d; + int i; + + if (nu < 1 || nv < 1 || !(d = (projUV **)vector2(nu, nv, sizeof(projUV)))) + return 0; + /* do rows to power series */ + for (i = 0; i < nu; ++i) { + if (!rows(c[i], d[i], nv)) + return 0; + rowshft(a.v, b.v, d[i], nv); + } + /* do columns to power series */ + if (!cols(d, c, nu, nv)) + return 0; + colshft(a.u, b.u, c, nu, nv); + freev2((void **) d, nu); + return 1; +} diff --git a/src/bchgen.c b/src/bchgen.c deleted file mode 100644 index c129f0c1..00000000 --- a/src/bchgen.c +++ /dev/null @@ -1,58 +0,0 @@ -/* generate double bivariate Chebychev polynomial */ -#include "projects.h" - int -bchgen(projUV a, projUV b, int nu, int nv, projUV **f, projUV(*func)(projUV)) { - int i, j, k; - projUV arg, *t, bma, bpa, *c; - double d, fac; - - bma.u = 0.5 * (b.u - a.u); bma.v = 0.5 * (b.v - a.v); - bpa.u = 0.5 * (b.u + a.u); bpa.v = 0.5 * (b.v + a.v); - for ( i = 0; i < nu; ++i) { - arg.u = cos(M_PI * (i + 0.5) / nu) * bma.u + bpa.u; - for ( j = 0; j < nv; ++j) { - arg.v = cos(M_PI * (j + 0.5) / nv) * bma.v + bpa.v; - f[i][j] = (*func)(arg); - if ((f[i][j]).u == HUGE_VAL) - return(1); - } - } - if (!(c = (projUV *) vector1(nu, sizeof(projUV)))) return 1; - fac = 2. / nu; - for ( j = 0; j < nv ; ++j) { - for ( i = 0; i < nu; ++i) { - arg.u = arg.v = 0.; - for (k = 0; k < nu; ++k) { - d = cos(M_PI * i * (k + .5) / nu); - arg.u += f[k][j].u * d; - arg.v += f[k][j].v * d; - } - arg.u *= fac; - arg.v *= fac; - c[i] = arg; - } - for (i = 0; i < nu; ++i) - f[i][j] = c[i]; - } - pj_dalloc(c); - if (!(c = (projUV*) vector1(nv, sizeof(projUV)))) return 1; - fac = 2. / nv; - for ( i = 0; i < nu; ++i) { - t = f[i]; - for (j = 0; j < nv; ++j) { - arg.u = arg.v = 0.; - for (k = 0; k < nv; ++k) { - d = cos(M_PI * j * (k + .5) / nv); - arg.u += t[k].u * d; - arg.v += t[k].v * d; - } - arg.u *= fac; - arg.v *= fac; - c[j] = arg; - } - f[i] = c; - c = t; - } - pj_dalloc(c); - return(0); -} diff --git a/src/bchgen.cpp b/src/bchgen.cpp new file mode 100644 index 00000000..c129f0c1 --- /dev/null +++ b/src/bchgen.cpp @@ -0,0 +1,58 @@ +/* generate double bivariate Chebychev polynomial */ +#include "projects.h" + int +bchgen(projUV a, projUV b, int nu, int nv, projUV **f, projUV(*func)(projUV)) { + int i, j, k; + projUV arg, *t, bma, bpa, *c; + double d, fac; + + bma.u = 0.5 * (b.u - a.u); bma.v = 0.5 * (b.v - a.v); + bpa.u = 0.5 * (b.u + a.u); bpa.v = 0.5 * (b.v + a.v); + for ( i = 0; i < nu; ++i) { + arg.u = cos(M_PI * (i + 0.5) / nu) * bma.u + bpa.u; + for ( j = 0; j < nv; ++j) { + arg.v = cos(M_PI * (j + 0.5) / nv) * bma.v + bpa.v; + f[i][j] = (*func)(arg); + if ((f[i][j]).u == HUGE_VAL) + return(1); + } + } + if (!(c = (projUV *) vector1(nu, sizeof(projUV)))) return 1; + fac = 2. / nu; + for ( j = 0; j < nv ; ++j) { + for ( i = 0; i < nu; ++i) { + arg.u = arg.v = 0.; + for (k = 0; k < nu; ++k) { + d = cos(M_PI * i * (k + .5) / nu); + arg.u += f[k][j].u * d; + arg.v += f[k][j].v * d; + } + arg.u *= fac; + arg.v *= fac; + c[i] = arg; + } + for (i = 0; i < nu; ++i) + f[i][j] = c[i]; + } + pj_dalloc(c); + if (!(c = (projUV*) vector1(nv, sizeof(projUV)))) return 1; + fac = 2. / nv; + for ( i = 0; i < nu; ++i) { + t = f[i]; + for (j = 0; j < nv; ++j) { + arg.u = arg.v = 0.; + for (k = 0; k < nv; ++k) { + d = cos(M_PI * j * (k + .5) / nv); + arg.u += t[k].u * d; + arg.v += t[k].v * d; + } + arg.u *= fac; + arg.v *= fac; + c[j] = arg; + } + f[i] = c; + c = t; + } + pj_dalloc(c); + return(0); +} diff --git a/src/bin_cct.cmake b/src/bin_cct.cmake index 37752217..11643bca 100644 --- a/src/bin_cct.cmake +++ b/src/bin_cct.cmake @@ -1,4 +1,4 @@ -set(CCT_SRC cct.c proj_strtod.c proj_strtod.h) +set(CCT_SRC cct.cpp proj_strtod.cpp proj_strtod.h) set(CCT_INCLUDE optargpm.h) source_group("Source Files\\Bin" FILES ${CCT_SRC}) diff --git a/src/bin_cs2cs.cmake b/src/bin_cs2cs.cmake index 9c013dcc..70d3b04d 100644 --- a/src/bin_cs2cs.cmake +++ b/src/bin_cs2cs.cmake @@ -1,6 +1,6 @@ set(CS2CS_SRC cs2cs.cpp - gen_cheb.c - p_series.c) + gen_cheb.cpp + p_series.cpp) source_group("Source Files\\Bin" FILES ${CS2CS_SRC}) diff --git a/src/bin_geod.cmake b/src/bin_geod.cmake index 1f9ee788..fe0e5c08 100644 --- a/src/bin_geod.cmake +++ b/src/bin_geod.cmake @@ -1,5 +1,5 @@ -set(GEOD_SRC geod.c - geod_set.c geod_interface.c ) +set(GEOD_SRC geod.cpp + geod_set.cpp geod_interface.cpp ) set(GEOD_INCLUDE geod_interface.h) source_group("Source Files\\Bin" FILES ${GEOD_SRC} ${GEOD_INCLUDE}) diff --git a/src/bin_geodtest.cmake b/src/bin_geodtest.cmake index 743c630e..bfd6cf99 100644 --- a/src/bin_geodtest.cmake +++ b/src/bin_geodtest.cmake @@ -1,4 +1,4 @@ -set(GEODTEST_SRC geodtest.c ) +set(GEODTEST_SRC geodtest.cpp ) set(GEODTEST_INCLUDE) source_group("Source Files\\Bin" FILES ${GEODTEST_SRC} ${GEODTEST_INCLUDE}) diff --git a/src/bin_gie.cmake b/src/bin_gie.cmake index 596592fa..f59319fc 100644 --- a/src/bin_gie.cmake +++ b/src/bin_gie.cmake @@ -1,4 +1,4 @@ -set(GIE_SRC gie.c proj_strtod.c proj_strtod.h) +set(GIE_SRC gie.cpp proj_strtod.cpp proj_strtod.h) set(GIE_INCLUDE optargpm.h) source_group("Source Files\\Bin" FILES ${GIE_SRC}) diff --git a/src/bin_nad2bin.cmake b/src/bin_nad2bin.cmake index 0d0fd473..3e1fa422 100644 --- a/src/bin_nad2bin.cmake +++ b/src/bin_nad2bin.cmake @@ -3,7 +3,7 @@ if(WIN32 AND BUILD_LIBPROJ_SHARED) endif(WIN32 AND BUILD_LIBPROJ_SHARED) -set(NAD2BIN_SRC nad2bin.c) +set(NAD2BIN_SRC nad2bin.cpp) source_group("Source Files\\Bin" FILES ${NAD2BIN_SRC}) #Executable diff --git a/src/bin_proj.cmake b/src/bin_proj.cmake index fb7c885d..5911aa50 100644 --- a/src/bin_proj.cmake +++ b/src/bin_proj.cmake @@ -1,6 +1,6 @@ -set(PROJ_SRC proj.c - gen_cheb.c - p_series.c) +set(PROJ_SRC proj.cpp + gen_cheb.cpp + p_series.cpp) source_group("Source Files\\Bin" FILES ${PROJ_SRC}) diff --git a/src/biveval.c b/src/biveval.c deleted file mode 100644 index 05e83e21..00000000 --- a/src/biveval.c +++ /dev/null @@ -1,87 +0,0 @@ -/* procedures for evaluating Tseries */ -# include "projects.h" -# define NEAR_ONE 1.00001 -static double ceval(struct PW_COEF *C, int n, projUV w, projUV w2) { - double d=0, dd=0, vd, vdd, tmp, *c; - int j; - - for (C += n ; n-- ; --C ) { - if ( (j = C->m) != 0) { - vd = vdd = 0.; - for (c = C->c + --j; j ; --j ) { - vd = w2.v * (tmp = vd) - vdd + *c--; - vdd = tmp; - } - d = w2.u * (tmp = d) - dd + w.v * vd - vdd + 0.5 * *c; - } else - d = w2.u * (tmp = d) - dd; - dd = tmp; - } - if ( (j = C->m) != 0 ) { - vd = vdd = 0.; - for (c = C->c + --j; j ; --j ) { - vd = w2.v * (tmp = vd) - vdd + *c--; - vdd = tmp; - } - return (w.u * d - dd + 0.5 * ( w.v * vd - vdd + 0.5 * *c )); - } else - return (w.u * d - dd); -} - -projUV /* bivariate Chebyshev polynomial entry point */ -bcheval(projUV in, Tseries *T) { - projUV w2, w; - projUV out; - /* scale to +-1 */ - w.u = ( in.u + in.u - T->a.u ) * T->b.u; - w.v = ( in.v + in.v - T->a.v ) * T->b.v; - if (fabs(w.u) > NEAR_ONE || fabs(w.v) > NEAR_ONE) { - out.u = out.v = HUGE_VAL; - pj_errno = PJD_ERR_TCHEBY_VAL_OUT_OF_RANGE; - } else { /* double evaluation */ - w2.u = w.u + w.u; - w2.v = w.v + w.v; - out.u = ceval(T->cu, T->mu, w, w2); - out.v = ceval(T->cv, T->mv, w, w2); - } - return out; -} - -projUV /* bivariate power polynomial entry point */ -bpseval(projUV in, Tseries *T) { - projUV out; - double *c, row; - int i, m; - - out.u = out.v = 0.; - for (i = T->mu; i >= 0; --i) { - row = 0.; - if ((m = T->cu[i].m) != 0) { - c = T->cu[i].c + m; - while (m--) - row = *--c + in.v * row; - } - out.u = row + in.u * out.u; - } - for (i = T->mv; i >= 0; --i) { - row = 0.; - if ((m = T->cv[i].m) != 0) { - c = T->cv[i].c + m; - while (m--) - row = *--c + in.v * row; - } - out.v = row + in.u * out.v; - } - return out; -} - -projUV /* general entry point selecting evaluation mode */ -biveval(projUV in, Tseries *T) { - - if (T->power) { - return bpseval(in, T); - } else { - return bcheval(in, T); - } -} - diff --git a/src/biveval.cpp b/src/biveval.cpp new file mode 100644 index 00000000..05e83e21 --- /dev/null +++ b/src/biveval.cpp @@ -0,0 +1,87 @@ +/* procedures for evaluating Tseries */ +# include "projects.h" +# define NEAR_ONE 1.00001 +static double ceval(struct PW_COEF *C, int n, projUV w, projUV w2) { + double d=0, dd=0, vd, vdd, tmp, *c; + int j; + + for (C += n ; n-- ; --C ) { + if ( (j = C->m) != 0) { + vd = vdd = 0.; + for (c = C->c + --j; j ; --j ) { + vd = w2.v * (tmp = vd) - vdd + *c--; + vdd = tmp; + } + d = w2.u * (tmp = d) - dd + w.v * vd - vdd + 0.5 * *c; + } else + d = w2.u * (tmp = d) - dd; + dd = tmp; + } + if ( (j = C->m) != 0 ) { + vd = vdd = 0.; + for (c = C->c + --j; j ; --j ) { + vd = w2.v * (tmp = vd) - vdd + *c--; + vdd = tmp; + } + return (w.u * d - dd + 0.5 * ( w.v * vd - vdd + 0.5 * *c )); + } else + return (w.u * d - dd); +} + +projUV /* bivariate Chebyshev polynomial entry point */ +bcheval(projUV in, Tseries *T) { + projUV w2, w; + projUV out; + /* scale to +-1 */ + w.u = ( in.u + in.u - T->a.u ) * T->b.u; + w.v = ( in.v + in.v - T->a.v ) * T->b.v; + if (fabs(w.u) > NEAR_ONE || fabs(w.v) > NEAR_ONE) { + out.u = out.v = HUGE_VAL; + pj_errno = PJD_ERR_TCHEBY_VAL_OUT_OF_RANGE; + } else { /* double evaluation */ + w2.u = w.u + w.u; + w2.v = w.v + w.v; + out.u = ceval(T->cu, T->mu, w, w2); + out.v = ceval(T->cv, T->mv, w, w2); + } + return out; +} + +projUV /* bivariate power polynomial entry point */ +bpseval(projUV in, Tseries *T) { + projUV out; + double *c, row; + int i, m; + + out.u = out.v = 0.; + for (i = T->mu; i >= 0; --i) { + row = 0.; + if ((m = T->cu[i].m) != 0) { + c = T->cu[i].c + m; + while (m--) + row = *--c + in.v * row; + } + out.u = row + in.u * out.u; + } + for (i = T->mv; i >= 0; --i) { + row = 0.; + if ((m = T->cv[i].m) != 0) { + c = T->cv[i].c + m; + while (m--) + row = *--c + in.v * row; + } + out.v = row + in.u * out.v; + } + return out; +} + +projUV /* general entry point selecting evaluation mode */ +biveval(projUV in, Tseries *T) { + + if (T->power) { + return bpseval(in, T); + } else { + return bcheval(in, T); + } +} + diff --git a/src/cct.c b/src/cct.c deleted file mode 100644 index 54734b98..00000000 --- a/src/cct.c +++ /dev/null @@ -1,472 +0,0 @@ -/*********************************************************************** - - 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 == NULL ) { - 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, direction = 1, 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", 0}; - const char *longkeys[] = { - "o=output", - "c=columns", - "d=decimals", - "z=height", - "t=time", - "s=skip-lines", - 0}; - - fout = stdout; - - o = opt_parse (argc, argv, "hvI", "cdozts", longflags, longkeys); - if (0==o) - return 0; - - if (opt_given (o, "h") || argc==1) { - printf (usage, o->progname); - return 0; - } - - direction = opt_given (o, "I")? -1: 1; - - verbose = MIN(opt_given (o, "v"), 3); /* log level can't be larger than 3 */ - proj_log_level (PJ_DEFAULT_CTX, 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 (0==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 (0, o->pargc, o->pargv); - if ((0==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==-1) { - /* 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 = 1; - - /* Allocate input buffer */ - buf = calloc (1, 10000); - if (0==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 (0==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/cct.cpp b/src/cct.cpp new file mode 100644 index 00000000..0493e721 --- /dev/null +++ b/src/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 == NULL ) { + 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", 0}; + const char *longkeys[] = { + "o=output", + "c=columns", + "d=decimals", + "z=height", + "t=time", + "s=skip-lines", + 0}; + + fout = stdout; + + o = opt_parse (argc, argv, "hvI", "cdozts", longflags, longkeys); + if (0==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 (0==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 (0, o->pargc, o->pargv); + if ((0==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 (0==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 (0==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/dmstor.c b/src/dmstor.c deleted file mode 100644 index ab8e33f4..00000000 --- a/src/dmstor.c +++ /dev/null @@ -1,121 +0,0 @@ -/* Convert DMS string to radians */ - -#include -#include -#include -#include - -#include "projects.h" - -static double proj_strtod(char *nptr, char **endptr); - -/* following should be sufficient for all but the ridiculous */ -#define MAX_WORK 64 - static const char -*sym = "NnEeSsWw"; - static const double -vm[] = { - DEG_TO_RAD, - .0002908882086657216, - .0000048481368110953599 -}; - double -dmstor(const char *is, char **rs) { - return dmstor_ctx( pj_get_default_ctx(), is, rs ); -} - - double -dmstor_ctx(projCtx ctx, const char *is, char **rs) { - int sign, n, nl; - char *p, *s, work[MAX_WORK]; - double v, tv; - - if (rs) - *rs = (char *)is; - /* copy sting into work space */ - while (isspace(sign = *is)) ++is; - n = MAX_WORK; - s = work; - p = (char *)is; - while (isgraph(*p) && --n) - *s++ = *p++; - *s = '\0'; - /* it is possible that a really odd input (like lots of leading - zeros) could be truncated in copying into work. But ... */ - sign = *(s = work); - if (sign == '+' || sign == '-') s++; - else sign = '+'; - v = 0.; - for (nl = 0 ; nl < 3 ; nl = n + 1 ) { - if (!(isdigit(*s) || *s == '.')) break; - if ((tv = proj_strtod(s, &s)) == HUGE_VAL) - return tv; - switch (*s) { - case 'D': case 'd': - n = 0; break; - case '\'': - n = 1; break; - case '"': - n = 2; break; - case 'r': case 'R': - if (nl) { - pj_ctx_set_errno( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); - return HUGE_VAL; - } - ++s; - v = tv; - goto skip; - default: - v += tv * vm[nl]; - skip: n = 4; - continue; - } - if (n < nl) { - pj_ctx_set_errno( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); - return HUGE_VAL; - } - v += tv * vm[n]; - ++s; - } - /* postfix sign */ - if (*s && (p = strchr(sym, *s))) { - sign = (p - sym) >= 4 ? '-' : '+'; - ++s; - } - if (sign == '-') - v = -v; - if (rs) /* return point of next char after valid string */ - *rs = (char *)is + (s - work); - return v; -} - -static double -proj_strtod(char *nptr, char **endptr) - -{ - char c, *cp = nptr; - double result; - - /* - * Scan for characters which cause problems with VC++ strtod() - */ - while ((c = *cp) != '\0') { - if (c == 'd' || c == 'D') { - - /* - * Found one, so NUL it out, call strtod(), - * then restore it and return - */ - *cp = '\0'; - result = strtod(nptr, endptr); - *cp = c; - return result; - } - ++cp; - } - - /* no offending characters, just handle normally */ - - return pj_strtod(nptr, endptr); -} - diff --git a/src/dmstor.cpp b/src/dmstor.cpp new file mode 100644 index 00000000..967a9f18 --- /dev/null +++ b/src/dmstor.cpp @@ -0,0 +1,122 @@ +/* Convert DMS string to radians */ + +#include +#include +#include +#include + +#include "projects.h" + +static double proj_strtod(char *nptr, char **endptr); + +/* following should be sufficient for all but the ridiculous */ +#define MAX_WORK 64 + static const char +*sym = "NnEeSsWw"; + static const double +vm[] = { + DEG_TO_RAD, + .0002908882086657216, + .0000048481368110953599 +}; + double +dmstor(const char *is, char **rs) { + return dmstor_ctx( pj_get_default_ctx(), is, rs ); +} + + double +dmstor_ctx(projCtx ctx, const char *is, char **rs) { + int sign, n, nl; + char *s, work[MAX_WORK]; + const char* p; + double v, tv; + + if (rs) + *rs = (char *)is; + /* copy sting into work space */ + while (isspace(sign = *is)) ++is; + n = MAX_WORK; + s = work; + p = (char *)is; + while (isgraph(*p) && --n) + *s++ = *p++; + *s = '\0'; + /* it is possible that a really odd input (like lots of leading + zeros) could be truncated in copying into work. But ... */ + sign = *(s = work); + if (sign == '+' || sign == '-') s++; + else sign = '+'; + v = 0.; + for (nl = 0 ; nl < 3 ; nl = n + 1 ) { + if (!(isdigit(*s) || *s == '.')) break; + if ((tv = proj_strtod(s, &s)) == HUGE_VAL) + return tv; + switch (*s) { + case 'D': case 'd': + n = 0; break; + case '\'': + n = 1; break; + case '"': + n = 2; break; + case 'r': case 'R': + if (nl) { + pj_ctx_set_errno( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); + return HUGE_VAL; + } + ++s; + v = tv; + goto skip; + default: + v += tv * vm[nl]; + skip: n = 4; + continue; + } + if (n < nl) { + pj_ctx_set_errno( ctx, PJD_ERR_WRONG_FORMAT_DMS_VALUE ); + return HUGE_VAL; + } + v += tv * vm[n]; + ++s; + } + /* postfix sign */ + if (*s && (p = strchr(sym, *s))) { + sign = (p - sym) >= 4 ? '-' : '+'; + ++s; + } + if (sign == '-') + v = -v; + if (rs) /* return point of next char after valid string */ + *rs = (char *)is + (s - work); + return v; +} + +static double +proj_strtod(char *nptr, char **endptr) + +{ + char c, *cp = nptr; + double result; + + /* + * Scan for characters which cause problems with VC++ strtod() + */ + while ((c = *cp) != '\0') { + if (c == 'd' || c == 'D') { + + /* + * Found one, so NUL it out, call strtod(), + * then restore it and return + */ + *cp = '\0'; + result = strtod(nptr, endptr); + *cp = c; + return result; + } + ++cp; + } + + /* no offending characters, just handle normally */ + + return pj_strtod(nptr, endptr); +} + diff --git a/src/emess.c b/src/emess.c deleted file mode 100644 index eb2ac9d6..00000000 --- a/src/emess.c +++ /dev/null @@ -1,68 +0,0 @@ -/* 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 != NULL) - (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 != NULL && *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/emess.cpp b/src/emess.cpp new file mode 100644 index 00000000..eb2ac9d6 --- /dev/null +++ b/src/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 != NULL) + (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 != NULL && *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/gen_cheb.c b/src/gen_cheb.c deleted file mode 100644 index ab16b409..00000000 --- a/src/gen_cheb.c +++ /dev/null @@ -1,78 +0,0 @@ -/* 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 - -/* FIXME: put the declaration in a header. Also used in proj.c */ -void gen_cheb(int inverse, projUV (*proj)(projUV), char *s, PJ *P, - int iargc, char **iargv); -extern void p_series(Tseries *, FILE *, char *); - -void gen_cheb(int inverse, projUV (*proj)(projUV), 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 *, char **); - - input = inverse ? strtod : dmstor; - 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(s, &s, 10); - if (*s == ',') if (*++s != ',') NU = strtol(s, &s, 10); - if (*s == ',') if (*++s != ',') NV = strtol(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/gen_cheb.cpp b/src/gen_cheb.cpp new file mode 100644 index 00000000..ab16b409 --- /dev/null +++ b/src/gen_cheb.cpp @@ -0,0 +1,78 @@ +/* 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 + +/* FIXME: put the declaration in a header. Also used in proj.c */ +void gen_cheb(int inverse, projUV (*proj)(projUV), char *s, PJ *P, + int iargc, char **iargv); +extern void p_series(Tseries *, FILE *, char *); + +void gen_cheb(int inverse, projUV (*proj)(projUV), 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 *, char **); + + input = inverse ? strtod : dmstor; + 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(s, &s, 10); + if (*s == ',') if (*++s != ',') NU = strtol(s, &s, 10); + if (*s == ',') if (*++s != ',') NV = strtol(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/geocent.c b/src/geocent.c deleted file mode 100644 index c023bdd3..00000000 --- a/src/geocent.c +++ /dev/null @@ -1,436 +0,0 @@ -/***************************************************************************/ -/* RSC IDENTIFIER: GEOCENTRIC - * - * ABSTRACT - * - * This component provides conversions between Geodetic coordinates (latitude, - * longitude in radians and height in meters) and Geocentric coordinates - * (X, Y, Z) in meters. - * - * ERROR HANDLING - * - * This component checks parameters for valid values. If an invalid value - * is found, the error code is combined with the current error code using - * the bitwise or. This combining allows multiple error codes to be - * returned. The possible error codes are: - * - * GEOCENT_NO_ERROR : No errors occurred in function - * GEOCENT_LAT_ERROR : Latitude out of valid range - * (-90 to 90 degrees) - * GEOCENT_LON_ERROR : Longitude out of valid range - * (-180 to 360 degrees) - * GEOCENT_A_ERROR : Semi-major axis lessthan or equal to zero - * GEOCENT_B_ERROR : Semi-minor axis lessthan or equal to zero - * GEOCENT_A_LESS_B_ERROR : Semi-major axis less than semi-minor axis - * - * - * REUSE NOTES - * - * GEOCENTRIC is intended for reuse by any application that performs - * coordinate conversions between geodetic coordinates and geocentric - * coordinates. - * - * - * REFERENCES - * - * An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion, - * Ralph Toms, February 1996 UCRL-JC-123138. - * - * Further information on GEOCENTRIC can be found in the Reuse Manual. - * - * GEOCENTRIC originated from : U.S. Army Topographic Engineering Center - * Geospatial Information Division - * 7701 Telegraph Road - * Alexandria, VA 22310-3864 - * - * LICENSES - * - * None apply to this component. - * - * RESTRICTIONS - * - * GEOCENTRIC has no restrictions. - * - * ENVIRONMENT - * - * GEOCENTRIC was tested and certified in the following environments: - * - * 1. Solaris 2.5 with GCC version 2.8.1 - * 2. Windows 95 with MS Visual C++ version 6 - * - * MODIFICATIONS - * - * Date Description - * ---- ----------- - * 25-02-97 Original Code - * - */ - - -/***************************************************************************/ -/* - * INCLUDES - */ -#include -#include "geocent.h" -/* - * math.h - is needed for calls to sin, cos, tan and sqrt. - * geocent.h - is needed for Error codes and prototype error checking. - */ - - -/***************************************************************************/ -/* - * DEFINES - */ -#define PI 3.14159265358979323e0 -#define PI_OVER_2 (PI / 2.0e0) -#define FALSE 0 -#define TRUE 1 -#define COS_67P5 0.38268343236508977 /* cosine of 67.5 degrees */ -#define AD_C 1.0026000 /* Toms region 1 constant */ - - -/***************************************************************************/ -/* - * FUNCTIONS - */ - - -long pj_Set_Geocentric_Parameters (GeocentricInfo *gi, double a, double b) - -{ /* BEGIN Set_Geocentric_Parameters */ -/* - * The function Set_Geocentric_Parameters receives the ellipsoid parameters - * as inputs and sets the corresponding state variables. - * - * a : Semi-major axis, in meters. (input) - * b : Semi-minor axis, in meters. (input) - */ - long Error_Code = GEOCENT_NO_ERROR; - - if (a <= 0.0) - Error_Code |= GEOCENT_A_ERROR; - if (b <= 0.0) - Error_Code |= GEOCENT_B_ERROR; - if (a < b) - Error_Code |= GEOCENT_A_LESS_B_ERROR; - if (!Error_Code) - { - gi->Geocent_a = a; - gi->Geocent_b = b; - gi->Geocent_a2 = a * a; - gi->Geocent_b2 = b * b; - gi->Geocent_e2 = (gi->Geocent_a2 - gi->Geocent_b2) / gi->Geocent_a2; - gi->Geocent_ep2 = (gi->Geocent_a2 - gi->Geocent_b2) / gi->Geocent_b2; - } - return (Error_Code); -} /* END OF Set_Geocentric_Parameters */ - - -void pj_Get_Geocentric_Parameters (GeocentricInfo *gi, - double *a, - double *b) -{ /* BEGIN Get_Geocentric_Parameters */ -/* - * The function Get_Geocentric_Parameters returns the ellipsoid parameters - * to be used in geocentric coordinate conversions. - * - * a : Semi-major axis, in meters. (output) - * b : Semi-minor axis, in meters. (output) - */ - - *a = gi->Geocent_a; - *b = gi->Geocent_b; -} /* END OF Get_Geocentric_Parameters */ - - -long pj_Convert_Geodetic_To_Geocentric (GeocentricInfo *gi, - double Latitude, - double Longitude, - double Height, - double *X, - double *Y, - double *Z) -{ /* BEGIN Convert_Geodetic_To_Geocentric */ -/* - * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates - * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z), - * according to the current ellipsoid parameters. - * - * Latitude : Geodetic latitude in radians (input) - * Longitude : Geodetic longitude in radians (input) - * Height : Geodetic height, in meters (input) - * X : Calculated Geocentric X coordinate, in meters (output) - * Y : Calculated Geocentric Y coordinate, in meters (output) - * Z : Calculated Geocentric Z coordinate, in meters (output) - * - */ - long Error_Code = GEOCENT_NO_ERROR; - double Rn; /* Earth radius at location */ - double Sin_Lat; /* sin(Latitude) */ - double Sin2_Lat; /* Square of sin(Latitude) */ - double Cos_Lat; /* cos(Latitude) */ - - /* - ** Don't blow up if Latitude is just a little out of the value - ** range as it may just be a rounding issue. Also removed longitude - ** test, it should be wrapped by cos() and sin(). NFW for PROJ.4, Sep/2001. - */ - if( Latitude < -PI_OVER_2 && Latitude > -1.001 * PI_OVER_2 ) - Latitude = -PI_OVER_2; - else if( Latitude > PI_OVER_2 && Latitude < 1.001 * PI_OVER_2 ) - Latitude = PI_OVER_2; - else if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) - { /* Latitude out of range */ - Error_Code |= GEOCENT_LAT_ERROR; - } - - if (!Error_Code) - { /* no errors */ - if (Longitude > PI) - Longitude -= (2*PI); - Sin_Lat = sin(Latitude); - Cos_Lat = cos(Latitude); - Sin2_Lat = Sin_Lat * Sin_Lat; - Rn = gi->Geocent_a / (sqrt(1.0e0 - gi->Geocent_e2 * Sin2_Lat)); - *X = (Rn + Height) * Cos_Lat * cos(Longitude); - *Y = (Rn + Height) * Cos_Lat * sin(Longitude); - *Z = ((Rn * (1 - gi->Geocent_e2)) + Height) * Sin_Lat; - - } - return (Error_Code); -} /* END OF Convert_Geodetic_To_Geocentric */ - -/* - * The function Convert_Geocentric_To_Geodetic converts geocentric - * coordinates (X, Y, Z) to geodetic coordinates (latitude, longitude, - * and height), according to the current ellipsoid parameters. - * - * X : Geocentric X coordinate, in meters. (input) - * Y : Geocentric Y coordinate, in meters. (input) - * Z : Geocentric Z coordinate, in meters. (input) - * Latitude : Calculated latitude value in radians. (output) - * Longitude : Calculated longitude value in radians. (output) - * Height : Calculated height value, in meters. (output) - */ - -#define USE_ITERATIVE_METHOD - -void pj_Convert_Geocentric_To_Geodetic (GeocentricInfo *gi, - double X, - double Y, - double Z, - double *Latitude, - double *Longitude, - double *Height) -{ /* BEGIN Convert_Geocentric_To_Geodetic */ -#if !defined(USE_ITERATIVE_METHOD) -/* - * The method used here is derived from 'An Improved Algorithm for - * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996 - */ - -/* Note: Variable names follow the notation used in Toms, Feb 1996 */ - - double W; /* distance from Z axis */ - double W2; /* square of distance from Z axis */ - double T0; /* initial estimate of vertical component */ - double T1; /* corrected estimate of vertical component */ - double S0; /* initial estimate of horizontal component */ - double S1; /* corrected estimate of horizontal component */ - double Sin_B0; /* sin(B0), B0 is estimate of Bowring aux variable */ - double Sin3_B0; /* cube of sin(B0) */ - double Cos_B0; /* cos(B0) */ - double Sin_p1; /* sin(phi1), phi1 is estimated latitude */ - double Cos_p1; /* cos(phi1) */ - double Rn; /* Earth radius at location */ - double Sum; /* numerator of cos(phi1) */ - int At_Pole; /* indicates location is in polar region */ - - At_Pole = FALSE; - if (X != 0.0) - { - *Longitude = atan2(Y,X); - } - else - { - if (Y > 0) - { - *Longitude = PI_OVER_2; - } - else if (Y < 0) - { - *Longitude = -PI_OVER_2; - } - else - { - At_Pole = TRUE; - *Longitude = 0.0; - if (Z > 0.0) - { /* north pole */ - *Latitude = PI_OVER_2; - } - else if (Z < 0.0) - { /* south pole */ - *Latitude = -PI_OVER_2; - } - else - { /* center of earth */ - *Latitude = PI_OVER_2; - *Height = -Geocent_b; - return; - } - } - } - W2 = X*X + Y*Y; - W = sqrt(W2); - T0 = Z * AD_C; - S0 = sqrt(T0 * T0 + W2); - Sin_B0 = T0 / S0; - Cos_B0 = W / S0; - Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0; - T1 = Z + gi->Geocent_b * gi->Geocent_ep2 * Sin3_B0; - Sum = W - gi->Geocent_a * gi->Geocent_e2 * Cos_B0 * Cos_B0 * Cos_B0; - S1 = sqrt(T1*T1 + Sum * Sum); - Sin_p1 = T1 / S1; - Cos_p1 = Sum / S1; - Rn = gi->Geocent_a / sqrt(1.0 - gi->Geocent_e2 * Sin_p1 * Sin_p1); - if (Cos_p1 >= COS_67P5) - { - *Height = W / Cos_p1 - Rn; - } - else if (Cos_p1 <= -COS_67P5) - { - *Height = W / -Cos_p1 - Rn; - } - else - { - *Height = Z / Sin_p1 + Rn * (gi->Geocent_e2 - 1.0); - } - if (At_Pole == FALSE) - { - *Latitude = atan(Sin_p1 / Cos_p1); - } -#else /* defined(USE_ITERATIVE_METHOD) */ -/* -* Reference... -* ============ -* Wenzel, H.-G.(1985): Hochauflösende Kugelfunktionsmodelle für -* das Gravitationspotential der Erde. Wiss. Arb. Univ. Hannover -* Nr. 137, p. 130-131. - -* Programmed by GGA- Leibniz-Institute of Applied Geophysics -* Stilleweg 2 -* D-30655 Hannover -* Federal Republic of Germany -* Internet: www.gga-hannover.de -* -* Hannover, March 1999, April 2004. -* see also: comments in statements -* remarks: -* Mathematically exact and because of symmetry of rotation-ellipsoid, -* each point (X,Y,Z) has at least two solutions (Latitude1,Longitude1,Height1) and -* (Latitude2,Longitude2,Height2). Is point=(0.,0.,Z) (P=0.), so you get even -* four solutions, every two symmetrical to the semi-minor axis. -* Here Height1 and Height2 have at least a difference in order of -* radius of curvature (e.g. (0,0,b)=> (90.,0.,0.) or (-90.,0.,-2b); -* (a+100.)*(sqrt(2.)/2.,sqrt(2.)/2.,0.) => (0.,45.,100.) or -* (0.,225.,-(2a+100.))). -* The algorithm always computes (Latitude,Longitude) with smallest |Height|. -* For normal computations, that means |Height|<10000.m, algorithm normally -* converges after to 2-3 steps!!! -* But if |Height| has the amount of length of ellipsoid's axis -* (e.g. -6300000.m), algorithm needs about 15 steps. -*/ - -/* local definitions and variables */ -/* end-criterium of loop, accuracy of sin(Latitude) */ -#define genau 1.E-12 -#define genau2 (genau*genau) -#define maxiter 30 - - double P; /* distance between semi-minor axis and location */ - double RR; /* distance between center and location */ - double CT; /* sin of geocentric latitude */ - double ST; /* cos of geocentric latitude */ - double RX; - double RK; - double RN; /* Earth radius at location */ - double CPHI0; /* cos of start or old geodetic latitude in iterations */ - double SPHI0; /* sin of start or old geodetic latitude in iterations */ - double CPHI; /* cos of searched geodetic latitude */ - double SPHI; /* sin of searched geodetic latitude */ - double SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ - int iter; /* # of continuous iteration, max. 30 is always enough (s.a.) */ - - P = sqrt(X*X+Y*Y); - RR = sqrt(X*X+Y*Y+Z*Z); - -/* special cases for latitude and longitude */ - if (P/gi->Geocent_a < genau) { - -/* special case, if P=0. (X=0., Y=0.) */ - *Longitude = 0.; - -/* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis - * of ellipsoid (=center of mass), Latitude becomes PI/2 */ - if (RR/gi->Geocent_a < genau) { - *Latitude = PI_OVER_2; - *Height = -gi->Geocent_b; - return ; - - } - } - else { -/* ellipsoidal (geodetic) longitude - * interval: -PI < Longitude <= +PI */ - *Longitude=atan2(Y,X); - } - -/* -------------------------------------------------------------- - * Following iterative algorithm was developed by - * "Institut für Erdmessung", University of Hannover, July 1988. - * Internet: www.ife.uni-hannover.de - * Iterative computation of CPHI,SPHI and Height. - * Iteration of CPHI and SPHI to 10**-12 radian resp. - * 2*10**-7 arcsec. - * -------------------------------------------------------------- - */ - CT = Z/RR; - ST = P/RR; - RX = 1.0/sqrt(1.0-gi->Geocent_e2*(2.0-gi->Geocent_e2)*ST*ST); - CPHI0 = ST*(1.0-gi->Geocent_e2)*RX; - SPHI0 = CT*RX; - iter = 0; - -/* loop to find sin(Latitude) resp. Latitude - * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ - do - { - iter++; - RN = gi->Geocent_a/sqrt(1.0-gi->Geocent_e2*SPHI0*SPHI0); - -/* ellipsoidal (geodetic) height */ - *Height = P*CPHI0+Z*SPHI0-RN*(1.0-gi->Geocent_e2*SPHI0*SPHI0); - - /* avoid zero division */ - if (RN+*Height==0.0) { - *Latitude = 0.0; - return; - } - RK = gi->Geocent_e2*RN/(RN+*Height); - RX = 1.0/sqrt(1.0-RK*(2.0-RK)*ST*ST); - CPHI = ST*(1.0-RK)*RX; - SPHI = CT*RX; - SDPHI = SPHI*CPHI0-CPHI*SPHI0; - CPHI0 = CPHI; - SPHI0 = SPHI; - } - while (SDPHI*SDPHI > genau2 && iter < maxiter); - -/* ellipsoidal (geodetic) latitude */ - *Latitude=atan2(SPHI, fabs(CPHI)); - -#endif /* defined(USE_ITERATIVE_METHOD) */ -} /* END OF Convert_Geocentric_To_Geodetic */ diff --git a/src/geocent.cpp b/src/geocent.cpp new file mode 100644 index 00000000..c023bdd3 --- /dev/null +++ b/src/geocent.cpp @@ -0,0 +1,436 @@ +/***************************************************************************/ +/* RSC IDENTIFIER: GEOCENTRIC + * + * ABSTRACT + * + * This component provides conversions between Geodetic coordinates (latitude, + * longitude in radians and height in meters) and Geocentric coordinates + * (X, Y, Z) in meters. + * + * ERROR HANDLING + * + * This component checks parameters for valid values. If an invalid value + * is found, the error code is combined with the current error code using + * the bitwise or. This combining allows multiple error codes to be + * returned. The possible error codes are: + * + * GEOCENT_NO_ERROR : No errors occurred in function + * GEOCENT_LAT_ERROR : Latitude out of valid range + * (-90 to 90 degrees) + * GEOCENT_LON_ERROR : Longitude out of valid range + * (-180 to 360 degrees) + * GEOCENT_A_ERROR : Semi-major axis lessthan or equal to zero + * GEOCENT_B_ERROR : Semi-minor axis lessthan or equal to zero + * GEOCENT_A_LESS_B_ERROR : Semi-major axis less than semi-minor axis + * + * + * REUSE NOTES + * + * GEOCENTRIC is intended for reuse by any application that performs + * coordinate conversions between geodetic coordinates and geocentric + * coordinates. + * + * + * REFERENCES + * + * An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion, + * Ralph Toms, February 1996 UCRL-JC-123138. + * + * Further information on GEOCENTRIC can be found in the Reuse Manual. + * + * GEOCENTRIC originated from : U.S. Army Topographic Engineering Center + * Geospatial Information Division + * 7701 Telegraph Road + * Alexandria, VA 22310-3864 + * + * LICENSES + * + * None apply to this component. + * + * RESTRICTIONS + * + * GEOCENTRIC has no restrictions. + * + * ENVIRONMENT + * + * GEOCENTRIC was tested and certified in the following environments: + * + * 1. Solaris 2.5 with GCC version 2.8.1 + * 2. Windows 95 with MS Visual C++ version 6 + * + * MODIFICATIONS + * + * Date Description + * ---- ----------- + * 25-02-97 Original Code + * + */ + + +/***************************************************************************/ +/* + * INCLUDES + */ +#include +#include "geocent.h" +/* + * math.h - is needed for calls to sin, cos, tan and sqrt. + * geocent.h - is needed for Error codes and prototype error checking. + */ + + +/***************************************************************************/ +/* + * DEFINES + */ +#define PI 3.14159265358979323e0 +#define PI_OVER_2 (PI / 2.0e0) +#define FALSE 0 +#define TRUE 1 +#define COS_67P5 0.38268343236508977 /* cosine of 67.5 degrees */ +#define AD_C 1.0026000 /* Toms region 1 constant */ + + +/***************************************************************************/ +/* + * FUNCTIONS + */ + + +long pj_Set_Geocentric_Parameters (GeocentricInfo *gi, double a, double b) + +{ /* BEGIN Set_Geocentric_Parameters */ +/* + * The function Set_Geocentric_Parameters receives the ellipsoid parameters + * as inputs and sets the corresponding state variables. + * + * a : Semi-major axis, in meters. (input) + * b : Semi-minor axis, in meters. (input) + */ + long Error_Code = GEOCENT_NO_ERROR; + + if (a <= 0.0) + Error_Code |= GEOCENT_A_ERROR; + if (b <= 0.0) + Error_Code |= GEOCENT_B_ERROR; + if (a < b) + Error_Code |= GEOCENT_A_LESS_B_ERROR; + if (!Error_Code) + { + gi->Geocent_a = a; + gi->Geocent_b = b; + gi->Geocent_a2 = a * a; + gi->Geocent_b2 = b * b; + gi->Geocent_e2 = (gi->Geocent_a2 - gi->Geocent_b2) / gi->Geocent_a2; + gi->Geocent_ep2 = (gi->Geocent_a2 - gi->Geocent_b2) / gi->Geocent_b2; + } + return (Error_Code); +} /* END OF Set_Geocentric_Parameters */ + + +void pj_Get_Geocentric_Parameters (GeocentricInfo *gi, + double *a, + double *b) +{ /* BEGIN Get_Geocentric_Parameters */ +/* + * The function Get_Geocentric_Parameters returns the ellipsoid parameters + * to be used in geocentric coordinate conversions. + * + * a : Semi-major axis, in meters. (output) + * b : Semi-minor axis, in meters. (output) + */ + + *a = gi->Geocent_a; + *b = gi->Geocent_b; +} /* END OF Get_Geocentric_Parameters */ + + +long pj_Convert_Geodetic_To_Geocentric (GeocentricInfo *gi, + double Latitude, + double Longitude, + double Height, + double *X, + double *Y, + double *Z) +{ /* BEGIN Convert_Geodetic_To_Geocentric */ +/* + * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates + * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z), + * according to the current ellipsoid parameters. + * + * Latitude : Geodetic latitude in radians (input) + * Longitude : Geodetic longitude in radians (input) + * Height : Geodetic height, in meters (input) + * X : Calculated Geocentric X coordinate, in meters (output) + * Y : Calculated Geocentric Y coordinate, in meters (output) + * Z : Calculated Geocentric Z coordinate, in meters (output) + * + */ + long Error_Code = GEOCENT_NO_ERROR; + double Rn; /* Earth radius at location */ + double Sin_Lat; /* sin(Latitude) */ + double Sin2_Lat; /* Square of sin(Latitude) */ + double Cos_Lat; /* cos(Latitude) */ + + /* + ** Don't blow up if Latitude is just a little out of the value + ** range as it may just be a rounding issue. Also removed longitude + ** test, it should be wrapped by cos() and sin(). NFW for PROJ.4, Sep/2001. + */ + if( Latitude < -PI_OVER_2 && Latitude > -1.001 * PI_OVER_2 ) + Latitude = -PI_OVER_2; + else if( Latitude > PI_OVER_2 && Latitude < 1.001 * PI_OVER_2 ) + Latitude = PI_OVER_2; + else if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) + { /* Latitude out of range */ + Error_Code |= GEOCENT_LAT_ERROR; + } + + if (!Error_Code) + { /* no errors */ + if (Longitude > PI) + Longitude -= (2*PI); + Sin_Lat = sin(Latitude); + Cos_Lat = cos(Latitude); + Sin2_Lat = Sin_Lat * Sin_Lat; + Rn = gi->Geocent_a / (sqrt(1.0e0 - gi->Geocent_e2 * Sin2_Lat)); + *X = (Rn + Height) * Cos_Lat * cos(Longitude); + *Y = (Rn + Height) * Cos_Lat * sin(Longitude); + *Z = ((Rn * (1 - gi->Geocent_e2)) + Height) * Sin_Lat; + + } + return (Error_Code); +} /* END OF Convert_Geodetic_To_Geocentric */ + +/* + * The function Convert_Geocentric_To_Geodetic converts geocentric + * coordinates (X, Y, Z) to geodetic coordinates (latitude, longitude, + * and height), according to the current ellipsoid parameters. + * + * X : Geocentric X coordinate, in meters. (input) + * Y : Geocentric Y coordinate, in meters. (input) + * Z : Geocentric Z coordinate, in meters. (input) + * Latitude : Calculated latitude value in radians. (output) + * Longitude : Calculated longitude value in radians. (output) + * Height : Calculated height value, in meters. (output) + */ + +#define USE_ITERATIVE_METHOD + +void pj_Convert_Geocentric_To_Geodetic (GeocentricInfo *gi, + double X, + double Y, + double Z, + double *Latitude, + double *Longitude, + double *Height) +{ /* BEGIN Convert_Geocentric_To_Geodetic */ +#if !defined(USE_ITERATIVE_METHOD) +/* + * The method used here is derived from 'An Improved Algorithm for + * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996 + */ + +/* Note: Variable names follow the notation used in Toms, Feb 1996 */ + + double W; /* distance from Z axis */ + double W2; /* square of distance from Z axis */ + double T0; /* initial estimate of vertical component */ + double T1; /* corrected estimate of vertical component */ + double S0; /* initial estimate of horizontal component */ + double S1; /* corrected estimate of horizontal component */ + double Sin_B0; /* sin(B0), B0 is estimate of Bowring aux variable */ + double Sin3_B0; /* cube of sin(B0) */ + double Cos_B0; /* cos(B0) */ + double Sin_p1; /* sin(phi1), phi1 is estimated latitude */ + double Cos_p1; /* cos(phi1) */ + double Rn; /* Earth radius at location */ + double Sum; /* numerator of cos(phi1) */ + int At_Pole; /* indicates location is in polar region */ + + At_Pole = FALSE; + if (X != 0.0) + { + *Longitude = atan2(Y,X); + } + else + { + if (Y > 0) + { + *Longitude = PI_OVER_2; + } + else if (Y < 0) + { + *Longitude = -PI_OVER_2; + } + else + { + At_Pole = TRUE; + *Longitude = 0.0; + if (Z > 0.0) + { /* north pole */ + *Latitude = PI_OVER_2; + } + else if (Z < 0.0) + { /* south pole */ + *Latitude = -PI_OVER_2; + } + else + { /* center of earth */ + *Latitude = PI_OVER_2; + *Height = -Geocent_b; + return; + } + } + } + W2 = X*X + Y*Y; + W = sqrt(W2); + T0 = Z * AD_C; + S0 = sqrt(T0 * T0 + W2); + Sin_B0 = T0 / S0; + Cos_B0 = W / S0; + Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0; + T1 = Z + gi->Geocent_b * gi->Geocent_ep2 * Sin3_B0; + Sum = W - gi->Geocent_a * gi->Geocent_e2 * Cos_B0 * Cos_B0 * Cos_B0; + S1 = sqrt(T1*T1 + Sum * Sum); + Sin_p1 = T1 / S1; + Cos_p1 = Sum / S1; + Rn = gi->Geocent_a / sqrt(1.0 - gi->Geocent_e2 * Sin_p1 * Sin_p1); + if (Cos_p1 >= COS_67P5) + { + *Height = W / Cos_p1 - Rn; + } + else if (Cos_p1 <= -COS_67P5) + { + *Height = W / -Cos_p1 - Rn; + } + else + { + *Height = Z / Sin_p1 + Rn * (gi->Geocent_e2 - 1.0); + } + if (At_Pole == FALSE) + { + *Latitude = atan(Sin_p1 / Cos_p1); + } +#else /* defined(USE_ITERATIVE_METHOD) */ +/* +* Reference... +* ============ +* Wenzel, H.-G.(1985): Hochauflösende Kugelfunktionsmodelle für +* das Gravitationspotential der Erde. Wiss. Arb. Univ. Hannover +* Nr. 137, p. 130-131. + +* Programmed by GGA- Leibniz-Institute of Applied Geophysics +* Stilleweg 2 +* D-30655 Hannover +* Federal Republic of Germany +* Internet: www.gga-hannover.de +* +* Hannover, March 1999, April 2004. +* see also: comments in statements +* remarks: +* Mathematically exact and because of symmetry of rotation-ellipsoid, +* each point (X,Y,Z) has at least two solutions (Latitude1,Longitude1,Height1) and +* (Latitude2,Longitude2,Height2). Is point=(0.,0.,Z) (P=0.), so you get even +* four solutions, every two symmetrical to the semi-minor axis. +* Here Height1 and Height2 have at least a difference in order of +* radius of curvature (e.g. (0,0,b)=> (90.,0.,0.) or (-90.,0.,-2b); +* (a+100.)*(sqrt(2.)/2.,sqrt(2.)/2.,0.) => (0.,45.,100.) or +* (0.,225.,-(2a+100.))). +* The algorithm always computes (Latitude,Longitude) with smallest |Height|. +* For normal computations, that means |Height|<10000.m, algorithm normally +* converges after to 2-3 steps!!! +* But if |Height| has the amount of length of ellipsoid's axis +* (e.g. -6300000.m), algorithm needs about 15 steps. +*/ + +/* local definitions and variables */ +/* end-criterium of loop, accuracy of sin(Latitude) */ +#define genau 1.E-12 +#define genau2 (genau*genau) +#define maxiter 30 + + double P; /* distance between semi-minor axis and location */ + double RR; /* distance between center and location */ + double CT; /* sin of geocentric latitude */ + double ST; /* cos of geocentric latitude */ + double RX; + double RK; + double RN; /* Earth radius at location */ + double CPHI0; /* cos of start or old geodetic latitude in iterations */ + double SPHI0; /* sin of start or old geodetic latitude in iterations */ + double CPHI; /* cos of searched geodetic latitude */ + double SPHI; /* sin of searched geodetic latitude */ + double SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ + int iter; /* # of continuous iteration, max. 30 is always enough (s.a.) */ + + P = sqrt(X*X+Y*Y); + RR = sqrt(X*X+Y*Y+Z*Z); + +/* special cases for latitude and longitude */ + if (P/gi->Geocent_a < genau) { + +/* special case, if P=0. (X=0., Y=0.) */ + *Longitude = 0.; + +/* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis + * of ellipsoid (=center of mass), Latitude becomes PI/2 */ + if (RR/gi->Geocent_a < genau) { + *Latitude = PI_OVER_2; + *Height = -gi->Geocent_b; + return ; + + } + } + else { +/* ellipsoidal (geodetic) longitude + * interval: -PI < Longitude <= +PI */ + *Longitude=atan2(Y,X); + } + +/* -------------------------------------------------------------- + * Following iterative algorithm was developed by + * "Institut für Erdmessung", University of Hannover, July 1988. + * Internet: www.ife.uni-hannover.de + * Iterative computation of CPHI,SPHI and Height. + * Iteration of CPHI and SPHI to 10**-12 radian resp. + * 2*10**-7 arcsec. + * -------------------------------------------------------------- + */ + CT = Z/RR; + ST = P/RR; + RX = 1.0/sqrt(1.0-gi->Geocent_e2*(2.0-gi->Geocent_e2)*ST*ST); + CPHI0 = ST*(1.0-gi->Geocent_e2)*RX; + SPHI0 = CT*RX; + iter = 0; + +/* loop to find sin(Latitude) resp. Latitude + * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ + do + { + iter++; + RN = gi->Geocent_a/sqrt(1.0-gi->Geocent_e2*SPHI0*SPHI0); + +/* ellipsoidal (geodetic) height */ + *Height = P*CPHI0+Z*SPHI0-RN*(1.0-gi->Geocent_e2*SPHI0*SPHI0); + + /* avoid zero division */ + if (RN+*Height==0.0) { + *Latitude = 0.0; + return; + } + RK = gi->Geocent_e2*RN/(RN+*Height); + RX = 1.0/sqrt(1.0-RK*(2.0-RK)*ST*ST); + CPHI = ST*(1.0-RK)*RX; + SPHI = CT*RX; + SDPHI = SPHI*CPHI0-CPHI*SPHI0; + CPHI0 = CPHI; + SPHI0 = SPHI; + } + while (SDPHI*SDPHI > genau2 && iter < maxiter); + +/* ellipsoidal (geodetic) latitude */ + *Latitude=atan2(SPHI, fabs(CPHI)); + +#endif /* defined(USE_ITERATIVE_METHOD) */ +} /* END OF Convert_Geocentric_To_Geodetic */ diff --git a/src/geod.c b/src/geod.c deleted file mode 100644 index bb52818e..00000000 --- a/src/geod.c +++ /dev/null @@ -1,239 +0,0 @@ -/* <<<< 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 char -*oform = (char *)0, /* output format for decimal degrees */ -*osform = "%.3f", /* output format for S */ -pline[50], /* work string */ -*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,'/')) != NULL) ++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++] = "-"; - 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++] = "-"; - for ( ; eargc-- ; ++eargv) { - if (**eargv == '-') { - fid = stdin; - emess_dat.File_name = ""; - } else { - if ((fid = fopen(*eargv, "r")) == NULL) { - 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 *)0; - } - } - exit(0); /* normal completion */ -} diff --git a/src/geod.cpp b/src/geod.cpp new file mode 100644 index 00000000..bb52818e --- /dev/null +++ b/src/geod.cpp @@ -0,0 +1,239 @@ +/* <<<< 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 char +*oform = (char *)0, /* output format for decimal degrees */ +*osform = "%.3f", /* output format for S */ +pline[50], /* work string */ +*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,'/')) != NULL) ++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++] = "-"; + 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++] = "-"; + for ( ; eargc-- ; ++eargv) { + if (**eargv == '-') { + fid = stdin; + emess_dat.File_name = ""; + } else { + if ((fid = fopen(*eargv, "r")) == NULL) { + 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 *)0; + } + } + exit(0); /* normal completion */ +} diff --git a/src/geod_interface.c b/src/geod_interface.c deleted file mode 100644 index a30377ac..00000000 --- a/src/geod_interface.c +++ /dev/null @@ -1,33 +0,0 @@ -#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/geod_interface.cpp b/src/geod_interface.cpp new file mode 100644 index 00000000..a30377ac --- /dev/null +++ b/src/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/geod_set.c b/src/geod_set.c deleted file mode 100644 index b5bd0667..00000000 --- a/src/geod_set.c +++ /dev/null @@ -1,75 +0,0 @@ -#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 = 0, *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 != 0 && 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(NULL,start, "sunits").s) != NULL) { - 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(NULL,start, "tlat_1").i) { - double del_S; -#undef f - phi1 = pj_param(NULL,start, "rlat_1").f; - lam1 = pj_param(NULL,start, "rlon_1").f; - if (pj_param(NULL,start, "tlat_2").i) { - phi2 = pj_param(NULL,start, "rlat_2").f; - lam2 = pj_param(NULL,start, "rlon_2").f; - geod_inv(); - geod_pre(); - } else if ((geod_S = pj_param(NULL,start, "dS").f) != 0.) { - al12 = pj_param(NULL,start, "rA").f; - geod_pre(); - geod_for(); - } else emess(1,"incomplete geodesic/arc info"); - if ((n_alpha = pj_param(NULL,start, "in_A").i) > 0) { - if ((del_alpha = pj_param(NULL,start, "rdel_A").f) == 0.0) - emess(1,"del azimuth == 0"); - } else if ((del_S = fabs(pj_param(NULL,start, "ddel_S").f)) != 0.) { - n_S = (int)(geod_S / del_S + .5); - } else if ((n_S = pj_param(NULL,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/geod_set.cpp b/src/geod_set.cpp new file mode 100644 index 00000000..b5bd0667 --- /dev/null +++ b/src/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 = 0, *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 != 0 && 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(NULL,start, "sunits").s) != NULL) { + 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(NULL,start, "tlat_1").i) { + double del_S; +#undef f + phi1 = pj_param(NULL,start, "rlat_1").f; + lam1 = pj_param(NULL,start, "rlon_1").f; + if (pj_param(NULL,start, "tlat_2").i) { + phi2 = pj_param(NULL,start, "rlat_2").f; + lam2 = pj_param(NULL,start, "rlon_2").f; + geod_inv(); + geod_pre(); + } else if ((geod_S = pj_param(NULL,start, "dS").f) != 0.) { + al12 = pj_param(NULL,start, "rA").f; + geod_pre(); + geod_for(); + } else emess(1,"incomplete geodesic/arc info"); + if ((n_alpha = pj_param(NULL,start, "in_A").i) > 0) { + if ((del_alpha = pj_param(NULL,start, "rdel_A").f) == 0.0) + emess(1,"del azimuth == 0"); + } else if ((del_S = fabs(pj_param(NULL,start, "ddel_S").f)) != 0.) { + n_S = (int)(geod_S / del_S + .5); + } else if ((n_S = pj_param(NULL,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/geodesic.c b/src/geodesic.c deleted file mode 100644 index 220dcd7f..00000000 --- a/src/geodesic.c +++ /dev/null @@ -1,2100 +0,0 @@ -/** - * \file geodesic.c - * \brief Implementation of the geodesic routines in C - * - * For the full documentation see geodesic.h. - **********************************************************************/ - -/** @cond SKIP */ - -/* - * This is a C implementation of the geodesic algorithms described in - * - * C. F. F. Karney, - * Algorithms for geodesics, - * J. Geodesy 87, 43--55 (2013); - * https://doi.org/10.1007/s00190-012-0578-z - * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html - * - * See the comments in geodesic.h for documentation. - * - * Copyright (c) Charles Karney (2012-2018) and licensed - * under the MIT/X11 License. For more information, see - * https://geographiclib.sourceforge.io/ - */ - -#include "geodesic.h" -#ifdef PJ_LIB__ -#include "proj_math.h" -#else -#include -#endif - -#if !defined(HAVE_C99_MATH) -#define HAVE_C99_MATH 0 -#endif - -#define GEOGRAPHICLIB_GEODESIC_ORDER 6 -#define nA1 GEOGRAPHICLIB_GEODESIC_ORDER -#define nC1 GEOGRAPHICLIB_GEODESIC_ORDER -#define nC1p GEOGRAPHICLIB_GEODESIC_ORDER -#define nA2 GEOGRAPHICLIB_GEODESIC_ORDER -#define nC2 GEOGRAPHICLIB_GEODESIC_ORDER -#define nA3 GEOGRAPHICLIB_GEODESIC_ORDER -#define nA3x nA3 -#define nC3 GEOGRAPHICLIB_GEODESIC_ORDER -#define nC3x ((nC3 * (nC3 - 1)) / 2) -#define nC4 GEOGRAPHICLIB_GEODESIC_ORDER -#define nC4x ((nC4 * (nC4 + 1)) / 2) -#define nC (GEOGRAPHICLIB_GEODESIC_ORDER + 1) - -typedef double real; -typedef int boolx; - -static unsigned init = 0; -static const int FALSE = 0; -static const int TRUE = 1; -static unsigned digits, maxit1, maxit2; -static real epsilon, realmin, pi, degree, NaN, - tiny, tol0, tol1, tol2, tolb, xthresh; - -static void Init() { - if (!init) { -#if defined(__DBL_MANT_DIG__) - digits = __DBL_MANT_DIG__; -#else - digits = 53; -#endif -#if defined(__DBL_EPSILON__) - epsilon = __DBL_EPSILON__; -#else - epsilon = pow(0.5, digits - 1); -#endif -#if defined(__DBL_MIN__) - realmin = __DBL_MIN__; -#else - realmin = pow(0.5, 1022); -#endif -#if defined(M_PI) - pi = M_PI; -#else - pi = atan2(0.0, -1.0); -#endif - maxit1 = 20; - maxit2 = maxit1 + digits + 10; - tiny = sqrt(realmin); - tol0 = epsilon; - /* Increase multiplier in defn of tol1 from 100 to 200 to fix inverse case - * 52.784459512564 0 -52.784459512563990912 179.634407464943777557 - * which otherwise failed for Visual Studio 10 (Release and Debug) */ - tol1 = 200 * tol0; - tol2 = sqrt(tol0); - /* Check on bisection interval */ - tolb = tol0 * tol2; - xthresh = 1000 * tol2; - degree = pi/180; - #if defined(NAN) - NaN = NAN; - #else - { - real minus1 = -1; - /* cppcheck-suppress wrongmathcall */ - NaN = sqrt(minus1); - } - #endif - init = 1; - } -} - -enum captype { - CAP_NONE = 0U, - CAP_C1 = 1U<<0, - CAP_C1p = 1U<<1, - CAP_C2 = 1U<<2, - CAP_C3 = 1U<<3, - CAP_C4 = 1U<<4, - CAP_ALL = 0x1FU, - OUT_ALL = 0x7F80U -}; - -static real sq(real x) { return x * x; } -#if HAVE_C99_MATH -#define atanhx atanh -#define copysignx copysign -#define hypotx hypot -#define cbrtx cbrt -#else -static real log1px(real x) { - volatile real - y = 1 + x, - z = y - 1; - /* Here's the explanation for this magic: y = 1 + z, exactly, and z - * approx x, thus log(y)/z (which is nearly constant near z = 0) returns - * a good approximation to the true log(1 + x)/x. The multiplication x * - * (log(y)/z) introduces little additional error. */ - return z == 0 ? x : x * log(y) / z; -} - -static real atanhx(real x) { - real y = fabs(x); /* Enforce odd parity */ - y = log1px(2 * y/(1 - y))/2; - return x < 0 ? -y : y; -} - -static real copysignx(real x, real y) { - return fabs(x) * (y < 0 || (y == 0 && 1/y < 0) ? -1 : 1); -} - -static real hypotx(real x, real y) -{ return sqrt(x * x + y * y); } - -static real cbrtx(real x) { - real y = pow(fabs(x), 1/(real)(3)); /* Return the real cube root */ - return x < 0 ? -y : y; -} -#endif - -static real sumx(real u, real v, real* t) { - volatile real s = u + v; - volatile real up = s - v; - volatile real vpp = s - up; - up -= u; - vpp -= v; - if (t) *t = -(up + vpp); - /* error-free sum: - * u + v = s + t - * = round(u + v) + t */ - return s; -} - -static real polyval(int N, const real p[], real x) { - real y = N < 0 ? 0 : *p++; - while (--N >= 0) y = y * x + *p++; - return y; -} - -/* mimic C++ std::min and std::max */ -static real minx(real a, real b) -{ return (b < a) ? b : a; } - -static real maxx(real a, real b) -{ return (a < b) ? b : a; } - -static void swapx(real* x, real* y) -{ real t = *x; *x = *y; *y = t; } - -static void norm2(real* sinx, real* cosx) { - real r = hypotx(*sinx, *cosx); - *sinx /= r; - *cosx /= r; -} - -static real AngNormalize(real x) { -#if HAVE_C99_MATH - x = remainder(x, (real)(360)); - return x != -180 ? x : 180; -#else - real y = fmod(x, (real)(360)); -#if defined(_MSC_VER) && _MSC_VER < 1900 - /* - * Before version 14 (2015), Visual Studio had problems dealing - * with -0.0. Specifically - * VC 10,11,12 and 32-bit compile: fmod(-0.0, 360.0) -> +0.0 - * sincosdx has a similar fix. - * python 2.7 on Windows 32-bit machines has the same problem. - */ - if (x == 0) y = x; -#endif - return y <= -180 ? y + 360 : (y <= 180 ? y : y - 360); -#endif -} - -static real LatFix(real x) -{ return fabs(x) > 90 ? NaN : x; } - -static real AngDiff(real x, real y, real* e) { - real t, d = AngNormalize(sumx(AngNormalize(-x), AngNormalize(y), &t)); - /* Here y - x = d + t (mod 360), exactly, where d is in (-180,180] and - * abs(t) <= eps (eps = 2^-45 for doubles). The only case where the - * addition of t takes the result outside the range (-180,180] is d = 180 - * and t > 0. The case, d = -180 + eps, t = -eps, can't happen, since - * sum would have returned the exact result in such a case (i.e., given t - * = 0). */ - return sumx(d == 180 && t > 0 ? -180 : d, t, e); -} - -static real AngRound(real x) { - const real z = 1/(real)(16); - volatile real y; - if (x == 0) return 0; - y = fabs(x); - /* The compiler mustn't "simplify" z - (z - y) to y */ - y = y < z ? z - (z - y) : y; - return x < 0 ? -y : y; -} - -static void sincosdx(real x, real* sinx, real* cosx) { - /* In order to minimize round-off errors, this function exactly reduces - * the argument to the range [-45, 45] before converting it to radians. */ - real r, s, c; int q; -#if HAVE_C99_MATH && !defined(__GNUC__) - /* Disable for gcc because of bug in glibc version < 2.22, see - * https://sourceware.org/bugzilla/show_bug.cgi?id=17569 */ - r = remquo(x, (real)(90), &q); -#else - r = fmod(x, (real)(360)); - /* check for NaN */ - q = r == r ? (int)(floor(r / 90 + (real)(0.5))) : 0; - r -= 90 * q; -#endif - /* now abs(r) <= 45 */ - r *= degree; - /* Possibly could call the gnu extension sincos */ - s = sin(r); c = cos(r); -#if defined(_MSC_VER) && _MSC_VER < 1900 - /* - * Before version 14 (2015), Visual Studio had problems dealing - * with -0.0. Specifically - * VC 10,11,12 and 32-bit compile: fmod(-0.0, 360.0) -> +0.0 - * VC 12 and 64-bit compile: sin(-0.0) -> +0.0 - * AngNormalize has a similar fix. - * python 2.7 on Windows 32-bit machines has the same problem. - */ - if (x == 0) s = x; -#endif - switch ((unsigned)q & 3U) { - case 0U: *sinx = s; *cosx = c; break; - case 1U: *sinx = c; *cosx = -s; break; - case 2U: *sinx = -s; *cosx = -c; break; - default: *sinx = -c; *cosx = s; break; /* case 3U */ - } - if (x != 0) { *sinx += (real)(0); *cosx += (real)(0); } -} - -static real atan2dx(real y, real x) { - /* In order to minimize round-off errors, this function rearranges the - * arguments so that result of atan2 is in the range [-pi/4, pi/4] before - * converting it to degrees and mapping the result to the correct - * quadrant. */ - int q = 0; real ang; - if (fabs(y) > fabs(x)) { swapx(&x, &y); q = 2; } - if (x < 0) { x = -x; ++q; } - /* here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] */ - ang = atan2(y, x) / degree; - switch (q) { - /* Note that atan2d(-0.0, 1.0) will return -0. However, we expect that - * atan2d will not be called with y = -0. If need be, include - * - * case 0: ang = 0 + ang; break; - */ - case 1: ang = (y >= 0 ? 180 : -180) - ang; break; - case 2: ang = 90 - ang; break; - case 3: ang = -90 + ang; break; - } - return ang; -} - -static void A3coeff(struct geod_geodesic* g); -static void C3coeff(struct geod_geodesic* g); -static void C4coeff(struct geod_geodesic* g); -static real SinCosSeries(boolx sinp, - real sinx, real cosx, - const real c[], int n); -static void Lengths(const struct geod_geodesic* g, - real eps, real sig12, - real ssig1, real csig1, real dn1, - real ssig2, real csig2, real dn2, - real cbet1, real cbet2, - real* ps12b, real* pm12b, real* pm0, - real* pM12, real* pM21, - /* Scratch area of the right size */ - real Ca[]); -static real Astroid(real x, real y); -static real InverseStart(const struct geod_geodesic* g, - real sbet1, real cbet1, real dn1, - real sbet2, real cbet2, real dn2, - real lam12, real slam12, real clam12, - real* psalp1, real* pcalp1, - /* Only updated if return val >= 0 */ - real* psalp2, real* pcalp2, - /* Only updated for short lines */ - real* pdnm, - /* Scratch area of the right size */ - real Ca[]); -static real Lambda12(const struct geod_geodesic* g, - real sbet1, real cbet1, real dn1, - real sbet2, real cbet2, real dn2, - real salp1, real calp1, - real slam120, real clam120, - real* psalp2, real* pcalp2, - real* psig12, - real* pssig1, real* pcsig1, - real* pssig2, real* pcsig2, - real* peps, - real* pdomg12, - boolx diffp, real* pdlam12, - /* Scratch area of the right size */ - real Ca[]); -static real A3f(const struct geod_geodesic* g, real eps); -static void C3f(const struct geod_geodesic* g, real eps, real c[]); -static void C4f(const struct geod_geodesic* g, real eps, real c[]); -static real A1m1f(real eps); -static void C1f(real eps, real c[]); -static void C1pf(real eps, real c[]); -static real A2m1f(real eps); -static void C2f(real eps, real c[]); -static int transit(real lon1, real lon2); -static int transitdirect(real lon1, real lon2); -static void accini(real s[]); -static void acccopy(const real s[], real t[]); -static void accadd(real s[], real y); -static real accsum(const real s[], real y); -static void accneg(real s[]); - -void geod_init(struct geod_geodesic* g, real a, real f) { - if (!init) Init(); - g->a = a; - g->f = f; - g->f1 = 1 - g->f; - g->e2 = g->f * (2 - g->f); - g->ep2 = g->e2 / sq(g->f1); /* e2 / (1 - e2) */ - g->n = g->f / ( 2 - g->f); - g->b = g->a * g->f1; - g->c2 = (sq(g->a) + sq(g->b) * - (g->e2 == 0 ? 1 : - (g->e2 > 0 ? atanhx(sqrt(g->e2)) : atan(sqrt(-g->e2))) / - sqrt(fabs(g->e2))))/2; /* authalic radius squared */ - /* The sig12 threshold for "really short". Using the auxiliary sphere - * solution with dnm computed at (bet1 + bet2) / 2, the relative error in the - * azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2. (Error - * measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given f and - * sig12, the max error occurs for lines near the pole. If the old rule for - * computing dnm = (dn1 + dn2)/2 is used, then the error increases by a - * factor of 2.) Setting this equal to epsilon gives sig12 = etol2. Here - * 0.1 is a safety factor (error decreased by 100) and max(0.001, abs(f)) - * stops etol2 getting too large in the nearly spherical case. */ - g->etol2 = 0.1 * tol2 / - sqrt( maxx((real)(0.001), fabs(g->f)) * minx((real)(1), 1 - g->f/2) / 2 ); - - A3coeff(g); - C3coeff(g); - C4coeff(g); -} - -static void geod_lineinit_int(struct geod_geodesicline* l, - const struct geod_geodesic* g, - real lat1, real lon1, - real azi1, real salp1, real calp1, - unsigned caps) { - real cbet1, sbet1, eps; - l->a = g->a; - l->f = g->f; - l->b = g->b; - l->c2 = g->c2; - l->f1 = g->f1; - /* If caps is 0 assume the standard direct calculation */ - l->caps = (caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE) | - /* always allow latitude and azimuth and unrolling of longitude */ - GEOD_LATITUDE | GEOD_AZIMUTH | GEOD_LONG_UNROLL; - - l->lat1 = LatFix(lat1); - l->lon1 = lon1; - l->azi1 = azi1; - l->salp1 = salp1; - l->calp1 = calp1; - - sincosdx(AngRound(l->lat1), &sbet1, &cbet1); sbet1 *= l->f1; - /* Ensure cbet1 = +epsilon at poles */ - norm2(&sbet1, &cbet1); cbet1 = maxx(tiny, cbet1); - l->dn1 = sqrt(1 + g->ep2 * sq(sbet1)); - - /* Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), */ - l->salp0 = l->salp1 * cbet1; /* alp0 in [0, pi/2 - |bet1|] */ - /* Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following - * is slightly better (consider the case salp1 = 0). */ - l->calp0 = hypotx(l->calp1, l->salp1 * sbet1); - /* Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). - * sig = 0 is nearest northward crossing of equator. - * With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). - * With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 - * With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 - * Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). - * With alp0 in (0, pi/2], quadrants for sig and omg coincide. - * No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. - * With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. */ - l->ssig1 = sbet1; l->somg1 = l->salp0 * sbet1; - l->csig1 = l->comg1 = sbet1 != 0 || l->calp1 != 0 ? cbet1 * l->calp1 : 1; - norm2(&l->ssig1, &l->csig1); /* sig1 in (-pi, pi] */ - /* norm2(somg1, comg1); -- don't need to normalize! */ - - l->k2 = sq(l->calp0) * g->ep2; - eps = l->k2 / (2 * (1 + sqrt(1 + l->k2)) + l->k2); - - if (l->caps & CAP_C1) { - real s, c; - l->A1m1 = A1m1f(eps); - C1f(eps, l->C1a); - l->B11 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C1a, nC1); - s = sin(l->B11); c = cos(l->B11); - /* tau1 = sig1 + B11 */ - l->stau1 = l->ssig1 * c + l->csig1 * s; - l->ctau1 = l->csig1 * c - l->ssig1 * s; - /* Not necessary because C1pa reverts C1a - * B11 = -SinCosSeries(TRUE, stau1, ctau1, C1pa, nC1p); */ - } - - if (l->caps & CAP_C1p) - C1pf(eps, l->C1pa); - - if (l->caps & CAP_C2) { - l->A2m1 = A2m1f(eps); - C2f(eps, l->C2a); - l->B21 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C2a, nC2); - } - - if (l->caps & CAP_C3) { - C3f(g, eps, l->C3a); - l->A3c = -l->f * l->salp0 * A3f(g, eps); - l->B31 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C3a, nC3-1); - } - - if (l->caps & CAP_C4) { - C4f(g, eps, l->C4a); - /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) */ - l->A4 = sq(l->a) * l->calp0 * l->salp0 * g->e2; - l->B41 = SinCosSeries(FALSE, l->ssig1, l->csig1, l->C4a, nC4); - } - - l->a13 = l->s13 = NaN; -} - -void geod_lineinit(struct geod_geodesicline* l, - const struct geod_geodesic* g, - real lat1, real lon1, real azi1, unsigned caps) { - real salp1, calp1; - azi1 = AngNormalize(azi1); - /* Guard against underflow in salp0 */ - sincosdx(AngRound(azi1), &salp1, &calp1); - geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); -} - -void geod_gendirectline(struct geod_geodesicline* l, - const struct geod_geodesic* g, - real lat1, real lon1, real azi1, - unsigned flags, real s12_a12, - unsigned caps) { - geod_lineinit(l, g, lat1, lon1, azi1, caps); - geod_gensetdistance(l, flags, s12_a12); -} - -void geod_directline(struct geod_geodesicline* l, - const struct geod_geodesic* g, - real lat1, real lon1, real azi1, - real s12, unsigned caps) { - geod_gendirectline(l, g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, caps); -} - -real geod_genposition(const struct geod_geodesicline* l, - unsigned flags, real s12_a12, - real* plat2, real* plon2, real* pazi2, - real* ps12, real* pm12, - real* pM12, real* pM21, - real* pS12) { - real lat2 = 0, lon2 = 0, azi2 = 0, s12 = 0, - m12 = 0, M12 = 0, M21 = 0, S12 = 0; - /* Avoid warning about uninitialized B12. */ - real sig12, ssig12, csig12, B12 = 0, AB1 = 0; - real omg12, lam12, lon12; - real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2; - unsigned outmask = - (plat2 ? GEOD_LATITUDE : 0U) | - (plon2 ? GEOD_LONGITUDE : 0U) | - (pazi2 ? GEOD_AZIMUTH : 0U) | - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); - - outmask &= l->caps & OUT_ALL; - if (!( TRUE /*Init()*/ && - (flags & GEOD_ARCMODE || (l->caps & (GEOD_DISTANCE_IN & OUT_ALL))) )) - /* Uninitialized or impossible distance calculation requested */ - return NaN; - - if (flags & GEOD_ARCMODE) { - /* Interpret s12_a12 as spherical arc length */ - sig12 = s12_a12 * degree; - sincosdx(s12_a12, &ssig12, &csig12); - } else { - /* Interpret s12_a12 as distance */ - real - tau12 = s12_a12 / (l->b * (1 + l->A1m1)), - s = sin(tau12), - c = cos(tau12); - /* tau2 = tau1 + tau12 */ - B12 = - SinCosSeries(TRUE, - l->stau1 * c + l->ctau1 * s, - l->ctau1 * c - l->stau1 * s, - l->C1pa, nC1p); - sig12 = tau12 - (B12 - l->B11); - ssig12 = sin(sig12); csig12 = cos(sig12); - if (fabs(l->f) > 0.01) { - /* Reverted distance series is inaccurate for |f| > 1/100, so correct - * sig12 with 1 Newton iteration. The following table shows the - * approximate maximum error for a = WGS_a() and various f relative to - * GeodesicExact. - * erri = the error in the inverse solution (nm) - * errd = the error in the direct solution (series only) (nm) - * errda = the error in the direct solution (series + 1 Newton) (nm) - * - * f erri errd errda - * -1/5 12e6 1.2e9 69e6 - * -1/10 123e3 12e6 765e3 - * -1/20 1110 108e3 7155 - * -1/50 18.63 200.9 27.12 - * -1/100 18.63 23.78 23.37 - * -1/150 18.63 21.05 20.26 - * 1/150 22.35 24.73 25.83 - * 1/100 22.35 25.03 25.31 - * 1/50 29.80 231.9 30.44 - * 1/20 5376 146e3 10e3 - * 1/10 829e3 22e6 1.5e6 - * 1/5 157e6 3.8e9 280e6 */ - real serr; - ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; - csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; - B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); - serr = (1 + l->A1m1) * (sig12 + (B12 - l->B11)) - s12_a12 / l->b; - sig12 = sig12 - serr / sqrt(1 + l->k2 * sq(ssig2)); - ssig12 = sin(sig12); csig12 = cos(sig12); - /* Update B12 below */ - } - } - - /* sig2 = sig1 + sig12 */ - ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; - csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; - dn2 = sqrt(1 + l->k2 * sq(ssig2)); - if (outmask & (GEOD_DISTANCE | GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { - if (flags & GEOD_ARCMODE || fabs(l->f) > 0.01) - B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); - AB1 = (1 + l->A1m1) * (B12 - l->B11); - } - /* sin(bet2) = cos(alp0) * sin(sig2) */ - sbet2 = l->calp0 * ssig2; - /* Alt: cbet2 = hypot(csig2, salp0 * ssig2); */ - cbet2 = hypotx(l->salp0, l->calp0 * csig2); - if (cbet2 == 0) - /* I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case */ - cbet2 = csig2 = tiny; - /* tan(alp0) = cos(sig2)*tan(alp2) */ - salp2 = l->salp0; calp2 = l->calp0 * csig2; /* No need to normalize */ - - if (outmask & GEOD_DISTANCE) - s12 = (flags & GEOD_ARCMODE) ? - l->b * ((1 + l->A1m1) * sig12 + AB1) : - s12_a12; - - if (outmask & GEOD_LONGITUDE) { - real E = copysignx(1, l->salp0); /* east or west going? */ - /* tan(omg2) = sin(alp0) * tan(sig2) */ - somg2 = l->salp0 * ssig2; comg2 = csig2; /* No need to normalize */ - /* omg12 = omg2 - omg1 */ - omg12 = (flags & GEOD_LONG_UNROLL) - ? E * (sig12 - - (atan2( ssig2, csig2) - atan2( l->ssig1, l->csig1)) - + (atan2(E * somg2, comg2) - atan2(E * l->somg1, l->comg1))) - : atan2(somg2 * l->comg1 - comg2 * l->somg1, - comg2 * l->comg1 + somg2 * l->somg1); - lam12 = omg12 + l->A3c * - ( sig12 + (SinCosSeries(TRUE, ssig2, csig2, l->C3a, nC3-1) - - l->B31)); - lon12 = lam12 / degree; - lon2 = (flags & GEOD_LONG_UNROLL) ? l->lon1 + lon12 : - AngNormalize(AngNormalize(l->lon1) + AngNormalize(lon12)); - } - - if (outmask & GEOD_LATITUDE) - lat2 = atan2dx(sbet2, l->f1 * cbet2); - - if (outmask & GEOD_AZIMUTH) - azi2 = atan2dx(salp2, calp2); - - if (outmask & (GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { - real - B22 = SinCosSeries(TRUE, ssig2, csig2, l->C2a, nC2), - AB2 = (1 + l->A2m1) * (B22 - l->B21), - J12 = (l->A1m1 - l->A2m1) * sig12 + (AB1 - AB2); - if (outmask & GEOD_REDUCEDLENGTH) - /* Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure - * accurate cancellation in the case of coincident points. */ - m12 = l->b * ((dn2 * (l->csig1 * ssig2) - l->dn1 * (l->ssig1 * csig2)) - - l->csig1 * csig2 * J12); - if (outmask & GEOD_GEODESICSCALE) { - real t = l->k2 * (ssig2 - l->ssig1) * (ssig2 + l->ssig1) / - (l->dn1 + dn2); - M12 = csig12 + (t * ssig2 - csig2 * J12) * l->ssig1 / l->dn1; - M21 = csig12 - (t * l->ssig1 - l->csig1 * J12) * ssig2 / dn2; - } - } - - if (outmask & GEOD_AREA) { - real - B42 = SinCosSeries(FALSE, ssig2, csig2, l->C4a, nC4); - real salp12, calp12; - if (l->calp0 == 0 || l->salp0 == 0) { - /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ - salp12 = salp2 * l->calp1 - calp2 * l->salp1; - calp12 = calp2 * l->calp1 + salp2 * l->salp1; - } else { - /* tan(alp) = tan(alp0) * sec(sig) - * tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) - * = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) - * If csig12 > 0, write - * csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) - * else - * csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 - * No need to normalize */ - salp12 = l->calp0 * l->salp0 * - (csig12 <= 0 ? l->csig1 * (1 - csig12) + ssig12 * l->ssig1 : - ssig12 * (l->csig1 * ssig12 / (1 + csig12) + l->ssig1)); - calp12 = sq(l->salp0) + sq(l->calp0) * l->csig1 * csig2; - } - S12 = l->c2 * atan2(salp12, calp12) + l->A4 * (B42 - l->B41); - } - - /* In the pattern - * - * if ((outmask & GEOD_XX) && pYY) - * *pYY = YY; - * - * the second check "&& pYY" is redundant. It's there to make the CLang - * static analyzer happy. - */ - if ((outmask & GEOD_LATITUDE) && plat2) - *plat2 = lat2; - if ((outmask & GEOD_LONGITUDE) && plon2) - *plon2 = lon2; - if ((outmask & GEOD_AZIMUTH) && pazi2) - *pazi2 = azi2; - if ((outmask & GEOD_DISTANCE) && ps12) - *ps12 = s12; - if ((outmask & GEOD_REDUCEDLENGTH) && pm12) - *pm12 = m12; - if (outmask & GEOD_GEODESICSCALE) { - if (pM12) *pM12 = M12; - if (pM21) *pM21 = M21; - } - if ((outmask & GEOD_AREA) && pS12) - *pS12 = S12; - - return (flags & GEOD_ARCMODE) ? s12_a12 : sig12 / degree; -} - -void geod_setdistance(struct geod_geodesicline* l, real s13) { - l->s13 = s13; - l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, 0, 0, 0, 0, 0, 0, 0, 0); -} - -static void geod_setarc(struct geod_geodesicline* l, real a13) { - l->a13 = a13; l->s13 = NaN; - geod_genposition(l, GEOD_ARCMODE, l->a13, 0, 0, 0, &l->s13, 0, 0, 0, 0); -} - -void geod_gensetdistance(struct geod_geodesicline* l, - unsigned flags, real s13_a13) { - (flags & GEOD_ARCMODE) ? - geod_setarc(l, s13_a13) : - geod_setdistance(l, s13_a13); -} - -void geod_position(const struct geod_geodesicline* l, real s12, - real* plat2, real* plon2, real* pazi2) { - geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, 0, 0, 0, 0, 0); -} - -real geod_gendirect(const struct geod_geodesic* g, - real lat1, real lon1, real azi1, - unsigned flags, real s12_a12, - real* plat2, real* plon2, real* pazi2, - real* ps12, real* pm12, real* pM12, real* pM21, - real* pS12) { - struct geod_geodesicline l; - unsigned outmask = - (plat2 ? GEOD_LATITUDE : 0U) | - (plon2 ? GEOD_LONGITUDE : 0U) | - (pazi2 ? GEOD_AZIMUTH : 0U) | - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); - - geod_lineinit(&l, g, lat1, lon1, azi1, - /* Automatically supply GEOD_DISTANCE_IN if necessary */ - outmask | - ((flags & GEOD_ARCMODE) ? GEOD_NONE : GEOD_DISTANCE_IN)); - return geod_genposition(&l, flags, s12_a12, - plat2, plon2, pazi2, ps12, pm12, pM12, pM21, pS12); -} - -void geod_direct(const struct geod_geodesic* g, - real lat1, real lon1, real azi1, - real s12, - real* plat2, real* plon2, real* pazi2) { - geod_gendirect(g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, plat2, plon2, pazi2, - 0, 0, 0, 0, 0); -} - -static real geod_geninverse_int(const struct geod_geodesic* g, - real lat1, real lon1, real lat2, real lon2, - real* ps12, - real* psalp1, real* pcalp1, - real* psalp2, real* pcalp2, - real* pm12, real* pM12, real* pM21, - real* pS12) { - real s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0; - real lon12, lon12s; - int latsign, lonsign, swapp; - real sbet1, cbet1, sbet2, cbet2, s12x = 0, m12x = 0; - real dn1, dn2, lam12, slam12, clam12; - real a12 = 0, sig12, calp1 = 0, salp1 = 0, calp2 = 0, salp2 = 0; - real Ca[nC]; - boolx meridian; - /* somg12 > 1 marks that it needs to be calculated */ - real omg12 = 0, somg12 = 2, comg12 = 0; - - unsigned outmask = - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); - - outmask &= OUT_ALL; - /* Compute longitude difference (AngDiff does this carefully). Result is - * in [-180, 180] but -180 is only for west-going geodesics. 180 is for - * east-going and meridional geodesics. */ - lon12 = AngDiff(lon1, lon2, &lon12s); - /* Make longitude difference positive. */ - lonsign = lon12 >= 0 ? 1 : -1; - /* If very close to being on the same half-meridian, then make it so. */ - lon12 = lonsign * AngRound(lon12); - lon12s = AngRound((180 - lon12) - lonsign * lon12s); - lam12 = lon12 * degree; - if (lon12 > 90) { - sincosdx(lon12s, &slam12, &clam12); - clam12 = -clam12; - } else - sincosdx(lon12, &slam12, &clam12); - - /* If really close to the equator, treat as on equator. */ - lat1 = AngRound(LatFix(lat1)); - lat2 = AngRound(LatFix(lat2)); - /* Swap points so that point with higher (abs) latitude is point 1 - * If one latitude is a nan, then it becomes lat1. */ - swapp = fabs(lat1) < fabs(lat2) ? -1 : 1; - if (swapp < 0) { - lonsign *= -1; - swapx(&lat1, &lat2); - } - /* Make lat1 <= 0 */ - latsign = lat1 < 0 ? 1 : -1; - lat1 *= latsign; - lat2 *= latsign; - /* Now we have - * - * 0 <= lon12 <= 180 - * -90 <= lat1 <= 0 - * lat1 <= lat2 <= -lat1 - * - * longsign, swapp, latsign register the transformation to bring the - * coordinates to this canonical form. In all cases, 1 means no change was - * made. We make these transformations so that there are few cases to - * check, e.g., on verifying quadrants in atan2. In addition, this - * enforces some symmetries in the results returned. */ - - sincosdx(lat1, &sbet1, &cbet1); sbet1 *= g->f1; - /* Ensure cbet1 = +epsilon at poles */ - norm2(&sbet1, &cbet1); cbet1 = maxx(tiny, cbet1); - - sincosdx(lat2, &sbet2, &cbet2); sbet2 *= g->f1; - /* Ensure cbet2 = +epsilon at poles */ - norm2(&sbet2, &cbet2); cbet2 = maxx(tiny, cbet2); - - /* If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the - * |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is - * a better measure. This logic is used in assigning calp2 in Lambda12. - * Sometimes these quantities vanish and in that case we force bet2 = +/- - * bet1 exactly. An example where is is necessary is the inverse problem - * 48.522876735459 0 -48.52287673545898293 179.599720456223079643 - * which failed with Visual Studio 10 (Release and Debug) */ - - if (cbet1 < -sbet1) { - if (cbet2 == cbet1) - sbet2 = sbet2 < 0 ? sbet1 : -sbet1; - } else { - if (fabs(sbet2) == -sbet1) - cbet2 = cbet1; - } - - dn1 = sqrt(1 + g->ep2 * sq(sbet1)); - dn2 = sqrt(1 + g->ep2 * sq(sbet2)); - - meridian = lat1 == -90 || slam12 == 0; - - if (meridian) { - - /* Endpoints are on a single full meridian, so the geodesic might lie on - * a meridian. */ - - real ssig1, csig1, ssig2, csig2; - calp1 = clam12; salp1 = slam12; /* Head to the target longitude */ - calp2 = 1; salp2 = 0; /* At the target we're heading north */ - - /* tan(bet) = tan(sig) * cos(alp) */ - ssig1 = sbet1; csig1 = calp1 * cbet1; - ssig2 = sbet2; csig2 = calp2 * cbet2; - - /* sig12 = sig2 - sig1 */ - sig12 = atan2(maxx((real)(0), csig1 * ssig2 - ssig1 * csig2), - csig1 * csig2 + ssig1 * ssig2); - Lengths(g, g->n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, &s12x, &m12x, 0, - (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, - (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, - Ca); - /* Add the check for sig12 since zero length geodesics might yield m12 < - * 0. Test case was - * - * echo 20.001 0 20.001 0 | GeodSolve -i - * - * In fact, we will have sig12 > pi/2 for meridional geodesic which is - * not a shortest path. */ - if (sig12 < 1 || m12x >= 0) { - /* Need at least 2, to handle 90 0 90 180 */ - if (sig12 < 3 * tiny) - sig12 = m12x = s12x = 0; - m12x *= g->b; - s12x *= g->b; - a12 = sig12 / degree; - } else - /* m12 < 0, i.e., prolate and too close to anti-podal */ - meridian = FALSE; - } - - if (!meridian && - sbet1 == 0 && /* and sbet2 == 0 */ - /* Mimic the way Lambda12 works with calp1 = 0 */ - (g->f <= 0 || lon12s >= g->f * 180)) { - - /* Geodesic runs along equator */ - calp1 = calp2 = 0; salp1 = salp2 = 1; - s12x = g->a * lam12; - sig12 = omg12 = lam12 / g->f1; - m12x = g->b * sin(sig12); - if (outmask & GEOD_GEODESICSCALE) - M12 = M21 = cos(sig12); - a12 = lon12 / g->f1; - - } else if (!meridian) { - - /* Now point1 and point2 belong within a hemisphere bounded by a - * meridian and geodesic is neither meridional or equatorial. */ - - /* Figure a starting point for Newton's method */ - real dnm = 0; - sig12 = InverseStart(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, - lam12, slam12, clam12, - &salp1, &calp1, &salp2, &calp2, &dnm, - Ca); - - if (sig12 >= 0) { - /* Short lines (InverseStart sets salp2, calp2, dnm) */ - s12x = sig12 * g->b * dnm; - m12x = sq(dnm) * g->b * sin(sig12 / dnm); - if (outmask & GEOD_GEODESICSCALE) - M12 = M21 = cos(sig12 / dnm); - a12 = sig12 / degree; - omg12 = lam12 / (g->f1 * dnm); - } else { - - /* Newton's method. This is a straightforward solution of f(alp1) = - * lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one - * root in the interval (0, pi) and its derivative is positive at the - * root. Thus f(alp) is positive for alp > alp1 and negative for alp < - * alp1. During the course of the iteration, a range (alp1a, alp1b) is - * maintained which brackets the root and with each evaluation of - * f(alp) the range is shrunk, if possible. Newton's method is - * restarted whenever the derivative of f is negative (because the new - * value of alp1 is then further from the solution) or if the new - * estimate of alp1 lies outside (0,pi); in this case, the new starting - * guess is taken to be (alp1a + alp1b) / 2. */ - real ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, domg12 = 0; - unsigned numit = 0; - /* Bracketing range */ - real salp1a = tiny, calp1a = 1, salp1b = tiny, calp1b = -1; - boolx tripn = FALSE; - boolx tripb = FALSE; - for (; numit < maxit2; ++numit) { - /* the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 - * WGS84 and random input: mean = 2.85, sd = 0.60 */ - real dv = 0, - v = Lambda12(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, - slam12, clam12, - &salp2, &calp2, &sig12, &ssig1, &csig1, &ssig2, &csig2, - &eps, &domg12, numit < maxit1, &dv, Ca); - /* 2 * tol0 is approximately 1 ulp for a number in [0, pi]. */ - /* Reversed test to allow escape with NaNs */ - if (tripb || !(fabs(v) >= (tripn ? 8 : 1) * tol0)) break; - /* Update bracketing values */ - if (v > 0 && (numit > maxit1 || calp1/salp1 > calp1b/salp1b)) - { salp1b = salp1; calp1b = calp1; } - else if (v < 0 && (numit > maxit1 || calp1/salp1 < calp1a/salp1a)) - { salp1a = salp1; calp1a = calp1; } - if (numit < maxit1 && dv > 0) { - real - dalp1 = -v/dv; - real - sdalp1 = sin(dalp1), cdalp1 = cos(dalp1), - nsalp1 = salp1 * cdalp1 + calp1 * sdalp1; - if (nsalp1 > 0 && fabs(dalp1) < pi) { - calp1 = calp1 * cdalp1 - salp1 * sdalp1; - salp1 = nsalp1; - norm2(&salp1, &calp1); - /* In some regimes we don't get quadratic convergence because - * slope -> 0. So use convergence conditions based on epsilon - * instead of sqrt(epsilon). */ - tripn = fabs(v) <= 16 * tol0; - continue; - } - } - /* Either dv was not positive or updated value was outside legal - * range. Use the midpoint of the bracket as the next estimate. - * This mechanism is not needed for the WGS84 ellipsoid, but it does - * catch problems with more eccentric ellipsoids. Its efficacy is - * such for the WGS84 test set with the starting guess set to alp1 = - * 90deg: - * the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 - * WGS84 and random input: mean = 4.74, sd = 0.99 */ - salp1 = (salp1a + salp1b)/2; - calp1 = (calp1a + calp1b)/2; - norm2(&salp1, &calp1); - tripn = FALSE; - tripb = (fabs(salp1a - salp1) + (calp1a - calp1) < tolb || - fabs(salp1 - salp1b) + (calp1 - calp1b) < tolb); - } - Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, &s12x, &m12x, 0, - (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, - (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, Ca); - m12x *= g->b; - s12x *= g->b; - a12 = sig12 / degree; - if (outmask & GEOD_AREA) { - /* omg12 = lam12 - domg12 */ - real sdomg12 = sin(domg12), cdomg12 = cos(domg12); - somg12 = slam12 * cdomg12 - clam12 * sdomg12; - comg12 = clam12 * cdomg12 + slam12 * sdomg12; - } - } - } - - if (outmask & GEOD_DISTANCE) - s12 = 0 + s12x; /* Convert -0 to 0 */ - - if (outmask & GEOD_REDUCEDLENGTH) - m12 = 0 + m12x; /* Convert -0 to 0 */ - - if (outmask & GEOD_AREA) { - real - /* From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) */ - salp0 = salp1 * cbet1, - calp0 = hypotx(calp1, salp1 * sbet1); /* calp0 > 0 */ - real alp12; - if (calp0 != 0 && salp0 != 0) { - real - /* From Lambda12: tan(bet) = tan(sig) * cos(alp) */ - ssig1 = sbet1, csig1 = calp1 * cbet1, - ssig2 = sbet2, csig2 = calp2 * cbet2, - k2 = sq(calp0) * g->ep2, - eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2), - /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). */ - A4 = sq(g->a) * calp0 * salp0 * g->e2; - real B41, B42; - norm2(&ssig1, &csig1); - norm2(&ssig2, &csig2); - C4f(g, eps, Ca); - B41 = SinCosSeries(FALSE, ssig1, csig1, Ca, nC4); - B42 = SinCosSeries(FALSE, ssig2, csig2, Ca, nC4); - S12 = A4 * (B42 - B41); - } else - /* Avoid problems with indeterminate sig1, sig2 on equator */ - S12 = 0; - - if (!meridian && somg12 > 1) { - somg12 = sin(omg12); comg12 = cos(omg12); - } - - if (!meridian && - /* omg12 < 3/4 * pi */ - comg12 > -(real)(0.7071) && /* Long difference not too big */ - sbet2 - sbet1 < (real)(1.75)) { /* Lat difference not too big */ - /* Use tan(Gamma/2) = tan(omg12/2) - * * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) - * with tan(x/2) = sin(x)/(1+cos(x)) */ - real - domg12 = 1 + comg12, dbet1 = 1 + cbet1, dbet2 = 1 + cbet2; - alp12 = 2 * atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), - domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ); - } else { - /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ - real - salp12 = salp2 * calp1 - calp2 * salp1, - calp12 = calp2 * calp1 + salp2 * salp1; - /* The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz - * salp12 = -0 and alp12 = -180. However this depends on the sign - * being attached to 0 correctly. The following ensures the correct - * behavior. */ - if (salp12 == 0 && calp12 < 0) { - salp12 = tiny * calp1; - calp12 = -1; - } - alp12 = atan2(salp12, calp12); - } - S12 += g->c2 * alp12; - S12 *= swapp * lonsign * latsign; - /* Convert -0 to 0 */ - S12 += 0; - } - - /* Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. */ - if (swapp < 0) { - swapx(&salp1, &salp2); - swapx(&calp1, &calp2); - if (outmask & GEOD_GEODESICSCALE) - swapx(&M12, &M21); - } - - salp1 *= swapp * lonsign; calp1 *= swapp * latsign; - salp2 *= swapp * lonsign; calp2 *= swapp * latsign; - - if (psalp1) *psalp1 = salp1; - if (pcalp1) *pcalp1 = calp1; - if (psalp2) *psalp2 = salp2; - if (pcalp2) *pcalp2 = calp2; - - if (outmask & GEOD_DISTANCE) - *ps12 = s12; - if (outmask & GEOD_REDUCEDLENGTH) - *pm12 = m12; - if (outmask & GEOD_GEODESICSCALE) { - if (pM12) *pM12 = M12; - if (pM21) *pM21 = M21; - } - if (outmask & GEOD_AREA) - *pS12 = S12; - - /* Returned value in [0, 180] */ - return a12; -} - -real geod_geninverse(const struct geod_geodesic* g, - real lat1, real lon1, real lat2, real lon2, - real* ps12, real* pazi1, real* pazi2, - real* pm12, real* pM12, real* pM21, real* pS12) { - real salp1, calp1, salp2, calp2, - a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, ps12, - &salp1, &calp1, &salp2, &calp2, - pm12, pM12, pM21, pS12); - if (pazi1) *pazi1 = atan2dx(salp1, calp1); - if (pazi2) *pazi2 = atan2dx(salp2, calp2); - return a12; -} - -void geod_inverseline(struct geod_geodesicline* l, - const struct geod_geodesic* g, - real lat1, real lon1, real lat2, real lon2, - unsigned caps) { - real salp1, calp1, - a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, 0, - &salp1, &calp1, 0, 0, - 0, 0, 0, 0), - azi1 = atan2dx(salp1, calp1); - caps = caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE; - /* Ensure that a12 can be converted to a distance */ - if (caps & (OUT_ALL & GEOD_DISTANCE_IN)) caps |= GEOD_DISTANCE; - geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); - geod_setarc(l, a12); -} - -void geod_inverse(const struct geod_geodesic* g, - real lat1, real lon1, real lat2, real lon2, - real* ps12, real* pazi1, real* pazi2) { - geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, 0, 0, 0, 0); -} - -real SinCosSeries(boolx sinp, real sinx, real cosx, const real c[], int n) { - /* Evaluate - * y = sinp ? sum(c[i] * sin( 2*i * x), i, 1, n) : - * sum(c[i] * cos((2*i+1) * x), i, 0, n-1) - * using Clenshaw summation. N.B. c[0] is unused for sin series - * Approx operation count = (n + 5) mult and (2 * n + 2) add */ - real ar, y0, y1; - c += (n + sinp); /* Point to one beyond last element */ - ar = 2 * (cosx - sinx) * (cosx + sinx); /* 2 * cos(2 * x) */ - y0 = (n & 1) ? *--c : 0; y1 = 0; /* accumulators for sum */ - /* Now n is even */ - n /= 2; - while (n--) { - /* Unroll loop x 2, so accumulators return to their original role */ - y1 = ar * y0 - y1 + *--c; - y0 = ar * y1 - y0 + *--c; - } - return sinp - ? 2 * sinx * cosx * y0 /* sin(2 * x) * y0 */ - : cosx * (y0 - y1); /* cos(x) * (y0 - y1) */ -} - -void Lengths(const struct geod_geodesic* g, - real eps, real sig12, - real ssig1, real csig1, real dn1, - real ssig2, real csig2, real dn2, - real cbet1, real cbet2, - real* ps12b, real* pm12b, real* pm0, - real* pM12, real* pM21, - /* Scratch area of the right size */ - real Ca[]) { - real m0 = 0, J12 = 0, A1 = 0, A2 = 0; - real Cb[nC]; - - /* Return m12b = (reduced length)/b; also calculate s12b = distance/b, - * and m0 = coefficient of secular term in expression for reduced length. */ - boolx redlp = pm12b || pm0 || pM12 || pM21; - if (ps12b || redlp) { - A1 = A1m1f(eps); - C1f(eps, Ca); - if (redlp) { - A2 = A2m1f(eps); - C2f(eps, Cb); - m0 = A1 - A2; - A2 = 1 + A2; - } - A1 = 1 + A1; - } - if (ps12b) { - real B1 = SinCosSeries(TRUE, ssig2, csig2, Ca, nC1) - - SinCosSeries(TRUE, ssig1, csig1, Ca, nC1); - /* Missing a factor of b */ - *ps12b = A1 * (sig12 + B1); - if (redlp) { - real B2 = SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - - SinCosSeries(TRUE, ssig1, csig1, Cb, nC2); - J12 = m0 * sig12 + (A1 * B1 - A2 * B2); - } - } else if (redlp) { - /* Assume here that nC1 >= nC2 */ - int l; - for (l = 1; l <= nC2; ++l) - Cb[l] = A1 * Ca[l] - A2 * Cb[l]; - J12 = m0 * sig12 + (SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - - SinCosSeries(TRUE, ssig1, csig1, Cb, nC2)); - } - if (pm0) *pm0 = m0; - if (pm12b) - /* Missing a factor of b. - * Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure - * accurate cancellation in the case of coincident points. */ - *pm12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - - csig1 * csig2 * J12; - if (pM12 || pM21) { - real csig12 = csig1 * csig2 + ssig1 * ssig2; - real t = g->ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2); - if (pM12) - *pM12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1; - if (pM21) - *pM21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2; - } -} - -real Astroid(real x, real y) { - /* Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k. - * This solution is adapted from Geocentric::Reverse. */ - real k; - real - p = sq(x), - q = sq(y), - r = (p + q - 1) / 6; - if ( !(q == 0 && r <= 0) ) { - real - /* Avoid possible division by zero when r = 0 by multiplying equations - * for s and t by r^3 and r, resp. */ - S = p * q / 4, /* S = r^3 * s */ - r2 = sq(r), - r3 = r * r2, - /* The discriminant of the quadratic equation for T3. This is zero on - * the evolute curve p^(1/3)+q^(1/3) = 1 */ - disc = S * (S + 2 * r3); - real u = r; - real v, uv, w; - if (disc >= 0) { - real T3 = S + r3, T; - /* Pick the sign on the sqrt to maximize abs(T3). This minimizes loss - * of precision due to cancellation. The result is unchanged because - * of the way the T is used in definition of u. */ - T3 += T3 < 0 ? -sqrt(disc) : sqrt(disc); /* T3 = (r * t)^3 */ - /* N.B. cbrtx always returns the real root. cbrtx(-8) = -2. */ - T = cbrtx(T3); /* T = r * t */ - /* T can be zero; but then r2 / T -> 0. */ - u += T + (T != 0 ? r2 / T : 0); - } else { - /* T is complex, but the way u is defined the result is real. */ - real ang = atan2(sqrt(-disc), -(S + r3)); - /* There are three possible cube roots. We choose the root which - * avoids cancellation. Note that disc < 0 implies that r < 0. */ - u += 2 * r * cos(ang / 3); - } - v = sqrt(sq(u) + q); /* guaranteed positive */ - /* Avoid loss of accuracy when u < 0. */ - uv = u < 0 ? q / (v - u) : u + v; /* u+v, guaranteed positive */ - w = (uv - q) / (2 * v); /* positive? */ - /* Rearrange expression for k to avoid loss of accuracy due to - * subtraction. Division by 0 not possible because uv > 0, w >= 0. */ - k = uv / (sqrt(uv + sq(w)) + w); /* guaranteed positive */ - } else { /* q == 0 && r <= 0 */ - /* y = 0 with |x| <= 1. Handle this case directly. - * for y small, positive root is k = abs(y)/sqrt(1-x^2) */ - k = 0; - } - return k; -} - -real InverseStart(const struct geod_geodesic* g, - real sbet1, real cbet1, real dn1, - real sbet2, real cbet2, real dn2, - real lam12, real slam12, real clam12, - real* psalp1, real* pcalp1, - /* Only updated if return val >= 0 */ - real* psalp2, real* pcalp2, - /* Only updated for short lines */ - real* pdnm, - /* Scratch area of the right size */ - real Ca[]) { - real salp1 = 0, calp1 = 0, salp2 = 0, calp2 = 0, dnm = 0; - - /* Return a starting point for Newton's method in salp1 and calp1 (function - * value is -1). If Newton's method doesn't need to be used, return also - * salp2 and calp2 and function value is sig12. */ - real - sig12 = -1, /* Return value */ - /* bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0] */ - sbet12 = sbet2 * cbet1 - cbet2 * sbet1, - cbet12 = cbet2 * cbet1 + sbet2 * sbet1; - real sbet12a; - boolx shortline = cbet12 >= 0 && sbet12 < (real)(0.5) && - cbet2 * lam12 < (real)(0.5); - real somg12, comg12, ssig12, csig12; -#if defined(__GNUC__) && __GNUC__ == 4 && \ - (__GNUC_MINOR__ < 6 || defined(__MINGW32__)) - /* Volatile declaration needed to fix inverse cases - * 88.202499451857 0 -88.202499451857 179.981022032992859592 - * 89.262080389218 0 -89.262080389218 179.992207982775375662 - * 89.333123580033 0 -89.333123580032997687 179.99295812360148422 - * which otherwise fail with g++ 4.4.4 x86 -O3 (Linux) - * and g++ 4.4.0 (mingw) and g++ 4.6.1 (tdm mingw). */ - { - volatile real xx1 = sbet2 * cbet1; - volatile real xx2 = cbet2 * sbet1; - sbet12a = xx1 + xx2; - } -#else - sbet12a = sbet2 * cbet1 + cbet2 * sbet1; -#endif - if (shortline) { - real sbetm2 = sq(sbet1 + sbet2), omg12; - /* sin((bet1+bet2)/2)^2 - * = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2) */ - sbetm2 /= sbetm2 + sq(cbet1 + cbet2); - dnm = sqrt(1 + g->ep2 * sbetm2); - omg12 = lam12 / (g->f1 * dnm); - somg12 = sin(omg12); comg12 = cos(omg12); - } else { - somg12 = slam12; comg12 = clam12; - } - - salp1 = cbet2 * somg12; - calp1 = comg12 >= 0 ? - sbet12 + cbet2 * sbet1 * sq(somg12) / (1 + comg12) : - sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); - - ssig12 = hypotx(salp1, calp1); - csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12; - - if (shortline && ssig12 < g->etol2) { - /* really short lines */ - salp2 = cbet1 * somg12; - calp2 = sbet12 - cbet1 * sbet2 * - (comg12 >= 0 ? sq(somg12) / (1 + comg12) : 1 - comg12); - norm2(&salp2, &calp2); - /* Set return value */ - sig12 = atan2(ssig12, csig12); - } else if (fabs(g->n) > (real)(0.1) || /* No astroid calc if too eccentric */ - csig12 >= 0 || - ssig12 >= 6 * fabs(g->n) * pi * sq(cbet1)) { - /* Nothing to do, zeroth order spherical approximation is OK */ - } else { - /* Scale lam12 and bet2 to x, y coordinate system where antipodal point - * is at origin and singular point is at y = 0, x = -1. */ - real y, lamscale, betscale; - /* Volatile declaration needed to fix inverse case - * 56.320923501171 0 -56.320923501171 179.664747671772880215 - * which otherwise fails with g++ 4.4.4 x86 -O3 */ - volatile real x; - real lam12x = atan2(-slam12, -clam12); /* lam12 - pi */ - if (g->f >= 0) { /* In fact f == 0 does not get here */ - /* x = dlong, y = dlat */ - { - real - k2 = sq(sbet1) * g->ep2, - eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); - lamscale = g->f * cbet1 * A3f(g, eps) * pi; - } - betscale = lamscale * cbet1; - - x = lam12x / lamscale; - y = sbet12a / betscale; - } else { /* f < 0 */ - /* x = dlat, y = dlong */ - real - cbet12a = cbet2 * cbet1 - sbet2 * sbet1, - bet12a = atan2(sbet12a, cbet12a); - real m12b, m0; - /* In the case of lon12 = 180, this repeats a calculation made in - * Inverse. */ - Lengths(g, g->n, pi + bet12a, - sbet1, -cbet1, dn1, sbet2, cbet2, dn2, - cbet1, cbet2, 0, &m12b, &m0, 0, 0, Ca); - x = -1 + m12b / (cbet1 * cbet2 * m0 * pi); - betscale = x < -(real)(0.01) ? sbet12a / x : - -g->f * sq(cbet1) * pi; - lamscale = betscale / cbet1; - y = lam12x / lamscale; - } - - if (y > -tol1 && x > -1 - xthresh) { - /* strip near cut */ - if (g->f >= 0) { - salp1 = minx((real)(1), -(real)(x)); calp1 = - sqrt(1 - sq(salp1)); - } else { - calp1 = maxx((real)(x > -tol1 ? 0 : -1), (real)(x)); - salp1 = sqrt(1 - sq(calp1)); - } - } else { - /* Estimate alp1, by solving the astroid problem. - * - * Could estimate alpha1 = theta + pi/2, directly, i.e., - * calp1 = y/k; salp1 = -x/(1+k); for f >= 0 - * calp1 = x/(1+k); salp1 = -y/k; for f < 0 (need to check) - * - * However, it's better to estimate omg12 from astroid and use - * spherical formula to compute alp1. This reduces the mean number of - * Newton iterations for astroid cases from 2.24 (min 0, max 6) to 2.12 - * (min 0 max 5). The changes in the number of iterations are as - * follows: - * - * change percent - * 1 5 - * 0 78 - * -1 16 - * -2 0.6 - * -3 0.04 - * -4 0.002 - * - * The histogram of iterations is (m = number of iterations estimating - * alp1 directly, n = number of iterations estimating via omg12, total - * number of trials = 148605): - * - * iter m n - * 0 148 186 - * 1 13046 13845 - * 2 93315 102225 - * 3 36189 32341 - * 4 5396 7 - * 5 455 1 - * 6 56 0 - * - * Because omg12 is near pi, estimate work with omg12a = pi - omg12 */ - real k = Astroid(x, y); - real - omg12a = lamscale * ( g->f >= 0 ? -x * k/(1 + k) : -y * (1 + k)/k ); - somg12 = sin(omg12a); comg12 = -cos(omg12a); - /* Update spherical estimate of alp1 using omg12 instead of lam12 */ - salp1 = cbet2 * somg12; - calp1 = sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); - } - } - /* Sanity check on starting guess. Backwards check allows NaN through. */ - if (!(salp1 <= 0)) - norm2(&salp1, &calp1); - else { - salp1 = 1; calp1 = 0; - } - - *psalp1 = salp1; - *pcalp1 = calp1; - if (shortline) - *pdnm = dnm; - if (sig12 >= 0) { - *psalp2 = salp2; - *pcalp2 = calp2; - } - return sig12; -} - -real Lambda12(const struct geod_geodesic* g, - real sbet1, real cbet1, real dn1, - real sbet2, real cbet2, real dn2, - real salp1, real calp1, - real slam120, real clam120, - real* psalp2, real* pcalp2, - real* psig12, - real* pssig1, real* pcsig1, - real* pssig2, real* pcsig2, - real* peps, - real* pdomg12, - boolx diffp, real* pdlam12, - /* Scratch area of the right size */ - real Ca[]) { - real salp2 = 0, calp2 = 0, sig12 = 0, - ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, - domg12 = 0, dlam12 = 0; - real salp0, calp0; - real somg1, comg1, somg2, comg2, somg12, comg12, lam12; - real B312, eta, k2; - - if (sbet1 == 0 && calp1 == 0) - /* Break degeneracy of equatorial line. This case has already been - * handled. */ - calp1 = -tiny; - - /* sin(alp1) * cos(bet1) = sin(alp0) */ - salp0 = salp1 * cbet1; - calp0 = hypotx(calp1, salp1 * sbet1); /* calp0 > 0 */ - - /* tan(bet1) = tan(sig1) * cos(alp1) - * tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1) */ - ssig1 = sbet1; somg1 = salp0 * sbet1; - csig1 = comg1 = calp1 * cbet1; - norm2(&ssig1, &csig1); - /* norm2(&somg1, &comg1); -- don't need to normalize! */ - - /* Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful - * about this case, since this can yield singularities in the Newton - * iteration. - * sin(alp2) * cos(bet2) = sin(alp0) */ - salp2 = cbet2 != cbet1 ? salp0 / cbet2 : salp1; - /* calp2 = sqrt(1 - sq(salp2)) - * = sqrt(sq(calp0) - sq(sbet2)) / cbet2 - * and subst for calp0 and rearrange to give (choose positive sqrt - * to give alp2 in [0, pi/2]). */ - calp2 = cbet2 != cbet1 || fabs(sbet2) != -sbet1 ? - sqrt(sq(calp1 * cbet1) + - (cbet1 < -sbet1 ? - (cbet2 - cbet1) * (cbet1 + cbet2) : - (sbet1 - sbet2) * (sbet1 + sbet2))) / cbet2 : - fabs(calp1); - /* tan(bet2) = tan(sig2) * cos(alp2) - * tan(omg2) = sin(alp0) * tan(sig2). */ - ssig2 = sbet2; somg2 = salp0 * sbet2; - csig2 = comg2 = calp2 * cbet2; - norm2(&ssig2, &csig2); - /* norm2(&somg2, &comg2); -- don't need to normalize! */ - - /* sig12 = sig2 - sig1, limit to [0, pi] */ - sig12 = atan2(maxx((real)(0), csig1 * ssig2 - ssig1 * csig2), - csig1 * csig2 + ssig1 * ssig2); - - /* omg12 = omg2 - omg1, limit to [0, pi] */ - somg12 = maxx((real)(0), comg1 * somg2 - somg1 * comg2); - comg12 = comg1 * comg2 + somg1 * somg2; - /* eta = omg12 - lam120 */ - eta = atan2(somg12 * clam120 - comg12 * slam120, - comg12 * clam120 + somg12 * slam120); - k2 = sq(calp0) * g->ep2; - eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); - C3f(g, eps, Ca); - B312 = (SinCosSeries(TRUE, ssig2, csig2, Ca, nC3-1) - - SinCosSeries(TRUE, ssig1, csig1, Ca, nC3-1)); - domg12 = -g->f * A3f(g, eps) * salp0 * (sig12 + B312); - lam12 = eta + domg12; - - if (diffp) { - if (calp2 == 0) - dlam12 = - 2 * g->f1 * dn1 / sbet1; - else { - Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, 0, &dlam12, 0, 0, 0, Ca); - dlam12 *= g->f1 / (calp2 * cbet2); - } - } - - *psalp2 = salp2; - *pcalp2 = calp2; - *psig12 = sig12; - *pssig1 = ssig1; - *pcsig1 = csig1; - *pssig2 = ssig2; - *pcsig2 = csig2; - *peps = eps; - *pdomg12 = domg12; - if (diffp) - *pdlam12 = dlam12; - - return lam12; -} - -real A3f(const struct geod_geodesic* g, real eps) { - /* Evaluate A3 */ - return polyval(nA3 - 1, g->A3x, eps); -} - -void C3f(const struct geod_geodesic* g, real eps, real c[]) { - /* Evaluate C3 coeffs - * Elements c[1] through c[nC3 - 1] are set */ - real mult = 1; - int o = 0, l; - for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ - int m = nC3 - l - 1; /* order of polynomial in eps */ - mult *= eps; - c[l] = mult * polyval(m, g->C3x + o, eps); - o += m + 1; - } -} - -void C4f(const struct geod_geodesic* g, real eps, real c[]) { - /* Evaluate C4 coeffs - * Elements c[0] through c[nC4 - 1] are set */ - real mult = 1; - int o = 0, l; - for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ - int m = nC4 - l - 1; /* order of polynomial in eps */ - c[l] = mult * polyval(m, g->C4x + o, eps); - o += m + 1; - mult *= eps; - } -} - -/* The scale factor A1-1 = mean value of (d/dsigma)I1 - 1 */ -real A1m1f(real eps) { - static const real coeff[] = { - /* (1-eps)*A1-1, polynomial in eps2 of order 3 */ - 1, 4, 64, 0, 256, - }; - int m = nA1/2; - real t = polyval(m, coeff, sq(eps)) / coeff[m + 1]; - return (t + eps) / (1 - eps); -} - -/* The coefficients C1[l] in the Fourier expansion of B1 */ -void C1f(real eps, real c[]) { - static const real coeff[] = { - /* C1[1]/eps^1, polynomial in eps2 of order 2 */ - -1, 6, -16, 32, - /* C1[2]/eps^2, polynomial in eps2 of order 2 */ - -9, 64, -128, 2048, - /* C1[3]/eps^3, polynomial in eps2 of order 1 */ - 9, -16, 768, - /* C1[4]/eps^4, polynomial in eps2 of order 1 */ - 3, -5, 512, - /* C1[5]/eps^5, polynomial in eps2 of order 0 */ - -7, 1280, - /* C1[6]/eps^6, polynomial in eps2 of order 0 */ - -7, 2048, - }; - real - eps2 = sq(eps), - d = eps; - int o = 0, l; - for (l = 1; l <= nC1; ++l) { /* l is index of C1p[l] */ - int m = (nC1 - l) / 2; /* order of polynomial in eps^2 */ - c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; - o += m + 2; - d *= eps; - } -} - -/* The coefficients C1p[l] in the Fourier expansion of B1p */ -void C1pf(real eps, real c[]) { - static const real coeff[] = { - /* C1p[1]/eps^1, polynomial in eps2 of order 2 */ - 205, -432, 768, 1536, - /* C1p[2]/eps^2, polynomial in eps2 of order 2 */ - 4005, -4736, 3840, 12288, - /* C1p[3]/eps^3, polynomial in eps2 of order 1 */ - -225, 116, 384, - /* C1p[4]/eps^4, polynomial in eps2 of order 1 */ - -7173, 2695, 7680, - /* C1p[5]/eps^5, polynomial in eps2 of order 0 */ - 3467, 7680, - /* C1p[6]/eps^6, polynomial in eps2 of order 0 */ - 38081, 61440, - }; - real - eps2 = sq(eps), - d = eps; - int o = 0, l; - for (l = 1; l <= nC1p; ++l) { /* l is index of C1p[l] */ - int m = (nC1p - l) / 2; /* order of polynomial in eps^2 */ - c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; - o += m + 2; - d *= eps; - } -} - -/* The scale factor A2-1 = mean value of (d/dsigma)I2 - 1 */ -real A2m1f(real eps) { - static const real coeff[] = { - /* (eps+1)*A2-1, polynomial in eps2 of order 3 */ - -11, -28, -192, 0, 256, - }; - int m = nA2/2; - real t = polyval(m, coeff, sq(eps)) / coeff[m + 1]; - return (t - eps) / (1 + eps); -} - -/* The coefficients C2[l] in the Fourier expansion of B2 */ -void C2f(real eps, real c[]) { - static const real coeff[] = { - /* C2[1]/eps^1, polynomial in eps2 of order 2 */ - 1, 2, 16, 32, - /* C2[2]/eps^2, polynomial in eps2 of order 2 */ - 35, 64, 384, 2048, - /* C2[3]/eps^3, polynomial in eps2 of order 1 */ - 15, 80, 768, - /* C2[4]/eps^4, polynomial in eps2 of order 1 */ - 7, 35, 512, - /* C2[5]/eps^5, polynomial in eps2 of order 0 */ - 63, 1280, - /* C2[6]/eps^6, polynomial in eps2 of order 0 */ - 77, 2048, - }; - real - eps2 = sq(eps), - d = eps; - int o = 0, l; - for (l = 1; l <= nC2; ++l) { /* l is index of C2[l] */ - int m = (nC2 - l) / 2; /* order of polynomial in eps^2 */ - c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; - o += m + 2; - d *= eps; - } -} - -/* The scale factor A3 = mean value of (d/dsigma)I3 */ -void A3coeff(struct geod_geodesic* g) { - static const real coeff[] = { - /* A3, coeff of eps^5, polynomial in n of order 0 */ - -3, 128, - /* A3, coeff of eps^4, polynomial in n of order 1 */ - -2, -3, 64, - /* A3, coeff of eps^3, polynomial in n of order 2 */ - -1, -3, -1, 16, - /* A3, coeff of eps^2, polynomial in n of order 2 */ - 3, -1, -2, 8, - /* A3, coeff of eps^1, polynomial in n of order 1 */ - 1, -1, 2, - /* A3, coeff of eps^0, polynomial in n of order 0 */ - 1, 1, - }; - int o = 0, k = 0, j; - for (j = nA3 - 1; j >= 0; --j) { /* coeff of eps^j */ - int m = nA3 - j - 1 < j ? nA3 - j - 1 : j; /* order of polynomial in n */ - g->A3x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; - o += m + 2; - } -} - -/* The coefficients C3[l] in the Fourier expansion of B3 */ -void C3coeff(struct geod_geodesic* g) { - static const real coeff[] = { - /* C3[1], coeff of eps^5, polynomial in n of order 0 */ - 3, 128, - /* C3[1], coeff of eps^4, polynomial in n of order 1 */ - 2, 5, 128, - /* C3[1], coeff of eps^3, polynomial in n of order 2 */ - -1, 3, 3, 64, - /* C3[1], coeff of eps^2, polynomial in n of order 2 */ - -1, 0, 1, 8, - /* C3[1], coeff of eps^1, polynomial in n of order 1 */ - -1, 1, 4, - /* C3[2], coeff of eps^5, polynomial in n of order 0 */ - 5, 256, - /* C3[2], coeff of eps^4, polynomial in n of order 1 */ - 1, 3, 128, - /* C3[2], coeff of eps^3, polynomial in n of order 2 */ - -3, -2, 3, 64, - /* C3[2], coeff of eps^2, polynomial in n of order 2 */ - 1, -3, 2, 32, - /* C3[3], coeff of eps^5, polynomial in n of order 0 */ - 7, 512, - /* C3[3], coeff of eps^4, polynomial in n of order 1 */ - -10, 9, 384, - /* C3[3], coeff of eps^3, polynomial in n of order 2 */ - 5, -9, 5, 192, - /* C3[4], coeff of eps^5, polynomial in n of order 0 */ - 7, 512, - /* C3[4], coeff of eps^4, polynomial in n of order 1 */ - -14, 7, 512, - /* C3[5], coeff of eps^5, polynomial in n of order 0 */ - 21, 2560, - }; - int o = 0, k = 0, l, j; - for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ - for (j = nC3 - 1; j >= l; --j) { /* coeff of eps^j */ - int m = nC3 - j - 1 < j ? nC3 - j - 1 : j; /* order of polynomial in n */ - g->C3x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; - o += m + 2; - } - } -} - -/* The coefficients C4[l] in the Fourier expansion of I4 */ -void C4coeff(struct geod_geodesic* g) { - static const real coeff[] = { - /* C4[0], coeff of eps^5, polynomial in n of order 0 */ - 97, 15015, - /* C4[0], coeff of eps^4, polynomial in n of order 1 */ - 1088, 156, 45045, - /* C4[0], coeff of eps^3, polynomial in n of order 2 */ - -224, -4784, 1573, 45045, - /* C4[0], coeff of eps^2, polynomial in n of order 3 */ - -10656, 14144, -4576, -858, 45045, - /* C4[0], coeff of eps^1, polynomial in n of order 4 */ - 64, 624, -4576, 6864, -3003, 15015, - /* C4[0], coeff of eps^0, polynomial in n of order 5 */ - 100, 208, 572, 3432, -12012, 30030, 45045, - /* C4[1], coeff of eps^5, polynomial in n of order 0 */ - 1, 9009, - /* C4[1], coeff of eps^4, polynomial in n of order 1 */ - -2944, 468, 135135, - /* C4[1], coeff of eps^3, polynomial in n of order 2 */ - 5792, 1040, -1287, 135135, - /* C4[1], coeff of eps^2, polynomial in n of order 3 */ - 5952, -11648, 9152, -2574, 135135, - /* C4[1], coeff of eps^1, polynomial in n of order 4 */ - -64, -624, 4576, -6864, 3003, 135135, - /* C4[2], coeff of eps^5, polynomial in n of order 0 */ - 8, 10725, - /* C4[2], coeff of eps^4, polynomial in n of order 1 */ - 1856, -936, 225225, - /* C4[2], coeff of eps^3, polynomial in n of order 2 */ - -8448, 4992, -1144, 225225, - /* C4[2], coeff of eps^2, polynomial in n of order 3 */ - -1440, 4160, -4576, 1716, 225225, - /* C4[3], coeff of eps^5, polynomial in n of order 0 */ - -136, 63063, - /* C4[3], coeff of eps^4, polynomial in n of order 1 */ - 1024, -208, 105105, - /* C4[3], coeff of eps^3, polynomial in n of order 2 */ - 3584, -3328, 1144, 315315, - /* C4[4], coeff of eps^5, polynomial in n of order 0 */ - -128, 135135, - /* C4[4], coeff of eps^4, polynomial in n of order 1 */ - -2560, 832, 405405, - /* C4[5], coeff of eps^5, polynomial in n of order 0 */ - 128, 99099, - }; - int o = 0, k = 0, l, j; - for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ - for (j = nC4 - 1; j >= l; --j) { /* coeff of eps^j */ - int m = nC4 - j - 1; /* order of polynomial in n */ - g->C4x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; - o += m + 2; - } - } -} - -int transit(real lon1, real lon2) { - real lon12; - /* Return 1 or -1 if crossing prime meridian in east or west direction. - * Otherwise return zero. */ - /* Compute lon12 the same way as Geodesic::Inverse. */ - lon1 = AngNormalize(lon1); - lon2 = AngNormalize(lon2); - lon12 = AngDiff(lon1, lon2, 0); - return lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 : - (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0); -} - -int transitdirect(real lon1, real lon2) { -#if HAVE_C99_MATH - lon1 = remainder(lon1, (real)(720)); - lon2 = remainder(lon2, (real)(720)); - return ( (lon2 <= 0 && lon2 > -360 ? 1 : 0) - - (lon1 <= 0 && lon1 > -360 ? 1 : 0) ); -#else - lon1 = fmod(lon1, (real)(720)); - lon2 = fmod(lon2, (real)(720)); - return ( ((lon2 <= 0 && lon2 > -360) || lon2 > 360 ? 1 : 0) - - ((lon1 <= 0 && lon1 > -360) || lon1 > 360 ? 1 : 0) ); -#endif -} - -void accini(real s[]) { - /* Initialize an accumulator; this is an array with two elements. */ - s[0] = s[1] = 0; -} - -void acccopy(const real s[], real t[]) { - /* Copy an accumulator; t = s. */ - t[0] = s[0]; t[1] = s[1]; -} - -void accadd(real s[], real y) { - /* Add y to an accumulator. */ - real u, z = sumx(y, s[1], &u); - s[0] = sumx(z, s[0], &s[1]); - if (s[0] == 0) - s[0] = u; - else - s[1] = s[1] + u; -} - -real accsum(const real s[], real y) { - /* Return accumulator + y (but don't add to accumulator). */ - real t[2]; - acccopy(s, t); - accadd(t, y); - return t[0]; -} - -void accneg(real s[]) { - /* Negate an accumulator. */ - s[0] = -s[0]; s[1] = -s[1]; -} - -void geod_polygon_init(struct geod_polygon* p, boolx polylinep) { - p->polyline = (polylinep != 0); - geod_polygon_clear(p); -} - -void geod_polygon_clear(struct geod_polygon* p) { - p->lat0 = p->lon0 = p->lat = p->lon = NaN; - accini(p->P); - accini(p->A); - p->num = p->crossings = 0; -} - -void geod_polygon_addpoint(const struct geod_geodesic* g, - struct geod_polygon* p, - real lat, real lon) { - lon = AngNormalize(lon); - if (p->num == 0) { - p->lat0 = p->lat = lat; - p->lon0 = p->lon = lon; - } else { - real s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ - geod_geninverse(g, p->lat, p->lon, lat, lon, - &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); - accadd(p->P, s12); - if (!p->polyline) { - accadd(p->A, S12); - p->crossings += transit(p->lon, lon); - } - p->lat = lat; p->lon = lon; - } - ++p->num; -} - -void geod_polygon_addedge(const struct geod_geodesic* g, - struct geod_polygon* p, - real azi, real s) { - if (p->num) { /* Do nothing is num is zero */ - /* Initialize S12 to stop Visual Studio warning. Initialization of lat and - * lon is to make CLang static analyzer happy. */ - real lat = 0, lon = 0, S12 = 0; - geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, - &lat, &lon, 0, - 0, 0, 0, 0, p->polyline ? 0 : &S12); - accadd(p->P, s); - if (!p->polyline) { - accadd(p->A, S12); - p->crossings += transitdirect(p->lon, lon); - } - p->lat = lat; p->lon = lon; - ++p->num; - } -} - -unsigned geod_polygon_compute(const struct geod_geodesic* g, - const struct geod_polygon* p, - boolx reverse, boolx sign, - real* pA, real* pP) { - real s12, S12, t[2], area0; - int crossings; - if (p->num < 2) { - if (pP) *pP = 0; - if (!p->polyline && pA) *pA = 0; - return p->num; - } - if (p->polyline) { - if (pP) *pP = p->P[0]; - return p->num; - } - geod_geninverse(g, p->lat, p->lon, p->lat0, p->lon0, - &s12, 0, 0, 0, 0, 0, &S12); - if (pP) *pP = accsum(p->P, s12); - acccopy(p->A, t); - accadd(t, S12); - crossings = p->crossings + transit(p->lon, p->lon0); - area0 = 4 * pi * g->c2; - if (crossings & 1) - accadd(t, (t[0] < 0 ? 1 : -1) * area0/2); - /* area is with the clockwise sense. If !reverse convert to - * counter-clockwise convention. */ - if (!reverse) - accneg(t); - /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ - if (sign) { - if (t[0] > area0/2) - accadd(t, -area0); - else if (t[0] <= -area0/2) - accadd(t, +area0); - } else { - if (t[0] >= area0) - accadd(t, -area0); - else if (t[0] < 0) - accadd(t, +area0); - } - if (pA) *pA = 0 + t[0]; - return p->num; -} - -unsigned geod_polygon_testpoint(const struct geod_geodesic* g, - const struct geod_polygon* p, - real lat, real lon, - boolx reverse, boolx sign, - real* pA, real* pP) { - real perimeter, tempsum, area0; - int crossings, i; - unsigned num = p->num + 1; - if (num == 1) { - if (pP) *pP = 0; - if (!p->polyline && pA) *pA = 0; - return num; - } - perimeter = p->P[0]; - tempsum = p->polyline ? 0 : p->A[0]; - crossings = p->crossings; - for (i = 0; i < (p->polyline ? 1 : 2); ++i) { - real s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ - geod_geninverse(g, - i == 0 ? p->lat : lat, i == 0 ? p->lon : lon, - i != 0 ? p->lat0 : lat, i != 0 ? p->lon0 : lon, - &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); - perimeter += s12; - if (!p->polyline) { - tempsum += S12; - crossings += transit(i == 0 ? p->lon : lon, - i != 0 ? p->lon0 : lon); - } - } - - if (pP) *pP = perimeter; - if (p->polyline) - return num; - - area0 = 4 * pi * g->c2; - if (crossings & 1) - tempsum += (tempsum < 0 ? 1 : -1) * area0/2; - /* area is with the clockwise sense. If !reverse convert to - * counter-clockwise convention. */ - if (!reverse) - tempsum *= -1; - /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ - if (sign) { - if (tempsum > area0/2) - tempsum -= area0; - else if (tempsum <= -area0/2) - tempsum += area0; - } else { - if (tempsum >= area0) - tempsum -= area0; - else if (tempsum < 0) - tempsum += area0; - } - if (pA) *pA = 0 + tempsum; - return num; -} - -unsigned geod_polygon_testedge(const struct geod_geodesic* g, - const struct geod_polygon* p, - real azi, real s, - boolx reverse, boolx sign, - real* pA, real* pP) { - real perimeter, tempsum, area0; - int crossings; - unsigned num = p->num + 1; - if (num == 1) { /* we don't have a starting point! */ - if (pP) *pP = NaN; - if (!p->polyline && pA) *pA = NaN; - return 0; - } - perimeter = p->P[0] + s; - if (p->polyline) { - if (pP) *pP = perimeter; - return num; - } - - tempsum = p->A[0]; - crossings = p->crossings; - { - /* Initialization of lat, lon, and S12 is to make CLang static analyzer - happy. */ - real lat = 0, lon = 0, s12, S12 = 0; - geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, - &lat, &lon, 0, - 0, 0, 0, 0, &S12); - tempsum += S12; - crossings += transitdirect(p->lon, lon); - geod_geninverse(g, lat, lon, p->lat0, p->lon0, - &s12, 0, 0, 0, 0, 0, &S12); - perimeter += s12; - tempsum += S12; - crossings += transit(lon, p->lon0); - } - - area0 = 4 * pi * g->c2; - if (crossings & 1) - tempsum += (tempsum < 0 ? 1 : -1) * area0/2; - /* area is with the clockwise sense. If !reverse convert to - * counter-clockwise convention. */ - if (!reverse) - tempsum *= -1; - /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ - if (sign) { - if (tempsum > area0/2) - tempsum -= area0; - else if (tempsum <= -area0/2) - tempsum += area0; - } else { - if (tempsum >= area0) - tempsum -= area0; - else if (tempsum < 0) - tempsum += area0; - } - if (pP) *pP = perimeter; - if (pA) *pA = 0 + tempsum; - return num; -} - -void geod_polygonarea(const struct geod_geodesic* g, - real lats[], real lons[], int n, - real* pA, real* pP) { - int i; - struct geod_polygon p; - geod_polygon_init(&p, FALSE); - for (i = 0; i < n; ++i) - geod_polygon_addpoint(g, &p, lats[i], lons[i]); - geod_polygon_compute(g, &p, FALSE, TRUE, pA, pP); -} - -/** @endcond */ diff --git a/src/geodesic.cpp b/src/geodesic.cpp new file mode 100644 index 00000000..220dcd7f --- /dev/null +++ b/src/geodesic.cpp @@ -0,0 +1,2100 @@ +/** + * \file geodesic.c + * \brief Implementation of the geodesic routines in C + * + * For the full documentation see geodesic.h. + **********************************************************************/ + +/** @cond SKIP */ + +/* + * This is a C implementation of the geodesic algorithms described in + * + * C. F. F. Karney, + * Algorithms for geodesics, + * J. Geodesy 87, 43--55 (2013); + * https://doi.org/10.1007/s00190-012-0578-z + * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html + * + * See the comments in geodesic.h for documentation. + * + * Copyright (c) Charles Karney (2012-2018) and licensed + * under the MIT/X11 License. For more information, see + * https://geographiclib.sourceforge.io/ + */ + +#include "geodesic.h" +#ifdef PJ_LIB__ +#include "proj_math.h" +#else +#include +#endif + +#if !defined(HAVE_C99_MATH) +#define HAVE_C99_MATH 0 +#endif + +#define GEOGRAPHICLIB_GEODESIC_ORDER 6 +#define nA1 GEOGRAPHICLIB_GEODESIC_ORDER +#define nC1 GEOGRAPHICLIB_GEODESIC_ORDER +#define nC1p GEOGRAPHICLIB_GEODESIC_ORDER +#define nA2 GEOGRAPHICLIB_GEODESIC_ORDER +#define nC2 GEOGRAPHICLIB_GEODESIC_ORDER +#define nA3 GEOGRAPHICLIB_GEODESIC_ORDER +#define nA3x nA3 +#define nC3 GEOGRAPHICLIB_GEODESIC_ORDER +#define nC3x ((nC3 * (nC3 - 1)) / 2) +#define nC4 GEOGRAPHICLIB_GEODESIC_ORDER +#define nC4x ((nC4 * (nC4 + 1)) / 2) +#define nC (GEOGRAPHICLIB_GEODESIC_ORDER + 1) + +typedef double real; +typedef int boolx; + +static unsigned init = 0; +static const int FALSE = 0; +static const int TRUE = 1; +static unsigned digits, maxit1, maxit2; +static real epsilon, realmin, pi, degree, NaN, + tiny, tol0, tol1, tol2, tolb, xthresh; + +static void Init() { + if (!init) { +#if defined(__DBL_MANT_DIG__) + digits = __DBL_MANT_DIG__; +#else + digits = 53; +#endif +#if defined(__DBL_EPSILON__) + epsilon = __DBL_EPSILON__; +#else + epsilon = pow(0.5, digits - 1); +#endif +#if defined(__DBL_MIN__) + realmin = __DBL_MIN__; +#else + realmin = pow(0.5, 1022); +#endif +#if defined(M_PI) + pi = M_PI; +#else + pi = atan2(0.0, -1.0); +#endif + maxit1 = 20; + maxit2 = maxit1 + digits + 10; + tiny = sqrt(realmin); + tol0 = epsilon; + /* Increase multiplier in defn of tol1 from 100 to 200 to fix inverse case + * 52.784459512564 0 -52.784459512563990912 179.634407464943777557 + * which otherwise failed for Visual Studio 10 (Release and Debug) */ + tol1 = 200 * tol0; + tol2 = sqrt(tol0); + /* Check on bisection interval */ + tolb = tol0 * tol2; + xthresh = 1000 * tol2; + degree = pi/180; + #if defined(NAN) + NaN = NAN; + #else + { + real minus1 = -1; + /* cppcheck-suppress wrongmathcall */ + NaN = sqrt(minus1); + } + #endif + init = 1; + } +} + +enum captype { + CAP_NONE = 0U, + CAP_C1 = 1U<<0, + CAP_C1p = 1U<<1, + CAP_C2 = 1U<<2, + CAP_C3 = 1U<<3, + CAP_C4 = 1U<<4, + CAP_ALL = 0x1FU, + OUT_ALL = 0x7F80U +}; + +static real sq(real x) { return x * x; } +#if HAVE_C99_MATH +#define atanhx atanh +#define copysignx copysign +#define hypotx hypot +#define cbrtx cbrt +#else +static real log1px(real x) { + volatile real + y = 1 + x, + z = y - 1; + /* Here's the explanation for this magic: y = 1 + z, exactly, and z + * approx x, thus log(y)/z (which is nearly constant near z = 0) returns + * a good approximation to the true log(1 + x)/x. The multiplication x * + * (log(y)/z) introduces little additional error. */ + return z == 0 ? x : x * log(y) / z; +} + +static real atanhx(real x) { + real y = fabs(x); /* Enforce odd parity */ + y = log1px(2 * y/(1 - y))/2; + return x < 0 ? -y : y; +} + +static real copysignx(real x, real y) { + return fabs(x) * (y < 0 || (y == 0 && 1/y < 0) ? -1 : 1); +} + +static real hypotx(real x, real y) +{ return sqrt(x * x + y * y); } + +static real cbrtx(real x) { + real y = pow(fabs(x), 1/(real)(3)); /* Return the real cube root */ + return x < 0 ? -y : y; +} +#endif + +static real sumx(real u, real v, real* t) { + volatile real s = u + v; + volatile real up = s - v; + volatile real vpp = s - up; + up -= u; + vpp -= v; + if (t) *t = -(up + vpp); + /* error-free sum: + * u + v = s + t + * = round(u + v) + t */ + return s; +} + +static real polyval(int N, const real p[], real x) { + real y = N < 0 ? 0 : *p++; + while (--N >= 0) y = y * x + *p++; + return y; +} + +/* mimic C++ std::min and std::max */ +static real minx(real a, real b) +{ return (b < a) ? b : a; } + +static real maxx(real a, real b) +{ return (a < b) ? b : a; } + +static void swapx(real* x, real* y) +{ real t = *x; *x = *y; *y = t; } + +static void norm2(real* sinx, real* cosx) { + real r = hypotx(*sinx, *cosx); + *sinx /= r; + *cosx /= r; +} + +static real AngNormalize(real x) { +#if HAVE_C99_MATH + x = remainder(x, (real)(360)); + return x != -180 ? x : 180; +#else + real y = fmod(x, (real)(360)); +#if defined(_MSC_VER) && _MSC_VER < 1900 + /* + * Before version 14 (2015), Visual Studio had problems dealing + * with -0.0. Specifically + * VC 10,11,12 and 32-bit compile: fmod(-0.0, 360.0) -> +0.0 + * sincosdx has a similar fix. + * python 2.7 on Windows 32-bit machines has the same problem. + */ + if (x == 0) y = x; +#endif + return y <= -180 ? y + 360 : (y <= 180 ? y : y - 360); +#endif +} + +static real LatFix(real x) +{ return fabs(x) > 90 ? NaN : x; } + +static real AngDiff(real x, real y, real* e) { + real t, d = AngNormalize(sumx(AngNormalize(-x), AngNormalize(y), &t)); + /* Here y - x = d + t (mod 360), exactly, where d is in (-180,180] and + * abs(t) <= eps (eps = 2^-45 for doubles). The only case where the + * addition of t takes the result outside the range (-180,180] is d = 180 + * and t > 0. The case, d = -180 + eps, t = -eps, can't happen, since + * sum would have returned the exact result in such a case (i.e., given t + * = 0). */ + return sumx(d == 180 && t > 0 ? -180 : d, t, e); +} + +static real AngRound(real x) { + const real z = 1/(real)(16); + volatile real y; + if (x == 0) return 0; + y = fabs(x); + /* The compiler mustn't "simplify" z - (z - y) to y */ + y = y < z ? z - (z - y) : y; + return x < 0 ? -y : y; +} + +static void sincosdx(real x, real* sinx, real* cosx) { + /* In order to minimize round-off errors, this function exactly reduces + * the argument to the range [-45, 45] before converting it to radians. */ + real r, s, c; int q; +#if HAVE_C99_MATH && !defined(__GNUC__) + /* Disable for gcc because of bug in glibc version < 2.22, see + * https://sourceware.org/bugzilla/show_bug.cgi?id=17569 */ + r = remquo(x, (real)(90), &q); +#else + r = fmod(x, (real)(360)); + /* check for NaN */ + q = r == r ? (int)(floor(r / 90 + (real)(0.5))) : 0; + r -= 90 * q; +#endif + /* now abs(r) <= 45 */ + r *= degree; + /* Possibly could call the gnu extension sincos */ + s = sin(r); c = cos(r); +#if defined(_MSC_VER) && _MSC_VER < 1900 + /* + * Before version 14 (2015), Visual Studio had problems dealing + * with -0.0. Specifically + * VC 10,11,12 and 32-bit compile: fmod(-0.0, 360.0) -> +0.0 + * VC 12 and 64-bit compile: sin(-0.0) -> +0.0 + * AngNormalize has a similar fix. + * python 2.7 on Windows 32-bit machines has the same problem. + */ + if (x == 0) s = x; +#endif + switch ((unsigned)q & 3U) { + case 0U: *sinx = s; *cosx = c; break; + case 1U: *sinx = c; *cosx = -s; break; + case 2U: *sinx = -s; *cosx = -c; break; + default: *sinx = -c; *cosx = s; break; /* case 3U */ + } + if (x != 0) { *sinx += (real)(0); *cosx += (real)(0); } +} + +static real atan2dx(real y, real x) { + /* In order to minimize round-off errors, this function rearranges the + * arguments so that result of atan2 is in the range [-pi/4, pi/4] before + * converting it to degrees and mapping the result to the correct + * quadrant. */ + int q = 0; real ang; + if (fabs(y) > fabs(x)) { swapx(&x, &y); q = 2; } + if (x < 0) { x = -x; ++q; } + /* here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] */ + ang = atan2(y, x) / degree; + switch (q) { + /* Note that atan2d(-0.0, 1.0) will return -0. However, we expect that + * atan2d will not be called with y = -0. If need be, include + * + * case 0: ang = 0 + ang; break; + */ + case 1: ang = (y >= 0 ? 180 : -180) - ang; break; + case 2: ang = 90 - ang; break; + case 3: ang = -90 + ang; break; + } + return ang; +} + +static void A3coeff(struct geod_geodesic* g); +static void C3coeff(struct geod_geodesic* g); +static void C4coeff(struct geod_geodesic* g); +static real SinCosSeries(boolx sinp, + real sinx, real cosx, + const real c[], int n); +static void Lengths(const struct geod_geodesic* g, + real eps, real sig12, + real ssig1, real csig1, real dn1, + real ssig2, real csig2, real dn2, + real cbet1, real cbet2, + real* ps12b, real* pm12b, real* pm0, + real* pM12, real* pM21, + /* Scratch area of the right size */ + real Ca[]); +static real Astroid(real x, real y); +static real InverseStart(const struct geod_geodesic* g, + real sbet1, real cbet1, real dn1, + real sbet2, real cbet2, real dn2, + real lam12, real slam12, real clam12, + real* psalp1, real* pcalp1, + /* Only updated if return val >= 0 */ + real* psalp2, real* pcalp2, + /* Only updated for short lines */ + real* pdnm, + /* Scratch area of the right size */ + real Ca[]); +static real Lambda12(const struct geod_geodesic* g, + real sbet1, real cbet1, real dn1, + real sbet2, real cbet2, real dn2, + real salp1, real calp1, + real slam120, real clam120, + real* psalp2, real* pcalp2, + real* psig12, + real* pssig1, real* pcsig1, + real* pssig2, real* pcsig2, + real* peps, + real* pdomg12, + boolx diffp, real* pdlam12, + /* Scratch area of the right size */ + real Ca[]); +static real A3f(const struct geod_geodesic* g, real eps); +static void C3f(const struct geod_geodesic* g, real eps, real c[]); +static void C4f(const struct geod_geodesic* g, real eps, real c[]); +static real A1m1f(real eps); +static void C1f(real eps, real c[]); +static void C1pf(real eps, real c[]); +static real A2m1f(real eps); +static void C2f(real eps, real c[]); +static int transit(real lon1, real lon2); +static int transitdirect(real lon1, real lon2); +static void accini(real s[]); +static void acccopy(const real s[], real t[]); +static void accadd(real s[], real y); +static real accsum(const real s[], real y); +static void accneg(real s[]); + +void geod_init(struct geod_geodesic* g, real a, real f) { + if (!init) Init(); + g->a = a; + g->f = f; + g->f1 = 1 - g->f; + g->e2 = g->f * (2 - g->f); + g->ep2 = g->e2 / sq(g->f1); /* e2 / (1 - e2) */ + g->n = g->f / ( 2 - g->f); + g->b = g->a * g->f1; + g->c2 = (sq(g->a) + sq(g->b) * + (g->e2 == 0 ? 1 : + (g->e2 > 0 ? atanhx(sqrt(g->e2)) : atan(sqrt(-g->e2))) / + sqrt(fabs(g->e2))))/2; /* authalic radius squared */ + /* The sig12 threshold for "really short". Using the auxiliary sphere + * solution with dnm computed at (bet1 + bet2) / 2, the relative error in the + * azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2. (Error + * measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given f and + * sig12, the max error occurs for lines near the pole. If the old rule for + * computing dnm = (dn1 + dn2)/2 is used, then the error increases by a + * factor of 2.) Setting this equal to epsilon gives sig12 = etol2. Here + * 0.1 is a safety factor (error decreased by 100) and max(0.001, abs(f)) + * stops etol2 getting too large in the nearly spherical case. */ + g->etol2 = 0.1 * tol2 / + sqrt( maxx((real)(0.001), fabs(g->f)) * minx((real)(1), 1 - g->f/2) / 2 ); + + A3coeff(g); + C3coeff(g); + C4coeff(g); +} + +static void geod_lineinit_int(struct geod_geodesicline* l, + const struct geod_geodesic* g, + real lat1, real lon1, + real azi1, real salp1, real calp1, + unsigned caps) { + real cbet1, sbet1, eps; + l->a = g->a; + l->f = g->f; + l->b = g->b; + l->c2 = g->c2; + l->f1 = g->f1; + /* If caps is 0 assume the standard direct calculation */ + l->caps = (caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE) | + /* always allow latitude and azimuth and unrolling of longitude */ + GEOD_LATITUDE | GEOD_AZIMUTH | GEOD_LONG_UNROLL; + + l->lat1 = LatFix(lat1); + l->lon1 = lon1; + l->azi1 = azi1; + l->salp1 = salp1; + l->calp1 = calp1; + + sincosdx(AngRound(l->lat1), &sbet1, &cbet1); sbet1 *= l->f1; + /* Ensure cbet1 = +epsilon at poles */ + norm2(&sbet1, &cbet1); cbet1 = maxx(tiny, cbet1); + l->dn1 = sqrt(1 + g->ep2 * sq(sbet1)); + + /* Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), */ + l->salp0 = l->salp1 * cbet1; /* alp0 in [0, pi/2 - |bet1|] */ + /* Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following + * is slightly better (consider the case salp1 = 0). */ + l->calp0 = hypotx(l->calp1, l->salp1 * sbet1); + /* Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). + * sig = 0 is nearest northward crossing of equator. + * With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). + * With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 + * With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 + * Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). + * With alp0 in (0, pi/2], quadrants for sig and omg coincide. + * No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. + * With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. */ + l->ssig1 = sbet1; l->somg1 = l->salp0 * sbet1; + l->csig1 = l->comg1 = sbet1 != 0 || l->calp1 != 0 ? cbet1 * l->calp1 : 1; + norm2(&l->ssig1, &l->csig1); /* sig1 in (-pi, pi] */ + /* norm2(somg1, comg1); -- don't need to normalize! */ + + l->k2 = sq(l->calp0) * g->ep2; + eps = l->k2 / (2 * (1 + sqrt(1 + l->k2)) + l->k2); + + if (l->caps & CAP_C1) { + real s, c; + l->A1m1 = A1m1f(eps); + C1f(eps, l->C1a); + l->B11 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C1a, nC1); + s = sin(l->B11); c = cos(l->B11); + /* tau1 = sig1 + B11 */ + l->stau1 = l->ssig1 * c + l->csig1 * s; + l->ctau1 = l->csig1 * c - l->ssig1 * s; + /* Not necessary because C1pa reverts C1a + * B11 = -SinCosSeries(TRUE, stau1, ctau1, C1pa, nC1p); */ + } + + if (l->caps & CAP_C1p) + C1pf(eps, l->C1pa); + + if (l->caps & CAP_C2) { + l->A2m1 = A2m1f(eps); + C2f(eps, l->C2a); + l->B21 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C2a, nC2); + } + + if (l->caps & CAP_C3) { + C3f(g, eps, l->C3a); + l->A3c = -l->f * l->salp0 * A3f(g, eps); + l->B31 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C3a, nC3-1); + } + + if (l->caps & CAP_C4) { + C4f(g, eps, l->C4a); + /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) */ + l->A4 = sq(l->a) * l->calp0 * l->salp0 * g->e2; + l->B41 = SinCosSeries(FALSE, l->ssig1, l->csig1, l->C4a, nC4); + } + + l->a13 = l->s13 = NaN; +} + +void geod_lineinit(struct geod_geodesicline* l, + const struct geod_geodesic* g, + real lat1, real lon1, real azi1, unsigned caps) { + real salp1, calp1; + azi1 = AngNormalize(azi1); + /* Guard against underflow in salp0 */ + sincosdx(AngRound(azi1), &salp1, &calp1); + geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); +} + +void geod_gendirectline(struct geod_geodesicline* l, + const struct geod_geodesic* g, + real lat1, real lon1, real azi1, + unsigned flags, real s12_a12, + unsigned caps) { + geod_lineinit(l, g, lat1, lon1, azi1, caps); + geod_gensetdistance(l, flags, s12_a12); +} + +void geod_directline(struct geod_geodesicline* l, + const struct geod_geodesic* g, + real lat1, real lon1, real azi1, + real s12, unsigned caps) { + geod_gendirectline(l, g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, caps); +} + +real geod_genposition(const struct geod_geodesicline* l, + unsigned flags, real s12_a12, + real* plat2, real* plon2, real* pazi2, + real* ps12, real* pm12, + real* pM12, real* pM21, + real* pS12) { + real lat2 = 0, lon2 = 0, azi2 = 0, s12 = 0, + m12 = 0, M12 = 0, M21 = 0, S12 = 0; + /* Avoid warning about uninitialized B12. */ + real sig12, ssig12, csig12, B12 = 0, AB1 = 0; + real omg12, lam12, lon12; + real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2; + unsigned outmask = + (plat2 ? GEOD_LATITUDE : 0U) | + (plon2 ? GEOD_LONGITUDE : 0U) | + (pazi2 ? GEOD_AZIMUTH : 0U) | + (ps12 ? GEOD_DISTANCE : 0U) | + (pm12 ? GEOD_REDUCEDLENGTH : 0U) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | + (pS12 ? GEOD_AREA : 0U); + + outmask &= l->caps & OUT_ALL; + if (!( TRUE /*Init()*/ && + (flags & GEOD_ARCMODE || (l->caps & (GEOD_DISTANCE_IN & OUT_ALL))) )) + /* Uninitialized or impossible distance calculation requested */ + return NaN; + + if (flags & GEOD_ARCMODE) { + /* Interpret s12_a12 as spherical arc length */ + sig12 = s12_a12 * degree; + sincosdx(s12_a12, &ssig12, &csig12); + } else { + /* Interpret s12_a12 as distance */ + real + tau12 = s12_a12 / (l->b * (1 + l->A1m1)), + s = sin(tau12), + c = cos(tau12); + /* tau2 = tau1 + tau12 */ + B12 = - SinCosSeries(TRUE, + l->stau1 * c + l->ctau1 * s, + l->ctau1 * c - l->stau1 * s, + l->C1pa, nC1p); + sig12 = tau12 - (B12 - l->B11); + ssig12 = sin(sig12); csig12 = cos(sig12); + if (fabs(l->f) > 0.01) { + /* Reverted distance series is inaccurate for |f| > 1/100, so correct + * sig12 with 1 Newton iteration. The following table shows the + * approximate maximum error for a = WGS_a() and various f relative to + * GeodesicExact. + * erri = the error in the inverse solution (nm) + * errd = the error in the direct solution (series only) (nm) + * errda = the error in the direct solution (series + 1 Newton) (nm) + * + * f erri errd errda + * -1/5 12e6 1.2e9 69e6 + * -1/10 123e3 12e6 765e3 + * -1/20 1110 108e3 7155 + * -1/50 18.63 200.9 27.12 + * -1/100 18.63 23.78 23.37 + * -1/150 18.63 21.05 20.26 + * 1/150 22.35 24.73 25.83 + * 1/100 22.35 25.03 25.31 + * 1/50 29.80 231.9 30.44 + * 1/20 5376 146e3 10e3 + * 1/10 829e3 22e6 1.5e6 + * 1/5 157e6 3.8e9 280e6 */ + real serr; + ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; + csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; + B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); + serr = (1 + l->A1m1) * (sig12 + (B12 - l->B11)) - s12_a12 / l->b; + sig12 = sig12 - serr / sqrt(1 + l->k2 * sq(ssig2)); + ssig12 = sin(sig12); csig12 = cos(sig12); + /* Update B12 below */ + } + } + + /* sig2 = sig1 + sig12 */ + ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; + csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; + dn2 = sqrt(1 + l->k2 * sq(ssig2)); + if (outmask & (GEOD_DISTANCE | GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { + if (flags & GEOD_ARCMODE || fabs(l->f) > 0.01) + B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); + AB1 = (1 + l->A1m1) * (B12 - l->B11); + } + /* sin(bet2) = cos(alp0) * sin(sig2) */ + sbet2 = l->calp0 * ssig2; + /* Alt: cbet2 = hypot(csig2, salp0 * ssig2); */ + cbet2 = hypotx(l->salp0, l->calp0 * csig2); + if (cbet2 == 0) + /* I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case */ + cbet2 = csig2 = tiny; + /* tan(alp0) = cos(sig2)*tan(alp2) */ + salp2 = l->salp0; calp2 = l->calp0 * csig2; /* No need to normalize */ + + if (outmask & GEOD_DISTANCE) + s12 = (flags & GEOD_ARCMODE) ? + l->b * ((1 + l->A1m1) * sig12 + AB1) : + s12_a12; + + if (outmask & GEOD_LONGITUDE) { + real E = copysignx(1, l->salp0); /* east or west going? */ + /* tan(omg2) = sin(alp0) * tan(sig2) */ + somg2 = l->salp0 * ssig2; comg2 = csig2; /* No need to normalize */ + /* omg12 = omg2 - omg1 */ + omg12 = (flags & GEOD_LONG_UNROLL) + ? E * (sig12 + - (atan2( ssig2, csig2) - atan2( l->ssig1, l->csig1)) + + (atan2(E * somg2, comg2) - atan2(E * l->somg1, l->comg1))) + : atan2(somg2 * l->comg1 - comg2 * l->somg1, + comg2 * l->comg1 + somg2 * l->somg1); + lam12 = omg12 + l->A3c * + ( sig12 + (SinCosSeries(TRUE, ssig2, csig2, l->C3a, nC3-1) + - l->B31)); + lon12 = lam12 / degree; + lon2 = (flags & GEOD_LONG_UNROLL) ? l->lon1 + lon12 : + AngNormalize(AngNormalize(l->lon1) + AngNormalize(lon12)); + } + + if (outmask & GEOD_LATITUDE) + lat2 = atan2dx(sbet2, l->f1 * cbet2); + + if (outmask & GEOD_AZIMUTH) + azi2 = atan2dx(salp2, calp2); + + if (outmask & (GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { + real + B22 = SinCosSeries(TRUE, ssig2, csig2, l->C2a, nC2), + AB2 = (1 + l->A2m1) * (B22 - l->B21), + J12 = (l->A1m1 - l->A2m1) * sig12 + (AB1 - AB2); + if (outmask & GEOD_REDUCEDLENGTH) + /* Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure + * accurate cancellation in the case of coincident points. */ + m12 = l->b * ((dn2 * (l->csig1 * ssig2) - l->dn1 * (l->ssig1 * csig2)) + - l->csig1 * csig2 * J12); + if (outmask & GEOD_GEODESICSCALE) { + real t = l->k2 * (ssig2 - l->ssig1) * (ssig2 + l->ssig1) / + (l->dn1 + dn2); + M12 = csig12 + (t * ssig2 - csig2 * J12) * l->ssig1 / l->dn1; + M21 = csig12 - (t * l->ssig1 - l->csig1 * J12) * ssig2 / dn2; + } + } + + if (outmask & GEOD_AREA) { + real + B42 = SinCosSeries(FALSE, ssig2, csig2, l->C4a, nC4); + real salp12, calp12; + if (l->calp0 == 0 || l->salp0 == 0) { + /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ + salp12 = salp2 * l->calp1 - calp2 * l->salp1; + calp12 = calp2 * l->calp1 + salp2 * l->salp1; + } else { + /* tan(alp) = tan(alp0) * sec(sig) + * tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) + * = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) + * If csig12 > 0, write + * csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) + * else + * csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 + * No need to normalize */ + salp12 = l->calp0 * l->salp0 * + (csig12 <= 0 ? l->csig1 * (1 - csig12) + ssig12 * l->ssig1 : + ssig12 * (l->csig1 * ssig12 / (1 + csig12) + l->ssig1)); + calp12 = sq(l->salp0) + sq(l->calp0) * l->csig1 * csig2; + } + S12 = l->c2 * atan2(salp12, calp12) + l->A4 * (B42 - l->B41); + } + + /* In the pattern + * + * if ((outmask & GEOD_XX) && pYY) + * *pYY = YY; + * + * the second check "&& pYY" is redundant. It's there to make the CLang + * static analyzer happy. + */ + if ((outmask & GEOD_LATITUDE) && plat2) + *plat2 = lat2; + if ((outmask & GEOD_LONGITUDE) && plon2) + *plon2 = lon2; + if ((outmask & GEOD_AZIMUTH) && pazi2) + *pazi2 = azi2; + if ((outmask & GEOD_DISTANCE) && ps12) + *ps12 = s12; + if ((outmask & GEOD_REDUCEDLENGTH) && pm12) + *pm12 = m12; + if (outmask & GEOD_GEODESICSCALE) { + if (pM12) *pM12 = M12; + if (pM21) *pM21 = M21; + } + if ((outmask & GEOD_AREA) && pS12) + *pS12 = S12; + + return (flags & GEOD_ARCMODE) ? s12_a12 : sig12 / degree; +} + +void geod_setdistance(struct geod_geodesicline* l, real s13) { + l->s13 = s13; + l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, 0, 0, 0, 0, 0, 0, 0, 0); +} + +static void geod_setarc(struct geod_geodesicline* l, real a13) { + l->a13 = a13; l->s13 = NaN; + geod_genposition(l, GEOD_ARCMODE, l->a13, 0, 0, 0, &l->s13, 0, 0, 0, 0); +} + +void geod_gensetdistance(struct geod_geodesicline* l, + unsigned flags, real s13_a13) { + (flags & GEOD_ARCMODE) ? + geod_setarc(l, s13_a13) : + geod_setdistance(l, s13_a13); +} + +void geod_position(const struct geod_geodesicline* l, real s12, + real* plat2, real* plon2, real* pazi2) { + geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, 0, 0, 0, 0, 0); +} + +real geod_gendirect(const struct geod_geodesic* g, + real lat1, real lon1, real azi1, + unsigned flags, real s12_a12, + real* plat2, real* plon2, real* pazi2, + real* ps12, real* pm12, real* pM12, real* pM21, + real* pS12) { + struct geod_geodesicline l; + unsigned outmask = + (plat2 ? GEOD_LATITUDE : 0U) | + (plon2 ? GEOD_LONGITUDE : 0U) | + (pazi2 ? GEOD_AZIMUTH : 0U) | + (ps12 ? GEOD_DISTANCE : 0U) | + (pm12 ? GEOD_REDUCEDLENGTH : 0U) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | + (pS12 ? GEOD_AREA : 0U); + + geod_lineinit(&l, g, lat1, lon1, azi1, + /* Automatically supply GEOD_DISTANCE_IN if necessary */ + outmask | + ((flags & GEOD_ARCMODE) ? GEOD_NONE : GEOD_DISTANCE_IN)); + return geod_genposition(&l, flags, s12_a12, + plat2, plon2, pazi2, ps12, pm12, pM12, pM21, pS12); +} + +void geod_direct(const struct geod_geodesic* g, + real lat1, real lon1, real azi1, + real s12, + real* plat2, real* plon2, real* pazi2) { + geod_gendirect(g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, plat2, plon2, pazi2, + 0, 0, 0, 0, 0); +} + +static real geod_geninverse_int(const struct geod_geodesic* g, + real lat1, real lon1, real lat2, real lon2, + real* ps12, + real* psalp1, real* pcalp1, + real* psalp2, real* pcalp2, + real* pm12, real* pM12, real* pM21, + real* pS12) { + real s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0; + real lon12, lon12s; + int latsign, lonsign, swapp; + real sbet1, cbet1, sbet2, cbet2, s12x = 0, m12x = 0; + real dn1, dn2, lam12, slam12, clam12; + real a12 = 0, sig12, calp1 = 0, salp1 = 0, calp2 = 0, salp2 = 0; + real Ca[nC]; + boolx meridian; + /* somg12 > 1 marks that it needs to be calculated */ + real omg12 = 0, somg12 = 2, comg12 = 0; + + unsigned outmask = + (ps12 ? GEOD_DISTANCE : 0U) | + (pm12 ? GEOD_REDUCEDLENGTH : 0U) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | + (pS12 ? GEOD_AREA : 0U); + + outmask &= OUT_ALL; + /* Compute longitude difference (AngDiff does this carefully). Result is + * in [-180, 180] but -180 is only for west-going geodesics. 180 is for + * east-going and meridional geodesics. */ + lon12 = AngDiff(lon1, lon2, &lon12s); + /* Make longitude difference positive. */ + lonsign = lon12 >= 0 ? 1 : -1; + /* If very close to being on the same half-meridian, then make it so. */ + lon12 = lonsign * AngRound(lon12); + lon12s = AngRound((180 - lon12) - lonsign * lon12s); + lam12 = lon12 * degree; + if (lon12 > 90) { + sincosdx(lon12s, &slam12, &clam12); + clam12 = -clam12; + } else + sincosdx(lon12, &slam12, &clam12); + + /* If really close to the equator, treat as on equator. */ + lat1 = AngRound(LatFix(lat1)); + lat2 = AngRound(LatFix(lat2)); + /* Swap points so that point with higher (abs) latitude is point 1 + * If one latitude is a nan, then it becomes lat1. */ + swapp = fabs(lat1) < fabs(lat2) ? -1 : 1; + if (swapp < 0) { + lonsign *= -1; + swapx(&lat1, &lat2); + } + /* Make lat1 <= 0 */ + latsign = lat1 < 0 ? 1 : -1; + lat1 *= latsign; + lat2 *= latsign; + /* Now we have + * + * 0 <= lon12 <= 180 + * -90 <= lat1 <= 0 + * lat1 <= lat2 <= -lat1 + * + * longsign, swapp, latsign register the transformation to bring the + * coordinates to this canonical form. In all cases, 1 means no change was + * made. We make these transformations so that there are few cases to + * check, e.g., on verifying quadrants in atan2. In addition, this + * enforces some symmetries in the results returned. */ + + sincosdx(lat1, &sbet1, &cbet1); sbet1 *= g->f1; + /* Ensure cbet1 = +epsilon at poles */ + norm2(&sbet1, &cbet1); cbet1 = maxx(tiny, cbet1); + + sincosdx(lat2, &sbet2, &cbet2); sbet2 *= g->f1; + /* Ensure cbet2 = +epsilon at poles */ + norm2(&sbet2, &cbet2); cbet2 = maxx(tiny, cbet2); + + /* If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the + * |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is + * a better measure. This logic is used in assigning calp2 in Lambda12. + * Sometimes these quantities vanish and in that case we force bet2 = +/- + * bet1 exactly. An example where is is necessary is the inverse problem + * 48.522876735459 0 -48.52287673545898293 179.599720456223079643 + * which failed with Visual Studio 10 (Release and Debug) */ + + if (cbet1 < -sbet1) { + if (cbet2 == cbet1) + sbet2 = sbet2 < 0 ? sbet1 : -sbet1; + } else { + if (fabs(sbet2) == -sbet1) + cbet2 = cbet1; + } + + dn1 = sqrt(1 + g->ep2 * sq(sbet1)); + dn2 = sqrt(1 + g->ep2 * sq(sbet2)); + + meridian = lat1 == -90 || slam12 == 0; + + if (meridian) { + + /* Endpoints are on a single full meridian, so the geodesic might lie on + * a meridian. */ + + real ssig1, csig1, ssig2, csig2; + calp1 = clam12; salp1 = slam12; /* Head to the target longitude */ + calp2 = 1; salp2 = 0; /* At the target we're heading north */ + + /* tan(bet) = tan(sig) * cos(alp) */ + ssig1 = sbet1; csig1 = calp1 * cbet1; + ssig2 = sbet2; csig2 = calp2 * cbet2; + + /* sig12 = sig2 - sig1 */ + sig12 = atan2(maxx((real)(0), csig1 * ssig2 - ssig1 * csig2), + csig1 * csig2 + ssig1 * ssig2); + Lengths(g, g->n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, + cbet1, cbet2, &s12x, &m12x, 0, + (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, + (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, + Ca); + /* Add the check for sig12 since zero length geodesics might yield m12 < + * 0. Test case was + * + * echo 20.001 0 20.001 0 | GeodSolve -i + * + * In fact, we will have sig12 > pi/2 for meridional geodesic which is + * not a shortest path. */ + if (sig12 < 1 || m12x >= 0) { + /* Need at least 2, to handle 90 0 90 180 */ + if (sig12 < 3 * tiny) + sig12 = m12x = s12x = 0; + m12x *= g->b; + s12x *= g->b; + a12 = sig12 / degree; + } else + /* m12 < 0, i.e., prolate and too close to anti-podal */ + meridian = FALSE; + } + + if (!meridian && + sbet1 == 0 && /* and sbet2 == 0 */ + /* Mimic the way Lambda12 works with calp1 = 0 */ + (g->f <= 0 || lon12s >= g->f * 180)) { + + /* Geodesic runs along equator */ + calp1 = calp2 = 0; salp1 = salp2 = 1; + s12x = g->a * lam12; + sig12 = omg12 = lam12 / g->f1; + m12x = g->b * sin(sig12); + if (outmask & GEOD_GEODESICSCALE) + M12 = M21 = cos(sig12); + a12 = lon12 / g->f1; + + } else if (!meridian) { + + /* Now point1 and point2 belong within a hemisphere bounded by a + * meridian and geodesic is neither meridional or equatorial. */ + + /* Figure a starting point for Newton's method */ + real dnm = 0; + sig12 = InverseStart(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, + lam12, slam12, clam12, + &salp1, &calp1, &salp2, &calp2, &dnm, + Ca); + + if (sig12 >= 0) { + /* Short lines (InverseStart sets salp2, calp2, dnm) */ + s12x = sig12 * g->b * dnm; + m12x = sq(dnm) * g->b * sin(sig12 / dnm); + if (outmask & GEOD_GEODESICSCALE) + M12 = M21 = cos(sig12 / dnm); + a12 = sig12 / degree; + omg12 = lam12 / (g->f1 * dnm); + } else { + + /* Newton's method. This is a straightforward solution of f(alp1) = + * lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one + * root in the interval (0, pi) and its derivative is positive at the + * root. Thus f(alp) is positive for alp > alp1 and negative for alp < + * alp1. During the course of the iteration, a range (alp1a, alp1b) is + * maintained which brackets the root and with each evaluation of + * f(alp) the range is shrunk, if possible. Newton's method is + * restarted whenever the derivative of f is negative (because the new + * value of alp1 is then further from the solution) or if the new + * estimate of alp1 lies outside (0,pi); in this case, the new starting + * guess is taken to be (alp1a + alp1b) / 2. */ + real ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, domg12 = 0; + unsigned numit = 0; + /* Bracketing range */ + real salp1a = tiny, calp1a = 1, salp1b = tiny, calp1b = -1; + boolx tripn = FALSE; + boolx tripb = FALSE; + for (; numit < maxit2; ++numit) { + /* the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 + * WGS84 and random input: mean = 2.85, sd = 0.60 */ + real dv = 0, + v = Lambda12(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, + slam12, clam12, + &salp2, &calp2, &sig12, &ssig1, &csig1, &ssig2, &csig2, + &eps, &domg12, numit < maxit1, &dv, Ca); + /* 2 * tol0 is approximately 1 ulp for a number in [0, pi]. */ + /* Reversed test to allow escape with NaNs */ + if (tripb || !(fabs(v) >= (tripn ? 8 : 1) * tol0)) break; + /* Update bracketing values */ + if (v > 0 && (numit > maxit1 || calp1/salp1 > calp1b/salp1b)) + { salp1b = salp1; calp1b = calp1; } + else if (v < 0 && (numit > maxit1 || calp1/salp1 < calp1a/salp1a)) + { salp1a = salp1; calp1a = calp1; } + if (numit < maxit1 && dv > 0) { + real + dalp1 = -v/dv; + real + sdalp1 = sin(dalp1), cdalp1 = cos(dalp1), + nsalp1 = salp1 * cdalp1 + calp1 * sdalp1; + if (nsalp1 > 0 && fabs(dalp1) < pi) { + calp1 = calp1 * cdalp1 - salp1 * sdalp1; + salp1 = nsalp1; + norm2(&salp1, &calp1); + /* In some regimes we don't get quadratic convergence because + * slope -> 0. So use convergence conditions based on epsilon + * instead of sqrt(epsilon). */ + tripn = fabs(v) <= 16 * tol0; + continue; + } + } + /* Either dv was not positive or updated value was outside legal + * range. Use the midpoint of the bracket as the next estimate. + * This mechanism is not needed for the WGS84 ellipsoid, but it does + * catch problems with more eccentric ellipsoids. Its efficacy is + * such for the WGS84 test set with the starting guess set to alp1 = + * 90deg: + * the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 + * WGS84 and random input: mean = 4.74, sd = 0.99 */ + salp1 = (salp1a + salp1b)/2; + calp1 = (calp1a + calp1b)/2; + norm2(&salp1, &calp1); + tripn = FALSE; + tripb = (fabs(salp1a - salp1) + (calp1a - calp1) < tolb || + fabs(salp1 - salp1b) + (calp1 - calp1b) < tolb); + } + Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, + cbet1, cbet2, &s12x, &m12x, 0, + (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, + (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, Ca); + m12x *= g->b; + s12x *= g->b; + a12 = sig12 / degree; + if (outmask & GEOD_AREA) { + /* omg12 = lam12 - domg12 */ + real sdomg12 = sin(domg12), cdomg12 = cos(domg12); + somg12 = slam12 * cdomg12 - clam12 * sdomg12; + comg12 = clam12 * cdomg12 + slam12 * sdomg12; + } + } + } + + if (outmask & GEOD_DISTANCE) + s12 = 0 + s12x; /* Convert -0 to 0 */ + + if (outmask & GEOD_REDUCEDLENGTH) + m12 = 0 + m12x; /* Convert -0 to 0 */ + + if (outmask & GEOD_AREA) { + real + /* From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) */ + salp0 = salp1 * cbet1, + calp0 = hypotx(calp1, salp1 * sbet1); /* calp0 > 0 */ + real alp12; + if (calp0 != 0 && salp0 != 0) { + real + /* From Lambda12: tan(bet) = tan(sig) * cos(alp) */ + ssig1 = sbet1, csig1 = calp1 * cbet1, + ssig2 = sbet2, csig2 = calp2 * cbet2, + k2 = sq(calp0) * g->ep2, + eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2), + /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). */ + A4 = sq(g->a) * calp0 * salp0 * g->e2; + real B41, B42; + norm2(&ssig1, &csig1); + norm2(&ssig2, &csig2); + C4f(g, eps, Ca); + B41 = SinCosSeries(FALSE, ssig1, csig1, Ca, nC4); + B42 = SinCosSeries(FALSE, ssig2, csig2, Ca, nC4); + S12 = A4 * (B42 - B41); + } else + /* Avoid problems with indeterminate sig1, sig2 on equator */ + S12 = 0; + + if (!meridian && somg12 > 1) { + somg12 = sin(omg12); comg12 = cos(omg12); + } + + if (!meridian && + /* omg12 < 3/4 * pi */ + comg12 > -(real)(0.7071) && /* Long difference not too big */ + sbet2 - sbet1 < (real)(1.75)) { /* Lat difference not too big */ + /* Use tan(Gamma/2) = tan(omg12/2) + * * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) + * with tan(x/2) = sin(x)/(1+cos(x)) */ + real + domg12 = 1 + comg12, dbet1 = 1 + cbet1, dbet2 = 1 + cbet2; + alp12 = 2 * atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), + domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ); + } else { + /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ + real + salp12 = salp2 * calp1 - calp2 * salp1, + calp12 = calp2 * calp1 + salp2 * salp1; + /* The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz + * salp12 = -0 and alp12 = -180. However this depends on the sign + * being attached to 0 correctly. The following ensures the correct + * behavior. */ + if (salp12 == 0 && calp12 < 0) { + salp12 = tiny * calp1; + calp12 = -1; + } + alp12 = atan2(salp12, calp12); + } + S12 += g->c2 * alp12; + S12 *= swapp * lonsign * latsign; + /* Convert -0 to 0 */ + S12 += 0; + } + + /* Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. */ + if (swapp < 0) { + swapx(&salp1, &salp2); + swapx(&calp1, &calp2); + if (outmask & GEOD_GEODESICSCALE) + swapx(&M12, &M21); + } + + salp1 *= swapp * lonsign; calp1 *= swapp * latsign; + salp2 *= swapp * lonsign; calp2 *= swapp * latsign; + + if (psalp1) *psalp1 = salp1; + if (pcalp1) *pcalp1 = calp1; + if (psalp2) *psalp2 = salp2; + if (pcalp2) *pcalp2 = calp2; + + if (outmask & GEOD_DISTANCE) + *ps12 = s12; + if (outmask & GEOD_REDUCEDLENGTH) + *pm12 = m12; + if (outmask & GEOD_GEODESICSCALE) { + if (pM12) *pM12 = M12; + if (pM21) *pM21 = M21; + } + if (outmask & GEOD_AREA) + *pS12 = S12; + + /* Returned value in [0, 180] */ + return a12; +} + +real geod_geninverse(const struct geod_geodesic* g, + real lat1, real lon1, real lat2, real lon2, + real* ps12, real* pazi1, real* pazi2, + real* pm12, real* pM12, real* pM21, real* pS12) { + real salp1, calp1, salp2, calp2, + a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, ps12, + &salp1, &calp1, &salp2, &calp2, + pm12, pM12, pM21, pS12); + if (pazi1) *pazi1 = atan2dx(salp1, calp1); + if (pazi2) *pazi2 = atan2dx(salp2, calp2); + return a12; +} + +void geod_inverseline(struct geod_geodesicline* l, + const struct geod_geodesic* g, + real lat1, real lon1, real lat2, real lon2, + unsigned caps) { + real salp1, calp1, + a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, 0, + &salp1, &calp1, 0, 0, + 0, 0, 0, 0), + azi1 = atan2dx(salp1, calp1); + caps = caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE; + /* Ensure that a12 can be converted to a distance */ + if (caps & (OUT_ALL & GEOD_DISTANCE_IN)) caps |= GEOD_DISTANCE; + geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); + geod_setarc(l, a12); +} + +void geod_inverse(const struct geod_geodesic* g, + real lat1, real lon1, real lat2, real lon2, + real* ps12, real* pazi1, real* pazi2) { + geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, 0, 0, 0, 0); +} + +real SinCosSeries(boolx sinp, real sinx, real cosx, const real c[], int n) { + /* Evaluate + * y = sinp ? sum(c[i] * sin( 2*i * x), i, 1, n) : + * sum(c[i] * cos((2*i+1) * x), i, 0, n-1) + * using Clenshaw summation. N.B. c[0] is unused for sin series + * Approx operation count = (n + 5) mult and (2 * n + 2) add */ + real ar, y0, y1; + c += (n + sinp); /* Point to one beyond last element */ + ar = 2 * (cosx - sinx) * (cosx + sinx); /* 2 * cos(2 * x) */ + y0 = (n & 1) ? *--c : 0; y1 = 0; /* accumulators for sum */ + /* Now n is even */ + n /= 2; + while (n--) { + /* Unroll loop x 2, so accumulators return to their original role */ + y1 = ar * y0 - y1 + *--c; + y0 = ar * y1 - y0 + *--c; + } + return sinp + ? 2 * sinx * cosx * y0 /* sin(2 * x) * y0 */ + : cosx * (y0 - y1); /* cos(x) * (y0 - y1) */ +} + +void Lengths(const struct geod_geodesic* g, + real eps, real sig12, + real ssig1, real csig1, real dn1, + real ssig2, real csig2, real dn2, + real cbet1, real cbet2, + real* ps12b, real* pm12b, real* pm0, + real* pM12, real* pM21, + /* Scratch area of the right size */ + real Ca[]) { + real m0 = 0, J12 = 0, A1 = 0, A2 = 0; + real Cb[nC]; + + /* Return m12b = (reduced length)/b; also calculate s12b = distance/b, + * and m0 = coefficient of secular term in expression for reduced length. */ + boolx redlp = pm12b || pm0 || pM12 || pM21; + if (ps12b || redlp) { + A1 = A1m1f(eps); + C1f(eps, Ca); + if (redlp) { + A2 = A2m1f(eps); + C2f(eps, Cb); + m0 = A1 - A2; + A2 = 1 + A2; + } + A1 = 1 + A1; + } + if (ps12b) { + real B1 = SinCosSeries(TRUE, ssig2, csig2, Ca, nC1) - + SinCosSeries(TRUE, ssig1, csig1, Ca, nC1); + /* Missing a factor of b */ + *ps12b = A1 * (sig12 + B1); + if (redlp) { + real B2 = SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - + SinCosSeries(TRUE, ssig1, csig1, Cb, nC2); + J12 = m0 * sig12 + (A1 * B1 - A2 * B2); + } + } else if (redlp) { + /* Assume here that nC1 >= nC2 */ + int l; + for (l = 1; l <= nC2; ++l) + Cb[l] = A1 * Ca[l] - A2 * Cb[l]; + J12 = m0 * sig12 + (SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - + SinCosSeries(TRUE, ssig1, csig1, Cb, nC2)); + } + if (pm0) *pm0 = m0; + if (pm12b) + /* Missing a factor of b. + * Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure + * accurate cancellation in the case of coincident points. */ + *pm12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - + csig1 * csig2 * J12; + if (pM12 || pM21) { + real csig12 = csig1 * csig2 + ssig1 * ssig2; + real t = g->ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2); + if (pM12) + *pM12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1; + if (pM21) + *pM21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2; + } +} + +real Astroid(real x, real y) { + /* Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k. + * This solution is adapted from Geocentric::Reverse. */ + real k; + real + p = sq(x), + q = sq(y), + r = (p + q - 1) / 6; + if ( !(q == 0 && r <= 0) ) { + real + /* Avoid possible division by zero when r = 0 by multiplying equations + * for s and t by r^3 and r, resp. */ + S = p * q / 4, /* S = r^3 * s */ + r2 = sq(r), + r3 = r * r2, + /* The discriminant of the quadratic equation for T3. This is zero on + * the evolute curve p^(1/3)+q^(1/3) = 1 */ + disc = S * (S + 2 * r3); + real u = r; + real v, uv, w; + if (disc >= 0) { + real T3 = S + r3, T; + /* Pick the sign on the sqrt to maximize abs(T3). This minimizes loss + * of precision due to cancellation. The result is unchanged because + * of the way the T is used in definition of u. */ + T3 += T3 < 0 ? -sqrt(disc) : sqrt(disc); /* T3 = (r * t)^3 */ + /* N.B. cbrtx always returns the real root. cbrtx(-8) = -2. */ + T = cbrtx(T3); /* T = r * t */ + /* T can be zero; but then r2 / T -> 0. */ + u += T + (T != 0 ? r2 / T : 0); + } else { + /* T is complex, but the way u is defined the result is real. */ + real ang = atan2(sqrt(-disc), -(S + r3)); + /* There are three possible cube roots. We choose the root which + * avoids cancellation. Note that disc < 0 implies that r < 0. */ + u += 2 * r * cos(ang / 3); + } + v = sqrt(sq(u) + q); /* guaranteed positive */ + /* Avoid loss of accuracy when u < 0. */ + uv = u < 0 ? q / (v - u) : u + v; /* u+v, guaranteed positive */ + w = (uv - q) / (2 * v); /* positive? */ + /* Rearrange expression for k to avoid loss of accuracy due to + * subtraction. Division by 0 not possible because uv > 0, w >= 0. */ + k = uv / (sqrt(uv + sq(w)) + w); /* guaranteed positive */ + } else { /* q == 0 && r <= 0 */ + /* y = 0 with |x| <= 1. Handle this case directly. + * for y small, positive root is k = abs(y)/sqrt(1-x^2) */ + k = 0; + } + return k; +} + +real InverseStart(const struct geod_geodesic* g, + real sbet1, real cbet1, real dn1, + real sbet2, real cbet2, real dn2, + real lam12, real slam12, real clam12, + real* psalp1, real* pcalp1, + /* Only updated if return val >= 0 */ + real* psalp2, real* pcalp2, + /* Only updated for short lines */ + real* pdnm, + /* Scratch area of the right size */ + real Ca[]) { + real salp1 = 0, calp1 = 0, salp2 = 0, calp2 = 0, dnm = 0; + + /* Return a starting point for Newton's method in salp1 and calp1 (function + * value is -1). If Newton's method doesn't need to be used, return also + * salp2 and calp2 and function value is sig12. */ + real + sig12 = -1, /* Return value */ + /* bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0] */ + sbet12 = sbet2 * cbet1 - cbet2 * sbet1, + cbet12 = cbet2 * cbet1 + sbet2 * sbet1; + real sbet12a; + boolx shortline = cbet12 >= 0 && sbet12 < (real)(0.5) && + cbet2 * lam12 < (real)(0.5); + real somg12, comg12, ssig12, csig12; +#if defined(__GNUC__) && __GNUC__ == 4 && \ + (__GNUC_MINOR__ < 6 || defined(__MINGW32__)) + /* Volatile declaration needed to fix inverse cases + * 88.202499451857 0 -88.202499451857 179.981022032992859592 + * 89.262080389218 0 -89.262080389218 179.992207982775375662 + * 89.333123580033 0 -89.333123580032997687 179.99295812360148422 + * which otherwise fail with g++ 4.4.4 x86 -O3 (Linux) + * and g++ 4.4.0 (mingw) and g++ 4.6.1 (tdm mingw). */ + { + volatile real xx1 = sbet2 * cbet1; + volatile real xx2 = cbet2 * sbet1; + sbet12a = xx1 + xx2; + } +#else + sbet12a = sbet2 * cbet1 + cbet2 * sbet1; +#endif + if (shortline) { + real sbetm2 = sq(sbet1 + sbet2), omg12; + /* sin((bet1+bet2)/2)^2 + * = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2) */ + sbetm2 /= sbetm2 + sq(cbet1 + cbet2); + dnm = sqrt(1 + g->ep2 * sbetm2); + omg12 = lam12 / (g->f1 * dnm); + somg12 = sin(omg12); comg12 = cos(omg12); + } else { + somg12 = slam12; comg12 = clam12; + } + + salp1 = cbet2 * somg12; + calp1 = comg12 >= 0 ? + sbet12 + cbet2 * sbet1 * sq(somg12) / (1 + comg12) : + sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); + + ssig12 = hypotx(salp1, calp1); + csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12; + + if (shortline && ssig12 < g->etol2) { + /* really short lines */ + salp2 = cbet1 * somg12; + calp2 = sbet12 - cbet1 * sbet2 * + (comg12 >= 0 ? sq(somg12) / (1 + comg12) : 1 - comg12); + norm2(&salp2, &calp2); + /* Set return value */ + sig12 = atan2(ssig12, csig12); + } else if (fabs(g->n) > (real)(0.1) || /* No astroid calc if too eccentric */ + csig12 >= 0 || + ssig12 >= 6 * fabs(g->n) * pi * sq(cbet1)) { + /* Nothing to do, zeroth order spherical approximation is OK */ + } else { + /* Scale lam12 and bet2 to x, y coordinate system where antipodal point + * is at origin and singular point is at y = 0, x = -1. */ + real y, lamscale, betscale; + /* Volatile declaration needed to fix inverse case + * 56.320923501171 0 -56.320923501171 179.664747671772880215 + * which otherwise fails with g++ 4.4.4 x86 -O3 */ + volatile real x; + real lam12x = atan2(-slam12, -clam12); /* lam12 - pi */ + if (g->f >= 0) { /* In fact f == 0 does not get here */ + /* x = dlong, y = dlat */ + { + real + k2 = sq(sbet1) * g->ep2, + eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); + lamscale = g->f * cbet1 * A3f(g, eps) * pi; + } + betscale = lamscale * cbet1; + + x = lam12x / lamscale; + y = sbet12a / betscale; + } else { /* f < 0 */ + /* x = dlat, y = dlong */ + real + cbet12a = cbet2 * cbet1 - sbet2 * sbet1, + bet12a = atan2(sbet12a, cbet12a); + real m12b, m0; + /* In the case of lon12 = 180, this repeats a calculation made in + * Inverse. */ + Lengths(g, g->n, pi + bet12a, + sbet1, -cbet1, dn1, sbet2, cbet2, dn2, + cbet1, cbet2, 0, &m12b, &m0, 0, 0, Ca); + x = -1 + m12b / (cbet1 * cbet2 * m0 * pi); + betscale = x < -(real)(0.01) ? sbet12a / x : + -g->f * sq(cbet1) * pi; + lamscale = betscale / cbet1; + y = lam12x / lamscale; + } + + if (y > -tol1 && x > -1 - xthresh) { + /* strip near cut */ + if (g->f >= 0) { + salp1 = minx((real)(1), -(real)(x)); calp1 = - sqrt(1 - sq(salp1)); + } else { + calp1 = maxx((real)(x > -tol1 ? 0 : -1), (real)(x)); + salp1 = sqrt(1 - sq(calp1)); + } + } else { + /* Estimate alp1, by solving the astroid problem. + * + * Could estimate alpha1 = theta + pi/2, directly, i.e., + * calp1 = y/k; salp1 = -x/(1+k); for f >= 0 + * calp1 = x/(1+k); salp1 = -y/k; for f < 0 (need to check) + * + * However, it's better to estimate omg12 from astroid and use + * spherical formula to compute alp1. This reduces the mean number of + * Newton iterations for astroid cases from 2.24 (min 0, max 6) to 2.12 + * (min 0 max 5). The changes in the number of iterations are as + * follows: + * + * change percent + * 1 5 + * 0 78 + * -1 16 + * -2 0.6 + * -3 0.04 + * -4 0.002 + * + * The histogram of iterations is (m = number of iterations estimating + * alp1 directly, n = number of iterations estimating via omg12, total + * number of trials = 148605): + * + * iter m n + * 0 148 186 + * 1 13046 13845 + * 2 93315 102225 + * 3 36189 32341 + * 4 5396 7 + * 5 455 1 + * 6 56 0 + * + * Because omg12 is near pi, estimate work with omg12a = pi - omg12 */ + real k = Astroid(x, y); + real + omg12a = lamscale * ( g->f >= 0 ? -x * k/(1 + k) : -y * (1 + k)/k ); + somg12 = sin(omg12a); comg12 = -cos(omg12a); + /* Update spherical estimate of alp1 using omg12 instead of lam12 */ + salp1 = cbet2 * somg12; + calp1 = sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); + } + } + /* Sanity check on starting guess. Backwards check allows NaN through. */ + if (!(salp1 <= 0)) + norm2(&salp1, &calp1); + else { + salp1 = 1; calp1 = 0; + } + + *psalp1 = salp1; + *pcalp1 = calp1; + if (shortline) + *pdnm = dnm; + if (sig12 >= 0) { + *psalp2 = salp2; + *pcalp2 = calp2; + } + return sig12; +} + +real Lambda12(const struct geod_geodesic* g, + real sbet1, real cbet1, real dn1, + real sbet2, real cbet2, real dn2, + real salp1, real calp1, + real slam120, real clam120, + real* psalp2, real* pcalp2, + real* psig12, + real* pssig1, real* pcsig1, + real* pssig2, real* pcsig2, + real* peps, + real* pdomg12, + boolx diffp, real* pdlam12, + /* Scratch area of the right size */ + real Ca[]) { + real salp2 = 0, calp2 = 0, sig12 = 0, + ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, + domg12 = 0, dlam12 = 0; + real salp0, calp0; + real somg1, comg1, somg2, comg2, somg12, comg12, lam12; + real B312, eta, k2; + + if (sbet1 == 0 && calp1 == 0) + /* Break degeneracy of equatorial line. This case has already been + * handled. */ + calp1 = -tiny; + + /* sin(alp1) * cos(bet1) = sin(alp0) */ + salp0 = salp1 * cbet1; + calp0 = hypotx(calp1, salp1 * sbet1); /* calp0 > 0 */ + + /* tan(bet1) = tan(sig1) * cos(alp1) + * tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1) */ + ssig1 = sbet1; somg1 = salp0 * sbet1; + csig1 = comg1 = calp1 * cbet1; + norm2(&ssig1, &csig1); + /* norm2(&somg1, &comg1); -- don't need to normalize! */ + + /* Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful + * about this case, since this can yield singularities in the Newton + * iteration. + * sin(alp2) * cos(bet2) = sin(alp0) */ + salp2 = cbet2 != cbet1 ? salp0 / cbet2 : salp1; + /* calp2 = sqrt(1 - sq(salp2)) + * = sqrt(sq(calp0) - sq(sbet2)) / cbet2 + * and subst for calp0 and rearrange to give (choose positive sqrt + * to give alp2 in [0, pi/2]). */ + calp2 = cbet2 != cbet1 || fabs(sbet2) != -sbet1 ? + sqrt(sq(calp1 * cbet1) + + (cbet1 < -sbet1 ? + (cbet2 - cbet1) * (cbet1 + cbet2) : + (sbet1 - sbet2) * (sbet1 + sbet2))) / cbet2 : + fabs(calp1); + /* tan(bet2) = tan(sig2) * cos(alp2) + * tan(omg2) = sin(alp0) * tan(sig2). */ + ssig2 = sbet2; somg2 = salp0 * sbet2; + csig2 = comg2 = calp2 * cbet2; + norm2(&ssig2, &csig2); + /* norm2(&somg2, &comg2); -- don't need to normalize! */ + + /* sig12 = sig2 - sig1, limit to [0, pi] */ + sig12 = atan2(maxx((real)(0), csig1 * ssig2 - ssig1 * csig2), + csig1 * csig2 + ssig1 * ssig2); + + /* omg12 = omg2 - omg1, limit to [0, pi] */ + somg12 = maxx((real)(0), comg1 * somg2 - somg1 * comg2); + comg12 = comg1 * comg2 + somg1 * somg2; + /* eta = omg12 - lam120 */ + eta = atan2(somg12 * clam120 - comg12 * slam120, + comg12 * clam120 + somg12 * slam120); + k2 = sq(calp0) * g->ep2; + eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); + C3f(g, eps, Ca); + B312 = (SinCosSeries(TRUE, ssig2, csig2, Ca, nC3-1) - + SinCosSeries(TRUE, ssig1, csig1, Ca, nC3-1)); + domg12 = -g->f * A3f(g, eps) * salp0 * (sig12 + B312); + lam12 = eta + domg12; + + if (diffp) { + if (calp2 == 0) + dlam12 = - 2 * g->f1 * dn1 / sbet1; + else { + Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, + cbet1, cbet2, 0, &dlam12, 0, 0, 0, Ca); + dlam12 *= g->f1 / (calp2 * cbet2); + } + } + + *psalp2 = salp2; + *pcalp2 = calp2; + *psig12 = sig12; + *pssig1 = ssig1; + *pcsig1 = csig1; + *pssig2 = ssig2; + *pcsig2 = csig2; + *peps = eps; + *pdomg12 = domg12; + if (diffp) + *pdlam12 = dlam12; + + return lam12; +} + +real A3f(const struct geod_geodesic* g, real eps) { + /* Evaluate A3 */ + return polyval(nA3 - 1, g->A3x, eps); +} + +void C3f(const struct geod_geodesic* g, real eps, real c[]) { + /* Evaluate C3 coeffs + * Elements c[1] through c[nC3 - 1] are set */ + real mult = 1; + int o = 0, l; + for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ + int m = nC3 - l - 1; /* order of polynomial in eps */ + mult *= eps; + c[l] = mult * polyval(m, g->C3x + o, eps); + o += m + 1; + } +} + +void C4f(const struct geod_geodesic* g, real eps, real c[]) { + /* Evaluate C4 coeffs + * Elements c[0] through c[nC4 - 1] are set */ + real mult = 1; + int o = 0, l; + for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ + int m = nC4 - l - 1; /* order of polynomial in eps */ + c[l] = mult * polyval(m, g->C4x + o, eps); + o += m + 1; + mult *= eps; + } +} + +/* The scale factor A1-1 = mean value of (d/dsigma)I1 - 1 */ +real A1m1f(real eps) { + static const real coeff[] = { + /* (1-eps)*A1-1, polynomial in eps2 of order 3 */ + 1, 4, 64, 0, 256, + }; + int m = nA1/2; + real t = polyval(m, coeff, sq(eps)) / coeff[m + 1]; + return (t + eps) / (1 - eps); +} + +/* The coefficients C1[l] in the Fourier expansion of B1 */ +void C1f(real eps, real c[]) { + static const real coeff[] = { + /* C1[1]/eps^1, polynomial in eps2 of order 2 */ + -1, 6, -16, 32, + /* C1[2]/eps^2, polynomial in eps2 of order 2 */ + -9, 64, -128, 2048, + /* C1[3]/eps^3, polynomial in eps2 of order 1 */ + 9, -16, 768, + /* C1[4]/eps^4, polynomial in eps2 of order 1 */ + 3, -5, 512, + /* C1[5]/eps^5, polynomial in eps2 of order 0 */ + -7, 1280, + /* C1[6]/eps^6, polynomial in eps2 of order 0 */ + -7, 2048, + }; + real + eps2 = sq(eps), + d = eps; + int o = 0, l; + for (l = 1; l <= nC1; ++l) { /* l is index of C1p[l] */ + int m = (nC1 - l) / 2; /* order of polynomial in eps^2 */ + c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; + o += m + 2; + d *= eps; + } +} + +/* The coefficients C1p[l] in the Fourier expansion of B1p */ +void C1pf(real eps, real c[]) { + static const real coeff[] = { + /* C1p[1]/eps^1, polynomial in eps2 of order 2 */ + 205, -432, 768, 1536, + /* C1p[2]/eps^2, polynomial in eps2 of order 2 */ + 4005, -4736, 3840, 12288, + /* C1p[3]/eps^3, polynomial in eps2 of order 1 */ + -225, 116, 384, + /* C1p[4]/eps^4, polynomial in eps2 of order 1 */ + -7173, 2695, 7680, + /* C1p[5]/eps^5, polynomial in eps2 of order 0 */ + 3467, 7680, + /* C1p[6]/eps^6, polynomial in eps2 of order 0 */ + 38081, 61440, + }; + real + eps2 = sq(eps), + d = eps; + int o = 0, l; + for (l = 1; l <= nC1p; ++l) { /* l is index of C1p[l] */ + int m = (nC1p - l) / 2; /* order of polynomial in eps^2 */ + c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; + o += m + 2; + d *= eps; + } +} + +/* The scale factor A2-1 = mean value of (d/dsigma)I2 - 1 */ +real A2m1f(real eps) { + static const real coeff[] = { + /* (eps+1)*A2-1, polynomial in eps2 of order 3 */ + -11, -28, -192, 0, 256, + }; + int m = nA2/2; + real t = polyval(m, coeff, sq(eps)) / coeff[m + 1]; + return (t - eps) / (1 + eps); +} + +/* The coefficients C2[l] in the Fourier expansion of B2 */ +void C2f(real eps, real c[]) { + static const real coeff[] = { + /* C2[1]/eps^1, polynomial in eps2 of order 2 */ + 1, 2, 16, 32, + /* C2[2]/eps^2, polynomial in eps2 of order 2 */ + 35, 64, 384, 2048, + /* C2[3]/eps^3, polynomial in eps2 of order 1 */ + 15, 80, 768, + /* C2[4]/eps^4, polynomial in eps2 of order 1 */ + 7, 35, 512, + /* C2[5]/eps^5, polynomial in eps2 of order 0 */ + 63, 1280, + /* C2[6]/eps^6, polynomial in eps2 of order 0 */ + 77, 2048, + }; + real + eps2 = sq(eps), + d = eps; + int o = 0, l; + for (l = 1; l <= nC2; ++l) { /* l is index of C2[l] */ + int m = (nC2 - l) / 2; /* order of polynomial in eps^2 */ + c[l] = d * polyval(m, coeff + o, eps2) / coeff[o + m + 1]; + o += m + 2; + d *= eps; + } +} + +/* The scale factor A3 = mean value of (d/dsigma)I3 */ +void A3coeff(struct geod_geodesic* g) { + static const real coeff[] = { + /* A3, coeff of eps^5, polynomial in n of order 0 */ + -3, 128, + /* A3, coeff of eps^4, polynomial in n of order 1 */ + -2, -3, 64, + /* A3, coeff of eps^3, polynomial in n of order 2 */ + -1, -3, -1, 16, + /* A3, coeff of eps^2, polynomial in n of order 2 */ + 3, -1, -2, 8, + /* A3, coeff of eps^1, polynomial in n of order 1 */ + 1, -1, 2, + /* A3, coeff of eps^0, polynomial in n of order 0 */ + 1, 1, + }; + int o = 0, k = 0, j; + for (j = nA3 - 1; j >= 0; --j) { /* coeff of eps^j */ + int m = nA3 - j - 1 < j ? nA3 - j - 1 : j; /* order of polynomial in n */ + g->A3x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; + o += m + 2; + } +} + +/* The coefficients C3[l] in the Fourier expansion of B3 */ +void C3coeff(struct geod_geodesic* g) { + static const real coeff[] = { + /* C3[1], coeff of eps^5, polynomial in n of order 0 */ + 3, 128, + /* C3[1], coeff of eps^4, polynomial in n of order 1 */ + 2, 5, 128, + /* C3[1], coeff of eps^3, polynomial in n of order 2 */ + -1, 3, 3, 64, + /* C3[1], coeff of eps^2, polynomial in n of order 2 */ + -1, 0, 1, 8, + /* C3[1], coeff of eps^1, polynomial in n of order 1 */ + -1, 1, 4, + /* C3[2], coeff of eps^5, polynomial in n of order 0 */ + 5, 256, + /* C3[2], coeff of eps^4, polynomial in n of order 1 */ + 1, 3, 128, + /* C3[2], coeff of eps^3, polynomial in n of order 2 */ + -3, -2, 3, 64, + /* C3[2], coeff of eps^2, polynomial in n of order 2 */ + 1, -3, 2, 32, + /* C3[3], coeff of eps^5, polynomial in n of order 0 */ + 7, 512, + /* C3[3], coeff of eps^4, polynomial in n of order 1 */ + -10, 9, 384, + /* C3[3], coeff of eps^3, polynomial in n of order 2 */ + 5, -9, 5, 192, + /* C3[4], coeff of eps^5, polynomial in n of order 0 */ + 7, 512, + /* C3[4], coeff of eps^4, polynomial in n of order 1 */ + -14, 7, 512, + /* C3[5], coeff of eps^5, polynomial in n of order 0 */ + 21, 2560, + }; + int o = 0, k = 0, l, j; + for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ + for (j = nC3 - 1; j >= l; --j) { /* coeff of eps^j */ + int m = nC3 - j - 1 < j ? nC3 - j - 1 : j; /* order of polynomial in n */ + g->C3x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; + o += m + 2; + } + } +} + +/* The coefficients C4[l] in the Fourier expansion of I4 */ +void C4coeff(struct geod_geodesic* g) { + static const real coeff[] = { + /* C4[0], coeff of eps^5, polynomial in n of order 0 */ + 97, 15015, + /* C4[0], coeff of eps^4, polynomial in n of order 1 */ + 1088, 156, 45045, + /* C4[0], coeff of eps^3, polynomial in n of order 2 */ + -224, -4784, 1573, 45045, + /* C4[0], coeff of eps^2, polynomial in n of order 3 */ + -10656, 14144, -4576, -858, 45045, + /* C4[0], coeff of eps^1, polynomial in n of order 4 */ + 64, 624, -4576, 6864, -3003, 15015, + /* C4[0], coeff of eps^0, polynomial in n of order 5 */ + 100, 208, 572, 3432, -12012, 30030, 45045, + /* C4[1], coeff of eps^5, polynomial in n of order 0 */ + 1, 9009, + /* C4[1], coeff of eps^4, polynomial in n of order 1 */ + -2944, 468, 135135, + /* C4[1], coeff of eps^3, polynomial in n of order 2 */ + 5792, 1040, -1287, 135135, + /* C4[1], coeff of eps^2, polynomial in n of order 3 */ + 5952, -11648, 9152, -2574, 135135, + /* C4[1], coeff of eps^1, polynomial in n of order 4 */ + -64, -624, 4576, -6864, 3003, 135135, + /* C4[2], coeff of eps^5, polynomial in n of order 0 */ + 8, 10725, + /* C4[2], coeff of eps^4, polynomial in n of order 1 */ + 1856, -936, 225225, + /* C4[2], coeff of eps^3, polynomial in n of order 2 */ + -8448, 4992, -1144, 225225, + /* C4[2], coeff of eps^2, polynomial in n of order 3 */ + -1440, 4160, -4576, 1716, 225225, + /* C4[3], coeff of eps^5, polynomial in n of order 0 */ + -136, 63063, + /* C4[3], coeff of eps^4, polynomial in n of order 1 */ + 1024, -208, 105105, + /* C4[3], coeff of eps^3, polynomial in n of order 2 */ + 3584, -3328, 1144, 315315, + /* C4[4], coeff of eps^5, polynomial in n of order 0 */ + -128, 135135, + /* C4[4], coeff of eps^4, polynomial in n of order 1 */ + -2560, 832, 405405, + /* C4[5], coeff of eps^5, polynomial in n of order 0 */ + 128, 99099, + }; + int o = 0, k = 0, l, j; + for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ + for (j = nC4 - 1; j >= l; --j) { /* coeff of eps^j */ + int m = nC4 - j - 1; /* order of polynomial in n */ + g->C4x[k++] = polyval(m, coeff + o, g->n) / coeff[o + m + 1]; + o += m + 2; + } + } +} + +int transit(real lon1, real lon2) { + real lon12; + /* Return 1 or -1 if crossing prime meridian in east or west direction. + * Otherwise return zero. */ + /* Compute lon12 the same way as Geodesic::Inverse. */ + lon1 = AngNormalize(lon1); + lon2 = AngNormalize(lon2); + lon12 = AngDiff(lon1, lon2, 0); + return lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 : + (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0); +} + +int transitdirect(real lon1, real lon2) { +#if HAVE_C99_MATH + lon1 = remainder(lon1, (real)(720)); + lon2 = remainder(lon2, (real)(720)); + return ( (lon2 <= 0 && lon2 > -360 ? 1 : 0) - + (lon1 <= 0 && lon1 > -360 ? 1 : 0) ); +#else + lon1 = fmod(lon1, (real)(720)); + lon2 = fmod(lon2, (real)(720)); + return ( ((lon2 <= 0 && lon2 > -360) || lon2 > 360 ? 1 : 0) - + ((lon1 <= 0 && lon1 > -360) || lon1 > 360 ? 1 : 0) ); +#endif +} + +void accini(real s[]) { + /* Initialize an accumulator; this is an array with two elements. */ + s[0] = s[1] = 0; +} + +void acccopy(const real s[], real t[]) { + /* Copy an accumulator; t = s. */ + t[0] = s[0]; t[1] = s[1]; +} + +void accadd(real s[], real y) { + /* Add y to an accumulator. */ + real u, z = sumx(y, s[1], &u); + s[0] = sumx(z, s[0], &s[1]); + if (s[0] == 0) + s[0] = u; + else + s[1] = s[1] + u; +} + +real accsum(const real s[], real y) { + /* Return accumulator + y (but don't add to accumulator). */ + real t[2]; + acccopy(s, t); + accadd(t, y); + return t[0]; +} + +void accneg(real s[]) { + /* Negate an accumulator. */ + s[0] = -s[0]; s[1] = -s[1]; +} + +void geod_polygon_init(struct geod_polygon* p, boolx polylinep) { + p->polyline = (polylinep != 0); + geod_polygon_clear(p); +} + +void geod_polygon_clear(struct geod_polygon* p) { + p->lat0 = p->lon0 = p->lat = p->lon = NaN; + accini(p->P); + accini(p->A); + p->num = p->crossings = 0; +} + +void geod_polygon_addpoint(const struct geod_geodesic* g, + struct geod_polygon* p, + real lat, real lon) { + lon = AngNormalize(lon); + if (p->num == 0) { + p->lat0 = p->lat = lat; + p->lon0 = p->lon = lon; + } else { + real s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ + geod_geninverse(g, p->lat, p->lon, lat, lon, + &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); + accadd(p->P, s12); + if (!p->polyline) { + accadd(p->A, S12); + p->crossings += transit(p->lon, lon); + } + p->lat = lat; p->lon = lon; + } + ++p->num; +} + +void geod_polygon_addedge(const struct geod_geodesic* g, + struct geod_polygon* p, + real azi, real s) { + if (p->num) { /* Do nothing is num is zero */ + /* Initialize S12 to stop Visual Studio warning. Initialization of lat and + * lon is to make CLang static analyzer happy. */ + real lat = 0, lon = 0, S12 = 0; + geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, + &lat, &lon, 0, + 0, 0, 0, 0, p->polyline ? 0 : &S12); + accadd(p->P, s); + if (!p->polyline) { + accadd(p->A, S12); + p->crossings += transitdirect(p->lon, lon); + } + p->lat = lat; p->lon = lon; + ++p->num; + } +} + +unsigned geod_polygon_compute(const struct geod_geodesic* g, + const struct geod_polygon* p, + boolx reverse, boolx sign, + real* pA, real* pP) { + real s12, S12, t[2], area0; + int crossings; + if (p->num < 2) { + if (pP) *pP = 0; + if (!p->polyline && pA) *pA = 0; + return p->num; + } + if (p->polyline) { + if (pP) *pP = p->P[0]; + return p->num; + } + geod_geninverse(g, p->lat, p->lon, p->lat0, p->lon0, + &s12, 0, 0, 0, 0, 0, &S12); + if (pP) *pP = accsum(p->P, s12); + acccopy(p->A, t); + accadd(t, S12); + crossings = p->crossings + transit(p->lon, p->lon0); + area0 = 4 * pi * g->c2; + if (crossings & 1) + accadd(t, (t[0] < 0 ? 1 : -1) * area0/2); + /* area is with the clockwise sense. If !reverse convert to + * counter-clockwise convention. */ + if (!reverse) + accneg(t); + /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ + if (sign) { + if (t[0] > area0/2) + accadd(t, -area0); + else if (t[0] <= -area0/2) + accadd(t, +area0); + } else { + if (t[0] >= area0) + accadd(t, -area0); + else if (t[0] < 0) + accadd(t, +area0); + } + if (pA) *pA = 0 + t[0]; + return p->num; +} + +unsigned geod_polygon_testpoint(const struct geod_geodesic* g, + const struct geod_polygon* p, + real lat, real lon, + boolx reverse, boolx sign, + real* pA, real* pP) { + real perimeter, tempsum, area0; + int crossings, i; + unsigned num = p->num + 1; + if (num == 1) { + if (pP) *pP = 0; + if (!p->polyline && pA) *pA = 0; + return num; + } + perimeter = p->P[0]; + tempsum = p->polyline ? 0 : p->A[0]; + crossings = p->crossings; + for (i = 0; i < (p->polyline ? 1 : 2); ++i) { + real s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ + geod_geninverse(g, + i == 0 ? p->lat : lat, i == 0 ? p->lon : lon, + i != 0 ? p->lat0 : lat, i != 0 ? p->lon0 : lon, + &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); + perimeter += s12; + if (!p->polyline) { + tempsum += S12; + crossings += transit(i == 0 ? p->lon : lon, + i != 0 ? p->lon0 : lon); + } + } + + if (pP) *pP = perimeter; + if (p->polyline) + return num; + + area0 = 4 * pi * g->c2; + if (crossings & 1) + tempsum += (tempsum < 0 ? 1 : -1) * area0/2; + /* area is with the clockwise sense. If !reverse convert to + * counter-clockwise convention. */ + if (!reverse) + tempsum *= -1; + /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ + if (sign) { + if (tempsum > area0/2) + tempsum -= area0; + else if (tempsum <= -area0/2) + tempsum += area0; + } else { + if (tempsum >= area0) + tempsum -= area0; + else if (tempsum < 0) + tempsum += area0; + } + if (pA) *pA = 0 + tempsum; + return num; +} + +unsigned geod_polygon_testedge(const struct geod_geodesic* g, + const struct geod_polygon* p, + real azi, real s, + boolx reverse, boolx sign, + real* pA, real* pP) { + real perimeter, tempsum, area0; + int crossings; + unsigned num = p->num + 1; + if (num == 1) { /* we don't have a starting point! */ + if (pP) *pP = NaN; + if (!p->polyline && pA) *pA = NaN; + return 0; + } + perimeter = p->P[0] + s; + if (p->polyline) { + if (pP) *pP = perimeter; + return num; + } + + tempsum = p->A[0]; + crossings = p->crossings; + { + /* Initialization of lat, lon, and S12 is to make CLang static analyzer + happy. */ + real lat = 0, lon = 0, s12, S12 = 0; + geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, + &lat, &lon, 0, + 0, 0, 0, 0, &S12); + tempsum += S12; + crossings += transitdirect(p->lon, lon); + geod_geninverse(g, lat, lon, p->lat0, p->lon0, + &s12, 0, 0, 0, 0, 0, &S12); + perimeter += s12; + tempsum += S12; + crossings += transit(lon, p->lon0); + } + + area0 = 4 * pi * g->c2; + if (crossings & 1) + tempsum += (tempsum < 0 ? 1 : -1) * area0/2; + /* area is with the clockwise sense. If !reverse convert to + * counter-clockwise convention. */ + if (!reverse) + tempsum *= -1; + /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ + if (sign) { + if (tempsum > area0/2) + tempsum -= area0; + else if (tempsum <= -area0/2) + tempsum += area0; + } else { + if (tempsum >= area0) + tempsum -= area0; + else if (tempsum < 0) + tempsum += area0; + } + if (pP) *pP = perimeter; + if (pA) *pA = 0 + tempsum; + return num; +} + +void geod_polygonarea(const struct geod_geodesic* g, + real lats[], real lons[], int n, + real* pA, real* pP) { + int i; + struct geod_polygon p; + geod_polygon_init(&p, FALSE); + for (i = 0; i < n; ++i) + geod_polygon_addpoint(g, &p, lats[i], lons[i]); + geod_polygon_compute(g, &p, FALSE, TRUE, pA, pP); +} + +/** @endcond */ diff --git a/src/geodtest.c b/src/geodtest.c deleted file mode 100644 index 0ee86d5c..00000000 --- a/src/geodtest.c +++ /dev/null @@ -1,1069 +0,0 @@ -/** - * \file geodtest.c - * \brief Test suite for the geodesic routines in C - * - * Run these tests by configuring with cmake and running "make test". - * - * Copyright (c) Charles Karney (2015-2018) and licensed - * under the MIT/X11 License. For more information, see - * https://geographiclib.sourceforge.io/ - **********************************************************************/ - -/** @cond SKIP */ - -#include "geodesic.h" -#include -#include - -#if defined(_MSC_VER) -/* Squelch warnings about assignment within conditional expression */ -# pragma warning (disable: 4706) -#endif - -static const double wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ - -static int checkEquals(double x, double y, double d) { - if (fabs(x - y) <= d) - return 0; - printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); - return 1; -} - -static int checkNaN(double x) { - if (x != x) - return 0; - printf("checkNaN fails: %.7g\n", x); - return 1; -} - -static const int ncases = 20; -static const double testcases[20][12] = { - {35.60777, -139.44815, 111.098748429560326, - -11.17491, -69.95921, 129.289270889708762, - 8935244.5604818305, 80.50729714281974, 6273170.2055303837, - 0.16606318447386067, 0.16479116945612937, 12841384694976.432}, - {55.52454, 106.05087, 22.020059880982801, - 77.03196, 197.18234, 109.112041110671519, - 4105086.1713924406, 36.892740690445894, 3828869.3344387607, - 0.80076349608092607, 0.80101006984201008, 61674961290615.615}, - {-21.97856, 142.59065, -32.44456876433189, - 41.84138, 98.56635, -41.84359951440466, - 8394328.894657671, 75.62930491011522, 6161154.5773110616, - 0.24816339233950381, 0.24930251203627892, -6637997720646.717}, - {-66.99028, 112.2363, 173.73491240878403, - -12.70631, 285.90344, 2.512956620913668, - 11150344.2312080241, 100.278634181155759, 6289939.5670446687, - -0.17199490274700385, -0.17722569526345708, -121287239862139.744}, - {-17.42761, 173.34268, -159.033557661192928, - -15.84784, 5.93557, -20.787484651536988, - 16076603.1631180673, 144.640108810286253, 3732902.1583877189, - -0.81273638700070476, -0.81299800519154474, 97825992354058.708}, - {32.84994, 48.28919, 150.492927788121982, - -56.28556, 202.29132, 48.113449399816759, - 16727068.9438164461, 150.565799985466607, 3147838.1910180939, - -0.87334918086923126, -0.86505036767110637, -72445258525585.010}, - {6.96833, 52.74123, 92.581585386317712, - -7.39675, 206.17291, 90.721692165923907, - 17102477.2496958388, 154.147366239113561, 2772035.6169917581, - -0.89991282520302447, -0.89986892177110739, -1311796973197.995}, - {-50.56724, -16.30485, -105.439679907590164, - -33.56571, -94.97412, -47.348547835650331, - 6455670.5118668696, 58.083719495371259, 5409150.7979815838, - 0.53053508035997263, 0.52988722644436602, 41071447902810.047}, - {-58.93002, -8.90775, 140.965397902500679, - -8.91104, 133.13503, 19.255429433416599, - 11756066.0219864627, 105.755691241406877, 6151101.2270708536, - -0.26548622269867183, -0.27068483874510741, -86143460552774.735}, - {-68.82867, -74.28391, 93.774347763114881, - -50.63005, -8.36685, 34.65564085411343, - 3956936.926063544, 35.572254987389284, 3708890.9544062657, - 0.81443963736383502, 0.81420859815358342, -41845309450093.787}, - {-10.62672, -32.0898, -86.426713286747751, - 5.883, -134.31681, -80.473780971034875, - 11470869.3864563009, 103.387395634504061, 6184411.6622659713, - -0.23138683500430237, -0.23155097622286792, 4198803992123.548}, - {-21.76221, 166.90563, 29.319421206936428, - 48.72884, 213.97627, 43.508671946410168, - 9098627.3986554915, 81.963476716121964, 6299240.9166992283, - 0.13965943368590333, 0.14152969707656796, 10024709850277.476}, - {-19.79938, -174.47484, 71.167275780171533, - -11.99349, -154.35109, 65.589099775199228, - 2319004.8601169389, 20.896611684802389, 2267960.8703918325, - 0.93427001867125849, 0.93424887135032789, -3935477535005.785}, - {-11.95887, -116.94513, 92.712619830452549, - 4.57352, 7.16501, 78.64960934409585, - 13834722.5801401374, 124.688684161089762, 5228093.177931598, - -0.56879356755666463, -0.56918731952397221, -9919582785894.853}, - {-87.85331, 85.66836, -65.120313040242748, - 66.48646, 16.09921, -4.888658719272296, - 17286615.3147144645, 155.58592449699137, 2635887.4729110181, - -0.90697975771398578, -0.91095608883042767, 42667211366919.534}, - {1.74708, 128.32011, -101.584843631173858, - -11.16617, 11.87109, -86.325793296437476, - 12942901.1241347408, 116.650512484301857, 5682744.8413270572, - -0.44857868222697644, -0.44824490340007729, 10763055294345.653}, - {-25.72959, -144.90758, -153.647468693117198, - -57.70581, -269.17879, -48.343983158876487, - 9413446.7452453107, 84.664533838404295, 6356176.6898881281, - 0.09492245755254703, 0.09737058264766572, 74515122850712.444}, - {-41.22777, 122.32875, 14.285113402275739, - -7.57291, 130.37946, 10.805303085187369, - 3812686.035106021, 34.34330804743883, 3588703.8812128856, - 0.82605222593217889, 0.82572158200920196, -2456961531057.857}, - {11.01307, 138.25278, 79.43682622782374, - 6.62726, 247.05981, 103.708090215522657, - 11911190.819018408, 107.341669954114577, 6070904.722786735, - -0.29767608923657404, -0.29785143390252321, 17121631423099.696}, - {-29.47124, 95.14681, -163.779130441688382, - -27.46601, -69.15955, -15.909335945554969, - 13487015.8381145492, 121.294026715742277, 5481428.9945736388, - -0.51527225545373252, -0.51556587964721788, 104679964020340.318}}; - -static int testinverse() { - double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; - double azi1a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; - struct geod_geodesic g; - int i, result = 0; - geod_init(&g, wgs84_a, wgs84_f); - for (i = 0; i < ncases; ++i) { - lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; - lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; - s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; - M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; - a12a = geod_geninverse(&g, lat1, lon1, lat2, lon2, &s12a, &azi1a, &azi2a, - &m12a, &M12a, &M21a, &S12a); - result += checkEquals(azi1, azi1a, 1e-13); - result += checkEquals(azi2, azi2a, 1e-13); - result += checkEquals(s12, s12a, 1e-8); - result += checkEquals(a12, a12a, 1e-13); - result += checkEquals(m12, m12a, 1e-8); - result += checkEquals(M12, M12a, 1e-15); - result += checkEquals(M21, M21a, 1e-15); - result += checkEquals(S12, S12a, 0.1); - } - return result; -} - -static int testdirect() { - double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; - double lat2a, lon2a, azi2a, a12a, m12a, M12a, M21a, S12a; - struct geod_geodesic g; - int i, result = 0; - unsigned flags = GEOD_LONG_UNROLL; - geod_init(&g, wgs84_a, wgs84_f); - for (i = 0; i < ncases; ++i) { - lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; - lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; - s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; - M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; - a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, s12, - &lat2a, &lon2a, &azi2a, 0, - &m12a, &M12a, &M21a, &S12a); - result += checkEquals(lat2, lat2a, 1e-13); - result += checkEquals(lon2, lon2a, 1e-13); - result += checkEquals(azi2, azi2a, 1e-13); - result += checkEquals(a12, a12a, 1e-13); - result += checkEquals(m12, m12a, 1e-8); - result += checkEquals(M12, M12a, 1e-15); - result += checkEquals(M21, M21a, 1e-15); - result += checkEquals(S12, S12a, 0.1); - } - return result; -} - -static int testarcdirect() { - double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; - double lat2a, lon2a, azi2a, s12a, m12a, M12a, M21a, S12a; - struct geod_geodesic g; - int i, result = 0; - unsigned flags = GEOD_ARCMODE | GEOD_LONG_UNROLL; - geod_init(&g, wgs84_a, wgs84_f); - for (i = 0; i < ncases; ++i) { - lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; - lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; - s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; - M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; - geod_gendirect(&g, lat1, lon1, azi1, flags, a12, - &lat2a, &lon2a, &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); - result += checkEquals(lat2, lat2a, 1e-13); - result += checkEquals(lon2, lon2a, 1e-13); - result += checkEquals(azi2, azi2a, 1e-13); - result += checkEquals(s12, s12a, 1e-8); - result += checkEquals(m12, m12a, 1e-8); - result += checkEquals(M12, M12a, 1e-15); - result += checkEquals(M21, M21a, 1e-15); - result += checkEquals(S12, S12a, 0.1); - } - return result; -} - -static int GeodSolve0() { - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 40.6, -73.8, 49.01666667, 2.55, &s12, &azi1, &azi2); - result += checkEquals(azi1, 53.47022, 0.5e-5); - result += checkEquals(azi2, 111.59367, 0.5e-5); - result += checkEquals(s12, 5853226, 0.5); - return result; -} - -static int GeodSolve1() { - double lat2, lon2, azi2; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_direct(&g, 40.63972222, -73.77888889, 53.5, 5850e3, - &lat2, &lon2, &azi2); - result += checkEquals(lat2, 49.01467, 0.5e-5); - result += checkEquals(lon2, 2.56106, 0.5e-5); - result += checkEquals(azi2, 111.62947, 0.5e-5); - return result; -} - -static int GeodSolve2() { - /* Check fix for antipodal prolate bug found 2010-09-04 */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, 6.4e6, -1/150.0); - geod_inverse(&g, 0.07476, 0, -0.07476, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00078, 0.5e-5); - result += checkEquals(azi2, 90.00078, 0.5e-5); - result += checkEquals(s12, 20106193, 0.5); - geod_inverse(&g, 0.1, 0, -0.1, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00105, 0.5e-5); - result += checkEquals(azi2, 90.00105, 0.5e-5); - result += checkEquals(s12, 20106193, 0.5); - return result; -} - -static int GeodSolve4() { - /* Check fix for short line bug found 2010-05-21 */ - double s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 36.493349428792, 0, 36.49334942879201, .0000008, - &s12, 0, 0); - result += checkEquals(s12, 0.072, 0.5e-3); - return result; -} - -static int GeodSolve5() { - /* Check fix for point2=pole bug found 2010-05-03 */ - double lat2, lon2, azi2; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_direct(&g, 0.01777745589997, 30, 0, 10e6, &lat2, &lon2, &azi2); - result += checkEquals(lat2, 90, 0.5e-5); - if (lon2 < 0) { - result += checkEquals(lon2, -150, 0.5e-5); - result += checkEquals(fabs(azi2), 180, 0.5e-5); - } else { - result += checkEquals(lon2, 30, 0.5e-5); - result += checkEquals(azi2, 0, 0.5e-5); - } - return result; -} - -static int GeodSolve6() { - /* Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 - * x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). */ - double s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 88.202499451857, 0, - -88.202499451857, 179.981022032992859592, &s12, 0, 0); - result += checkEquals(s12, 20003898.214, 0.5e-3); - geod_inverse(&g, 89.262080389218, 0, - -89.262080389218, 179.992207982775375662, &s12, 0, 0); - result += checkEquals(s12, 20003925.854, 0.5e-3); - geod_inverse(&g, 89.333123580033, 0, - -89.333123580032997687, 179.99295812360148422, &s12, 0, 0); - result += checkEquals(s12, 20003926.881, 0.5e-3); - return result; -} - -static int GeodSolve9() { - /* Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) */ - double s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 56.320923501171, 0, - -56.320923501171, 179.664747671772880215, &s12, 0, 0); - result += checkEquals(s12, 19993558.287, 0.5e-3); - return result; -} - -static int GeodSolve10() { - /* Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio - * 10 rel + debug) */ - double s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 52.784459512564, 0, - -52.784459512563990912, 179.634407464943777557, &s12, 0, 0); - result += checkEquals(s12, 19991596.095, 0.5e-3); - return result; -} - -static int GeodSolve11() { - /* Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio - * 10 rel + debug) */ - double s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 48.522876735459, 0, - -48.52287673545898293, 179.599720456223079643, &s12, 0, 0); - result += checkEquals(s12, 19989144.774, 0.5e-3); - return result; -} - -static int GeodSolve12() { - /* Check fix for inverse geodesics on extreme prolate/oblate - * ellipsoids Reported 2012-08-29 Stefan Guenther - * ; fixed 2012-10-07 */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, 89.8, -1.83); - geod_inverse(&g, 0, 0, -10, 160, &s12, &azi1, &azi2); - result += checkEquals(azi1, 120.27, 1e-2); - result += checkEquals(azi2, 105.15, 1e-2); - result += checkEquals(s12, 266.7, 1e-1); - return result; -} - -static int GeodSolve14() { - /* Check fix for inverse ignoring lon12 = nan */ - double azi1, azi2, s12, nan; - struct geod_geodesic g; - int result = 0; - { - double minus1 = -1; - /* cppcheck-suppress wrongmathcall */ - nan = sqrt(minus1); - } - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 0, 0, 1, nan, &s12, &azi1, &azi2); - result += checkNaN(azi1); - result += checkNaN(azi2); - result += checkNaN(s12); - return result; -} - -static int GeodSolve15() { - /* Initial implementation of Math::eatanhe was wrong for e^2 < 0. This - * checks that this is fixed. */ - double S12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, 6.4e6, -1/150.0); - geod_gendirect(&g, 1, 2, 3, 0, 4, - 0, 0, 0, 0, 0, 0, 0, &S12); - result += checkEquals(S12, 23700, 0.5); - return result; -} - -static int GeodSolve17() { - /* Check fix for LONG_UNROLL bug found on 2015-05-07 */ - double lat2, lon2, azi2; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - unsigned flags = GEOD_LONG_UNROLL; - geod_init(&g, wgs84_a, wgs84_f); - geod_gendirect(&g, 40, -75, -10, flags, 2e7, - &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, -39, 1); - result += checkEquals(lon2, -254, 1); - result += checkEquals(azi2, -170, 1); - geod_lineinit(&l, &g, 40, -75, -10, 0); - geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, -39, 1); - result += checkEquals(lon2, -254, 1); - result += checkEquals(azi2, -170, 1); - geod_direct(&g, 40, -75, -10, 2e7, &lat2, &lon2, &azi2); - result += checkEquals(lat2, -39, 1); - result += checkEquals(lon2, 105, 1); - result += checkEquals(azi2, -170, 1); - geod_position(&l, 2e7, &lat2, &lon2, &azi2); - result += checkEquals(lat2, -39, 1); - result += checkEquals(lon2, 105, 1); - result += checkEquals(azi2, -170, 1); - return result; -} - -static int GeodSolve26() { - /* Check 0/0 problem with area calculation on sphere 2015-09-08 */ - double S12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, 6.4e6, 0); - geod_geninverse(&g, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, &S12); - result += checkEquals(S12, 49911046115.0, 0.5); - return result; -} - -static int GeodSolve28() { - /* Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in - * Java implementation fixed on 2015-05-19). */ - double a12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, 6.4e6, 0.1); - a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, 0, 0, 0, 0, 0, 0, 0, 0); - result += checkEquals(a12, 48.55570690, 0.5e-8); - return result; -} - -static int GeodSolve33() { - /* Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- - * sind(-0.0) = +0.0 -- and in some version of Visual Studio -- - * fmod(-0.0, 360.0) = +0.0. */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00000, 0.5e-5); - result += checkEquals(azi2, 90.00000, 0.5e-5); - result += checkEquals(s12, 19926189, 0.5); - geod_inverse(&g, 0, 0, 0, 179.5, &s12, &azi1, &azi2); - result += checkEquals(azi1, 55.96650, 0.5e-5); - result += checkEquals(azi2, 124.03350, 0.5e-5); - result += checkEquals(s12, 19980862, 0.5); - geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 20003931, 0.5); - geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 19893357, 0.5); - geod_init(&g, 6.4e6, 0); - geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00000, 0.5e-5); - result += checkEquals(azi2, 90.00000, 0.5e-5); - result += checkEquals(s12, 19994492, 0.5); - geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 20106193, 0.5); - geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 19994492, 0.5); - geod_init(&g, 6.4e6, -1/300.0); - geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00000, 0.5e-5); - result += checkEquals(azi2, 90.00000, 0.5e-5); - result += checkEquals(s12, 19994492, 0.5); - geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 90.00000, 0.5e-5); - result += checkEquals(azi2, 90.00000, 0.5e-5); - result += checkEquals(s12, 20106193, 0.5); - geod_inverse(&g, 0, 0, 0.5, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 33.02493, 0.5e-5); - result += checkEquals(azi2, 146.97364, 0.5e-5); - result += checkEquals(s12, 20082617, 0.5); - geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 20027270, 0.5); - - return result; -} - -static int GeodSolve55() { - /* Check fix for nan + point on equator or pole not returning all nans in - * Geodesic::Inverse, found 2015-09-23. */ - double azi1, azi2, s12, nan; - struct geod_geodesic g; - int result = 0; - { - double minus1 = -1; - /* cppcheck-suppress wrongmathcall */ - nan = sqrt(minus1); - } - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, nan, 0, 0, 90, &s12, &azi1, &azi2); - result += checkNaN(azi1); - result += checkNaN(azi2); - result += checkNaN(s12); - geod_inverse(&g, nan, 0, 90, 9, &s12, &azi1, &azi2); - result += checkNaN(azi1); - result += checkNaN(azi2); - result += checkNaN(s12); - return result; -} - -static int GeodSolve59() { - /* Check for points close with longitudes close to 180 deg apart. */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 5, 0.00000000000001, 10, 180, &s12, &azi1, &azi2); - result += checkEquals(azi1, 0.000000000000035, 1.5e-14); - result += checkEquals(azi2, 179.99999999999996, 1.5e-14); - result += checkEquals(s12, 18345191.174332713, 5e-9); - return result; -} - -static int GeodSolve61() { - /* Make sure small negative azimuths are west-going */ - double lat2, lon2, azi2; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - unsigned flags = GEOD_LONG_UNROLL; - geod_init(&g, wgs84_a, wgs84_f); - geod_gendirect(&g, 45, 0, -0.000000000000000003, flags, 1e7, - &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, 45.30632, 0.5e-5); - result += checkEquals(lon2, -180, 0.5e-5); - result += checkEquals(fabs(azi2), 180, 0.5e-5); - geod_inverseline(&l, &g, 45, 0, 80, -0.000000000000000003, 0); - geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, 45.30632, 0.5e-5); - result += checkEquals(lon2, -180, 0.5e-5); - result += checkEquals(fabs(azi2), 180, 0.5e-5); - return result; -} - -static int GeodSolve65() { - /* Check for bug in east-going check in GeodesicLine (needed to check for - * sign of 0) and sign error in area calculation due to a bogus override of - * the code for alp12. Found/fixed on 2015-12-19. */ - double lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - unsigned flags = GEOD_LONG_UNROLL, caps = GEOD_ALL; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverseline(&l, &g, 30, -0.000000000000000001, -31, 180, caps); - a12 = geod_genposition(&l, flags, 1e7, - &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); - result += checkEquals(lat2, -60.23169, 0.5e-5); - result += checkEquals(lon2, -0.00000, 0.5e-5); - result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); - result += checkEquals(s12, 10000000, 0.5); - result += checkEquals(a12, 90.06544, 0.5e-5); - result += checkEquals(m12, 6363636, 0.5); - result += checkEquals(M12, -0.0012834, 0.5e-7); - result += checkEquals(M21, 0.0013749, 0.5e-7); - result += checkEquals(S12, 0, 0.5); - a12 = geod_genposition(&l, flags, 2e7, - &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); - result += checkEquals(lat2, -30.03547, 0.5e-5); - result += checkEquals(lon2, -180.00000, 0.5e-5); - result += checkEquals(azi2, -0.00000, 0.5e-5); - result += checkEquals(s12, 20000000, 0.5); - result += checkEquals(a12, 179.96459, 0.5e-5); - result += checkEquals(m12, 54342, 0.5); - result += checkEquals(M12, -1.0045592, 0.5e-7); - result += checkEquals(M21, -0.9954339, 0.5e-7); - result += checkEquals(S12, 127516405431022.0, 0.5); - return result; -} - -static int GeodSolve67() { - /* Check for InverseLine if line is slightly west of S and that s13 is - correctly set. */ - double lat2, lon2, azi2; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - unsigned flags = GEOD_LONG_UNROLL; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverseline(&l, &g, -5, -0.000000000000002, -10, 180, 0); - geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, 4.96445, 0.5e-5); - result += checkEquals(lon2, -180.00000, 0.5e-5); - result += checkEquals(azi2, -0.00000, 0.5e-5); - geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); - result += checkEquals(lat2, -87.52461, 0.5e-5); - result += checkEquals(lon2, -0.00000, 0.5e-5); - result += checkEquals(azi2, -180.00000, 0.5e-5); - return result; -} - -static int GeodSolve71() { - /* Check that DirectLine sets s13. */ - double lat2, lon2, azi2; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_directline(&l, &g, 1, 2, 45, 1e7, 0); - geod_position(&l, 0.5 * l.s13, &lat2, &lon2, &azi2); - result += checkEquals(lat2, 30.92625, 0.5e-5); - result += checkEquals(lon2, 37.54640, 0.5e-5); - result += checkEquals(azi2, 55.43104, 0.5e-5); - return result; -} - -static int GeodSolve73() { - /* Check for backwards from the pole bug reported by Anon on 2016-02-13. - * This only affected the Java implementation. It was introduced in Java - * version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. */ - double lat2, lon2, azi2; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_direct(&g, 90, 10, 180, -1e6, - &lat2, &lon2, &azi2); - result += checkEquals(lat2, 81.04623, 0.5e-5); - result += checkEquals(lon2, -170, 0.5e-5); - result += azi2 == 0 ? 0 : 1; - result += 1/azi2 > 0 ? 0 : 1; /* Check that azi2 = +0.0 not -0.0 */ - return result; -} - -static void planimeter(const struct geod_geodesic* g, - double points[][2], int N, - double* perimeter, double* area) { - struct geod_polygon p; - int i; - geod_polygon_init(&p, 0); - for (i = 0; i < N; ++i) - geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); - geod_polygon_compute(g, &p, 0, 1, area, perimeter); -} - -static void polylength(const struct geod_geodesic* g, - double points[][2], int N, - double* perimeter) { - struct geod_polygon p; - int i; - geod_polygon_init(&p, 1); - for (i = 0; i < N; ++i) - geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); - geod_polygon_compute(g, &p, 0, 1, 0, perimeter); -} - -static int GeodSolve74() { - /* Check fix for inaccurate areas, bug introduced in v1.46, fixed - 2015-10-16. */ - double a12, s12, azi1, azi2, m12, M12, M21, S12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - a12 = geod_geninverse(&g, 54.1589, 15.3872, 54.1591, 15.3877, - &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); - result += checkEquals(azi1, 55.723110355, 5e-9); - result += checkEquals(azi2, 55.723515675, 5e-9); - result += checkEquals(s12, 39.527686385, 5e-9); - result += checkEquals(a12, 0.000355495, 5e-9); - result += checkEquals(m12, 39.527686385, 5e-9); - result += checkEquals(M12, 0.999999995, 5e-9); - result += checkEquals(M21, 0.999999995, 5e-9); - result += checkEquals(S12, 286698586.30197, 5e-4); - return result; -} - -static int GeodSolve76() { - /* The distance from Wellington and Salamanca (a classic failure of - Vincenty) */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, -(41+19/60.0), 174+49/60.0, 40+58/60.0, -(5+30/60.0), - &s12, &azi1, &azi2); - result += checkEquals(azi1, 160.39137649664, 0.5e-11); - result += checkEquals(azi2, 19.50042925176, 0.5e-11); - result += checkEquals(s12, 19960543.857179, 0.5e-6); - return result; -} - -static int GeodSolve78() { - /* An example where the NGS calculator fails to converge */ - double azi1, azi2, s12; - struct geod_geodesic g; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_inverse(&g, 27.2, 0.0, -27.1, 179.5, &s12, &azi1, &azi2); - result += checkEquals(azi1, 45.82468716758, 0.5e-11); - result += checkEquals(azi2, 134.22776532670, 0.5e-11); - result += checkEquals(s12, 19974354.765767, 0.5e-6); - return result; -} - -static int GeodSolve80() { - /* Some tests to add code coverage: computing scale in special cases + zero - length geodesic (includes GeodSolve80 - GeodSolve83) + using an incapable - line. */ - double a12, s12, azi1, azi2, m12, M12, M21, S12; - struct geod_geodesic g; - struct geod_geodesicline l; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_geninverse(&g, 0, 0, 0, 90, 0, 0, 0, 0, &M12, &M21, 0); - result += checkEquals(M12, -0.00528427534, 0.5e-10); - result += checkEquals(M21, -0.00528427534, 0.5e-10); - geod_geninverse(&g, 0, 0, 1e-6, 1e-6, 0, 0, 0, 0, &M12, &M21, 0); - result += checkEquals(M12, 1, 0.5e-10); - result += checkEquals(M21, 1, 0.5e-10); - a12 = geod_geninverse(&g, 20.001, 0, 20.001, 0, - &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); - result += checkEquals(a12, 0, 1e-13); - result += checkEquals(s12, 0, 1e-8); - result += checkEquals(azi1, 180, 1e-13); - result += checkEquals(azi2, 180, 1e-13); - result += checkEquals(m12, 0, 1e-8); - result += checkEquals(M12, 1, 1e-15); - result += checkEquals(M21, 1, 1e-15); - result += checkEquals(S12, 0, 1e-10); - a12 = geod_geninverse(&g, 90, 0, 90, 180, - &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); - result += checkEquals(a12, 0, 1e-13); - result += checkEquals(s12, 0, 1e-8); - result += checkEquals(azi1, 0, 1e-13); - result += checkEquals(azi2, 180, 1e-13); - result += checkEquals(m12, 0, 1e-8); - result += checkEquals(M12, 1, 1e-15); - result += checkEquals(M21, 1, 1e-15); - result += checkEquals(S12, 127516405431022, 0.5); - /* An incapable line which can't take distance as input */ - geod_lineinit(&l, &g, 1, 2, 90, GEOD_LATITUDE); - a12 = geod_genposition(&l, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0); - result += checkNaN(a12); - return result; -} - -static int Planimeter0() { - /* Check fix for pole-encircling bug found 2011-03-16 */ - double pa[4][2] = {{89, 0}, {89, 90}, {89, 180}, {89, 270}}; - double pb[4][2] = {{-89, 0}, {-89, 90}, {-89, 180}, {-89, 270}}; - double pc[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; - double pd[3][2] = {{90, 0}, {0, 0}, {0, 90}}; - struct geod_geodesic g; - double perimeter, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - - planimeter(&g, pa, 4, &perimeter, &area); - result += checkEquals(perimeter, 631819.8745, 1e-4); - result += checkEquals(area, 24952305678.0, 1); - - planimeter(&g, pb, 4, &perimeter, &area); - result += checkEquals(perimeter, 631819.8745, 1e-4); - result += checkEquals(area, -24952305678.0, 1); - - planimeter(&g, pc, 4, &perimeter, &area); - result += checkEquals(perimeter, 627598.2731, 1e-4); - result += checkEquals(area, 24619419146.0, 1); - - planimeter(&g, pd, 3, &perimeter, &area); - result += checkEquals(perimeter, 30022685, 1); - result += checkEquals(area, 63758202715511.0, 1); - - polylength(&g, pd, 3, &perimeter); - result += checkEquals(perimeter, 20020719, 1); - - return result; -} - -static int Planimeter5() { - /* Check fix for Planimeter pole crossing bug found 2011-06-24 */ - double points[3][2] = {{89, 0.1}, {89, 90.1}, {89, -179.9}}; - struct geod_geodesic g; - double perimeter, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - planimeter(&g, points, 3, &perimeter, &area); - result += checkEquals(perimeter, 539297, 1); - result += checkEquals(area, 12476152838.5, 1); - return result; -} - -static int Planimeter6() { - /* Check fix for Planimeter lon12 rounding bug found 2012-12-03 */ - double pa[3][2] = {{9, -0.00000000000001}, {9, 180}, {9, 0}}; - double pb[3][2] = {{9, 0.00000000000001}, {9, 0}, {9, 180}}; - double pc[3][2] = {{9, 0.00000000000001}, {9, 180}, {9, 0}}; - double pd[3][2] = {{9, -0.00000000000001}, {9, 0}, {9, 180}}; - struct geod_geodesic g; - double perimeter, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - - planimeter(&g, pa, 3, &perimeter, &area); - result += checkEquals(perimeter, 36026861, 1); - result += checkEquals(area, 0, 1); - planimeter(&g, pb, 3, &perimeter, &area); - result += checkEquals(perimeter, 36026861, 1); - result += checkEquals(area, 0, 1); - planimeter(&g, pc, 3, &perimeter, &area); - result += checkEquals(perimeter, 36026861, 1); - result += checkEquals(area, 0, 1); - planimeter(&g, pd, 3, &perimeter, &area); - result += checkEquals(perimeter, 36026861, 1); - result += checkEquals(area, 0, 1); - return result; -} - -static int Planimeter12() { - /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ - double points[2][2] = {{66.562222222, 0}, {66.562222222, 180}}; - struct geod_geodesic g; - double perimeter, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - planimeter(&g, points, 2, &perimeter, &area); - result += checkEquals(perimeter, 10465729, 1); - result += checkEquals(area, 0, 1); - return result; -} - -static int Planimeter13() { - /* Check encircling pole twice */ - double points[6][2] = {{89,-360}, {89,-240}, {89,-120}, - {89,0}, {89,120}, {89,240}}; - struct geod_geodesic g; - double perimeter, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - planimeter(&g, points, 6, &perimeter, &area); - result += checkEquals(perimeter, 1160741, 1); - result += checkEquals(area, 32415230256.0, 1); - return result; -} - -static int Planimeter15() { - /* Coverage tests, includes Planimeter15 - Planimeter18 (combinations of - reverse and sign) + calls to testpoint, testedge, geod_polygonarea. */ - struct geod_geodesic g; - struct geod_polygon p; - double lat[] = {2, 1, 3}, lon[] = {1, 2, 3}; - double area, s12, azi1; - double r = 18454562325.45119, - a0 = 510065621724088.5093; /* ellipsoid area */ - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_polygon_init(&p, 0); - geod_polygon_addpoint(&g, &p, lat[0], lon[0]); - geod_polygon_addpoint(&g, &p, lat[1], lon[1]); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, 0); - result += checkEquals(area, -r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, 0); - result += checkEquals(area, a0-r, 0.5); - geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, 0); - geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, 0); - result += checkEquals(area, -r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, 0); - result += checkEquals(area, a0-r, 0.5); - geod_polygon_addpoint(&g, &p, lat[2], lon[2]); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_compute(&g, &p, 0, 0, &area, 0); - result += checkEquals(area, r, 0.5); - geod_polygon_compute(&g, &p, 1, 1, &area, 0); - result += checkEquals(area, -r, 0.5); - geod_polygon_compute(&g, &p, 1, 0, &area, 0); - result += checkEquals(area, a0-r, 0.5); - geod_polygonarea(&g, lat, lon, 3, &area, 0); - result += checkEquals(area, r, 0.5); - return result; -} - -static int Planimeter19() { - /* Coverage tests, includes Planimeter19 - Planimeter20 (degenerate - polygons) + extra cases. */ - struct geod_geodesic g; - struct geod_polygon p; - double area, perim; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_polygon_init(&p, 0); - geod_polygon_compute(&g, &p, 0, 1, &area, &perim); - result += area == 0 ? 0 : 1; - result += perim == 0 ? 0 : 1; - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); - result += area == 0 ? 0 : 1; - result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); - result += checkNaN(area); - result += checkNaN(perim); - geod_polygon_addpoint(&g, &p, 1, 1); - geod_polygon_compute(&g, &p, 0, 1, &area, &perim); - result += area == 0 ? 0 : 1; - result += perim == 0 ? 0 : 1; - geod_polygon_init(&p, 1); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); - result += perim == 0 ? 0 : 1; - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); - result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); - result += checkNaN(perim); - geod_polygon_addpoint(&g, &p, 1, 1); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); - result += perim == 0 ? 0 : 1; - return result; -} - -static int Planimeter21() { - /* Some test to add code coverage: multiple circlings of pole (includes - Planimeter21 - Planimeter28) + invocations via testpoint and testedge. - Some of the results for i = 4 in the loop are wrong because we don't - reduce the area to the allowed range correctly. However these cases are - not "simple" polygons, so we'll defer fixing the problem for now. - */ - struct geod_geodesic g; - struct geod_polygon p; - double area, lat = 45, - a = 39.2144607176828184218, s = 8420705.40957178156285, - r = 39433884866571.4277, /* Area for one circuit */ - a0 = 510065621724088.5093; /* Ellipsoid area */ - int result = 0, i; - geod_init(&g, wgs84_a, wgs84_f); - geod_polygon_init(&p, 0); - geod_polygon_addpoint(&g, &p, lat, 60); - geod_polygon_addpoint(&g, &p, lat, 180); - geod_polygon_addpoint(&g, &p, lat, -60); - geod_polygon_addpoint(&g, &p, lat, 60); - geod_polygon_addpoint(&g, &p, lat, 180); - geod_polygon_addpoint(&g, &p, lat, -60); - for (i = 3; i <= 4; ++i) { - geod_polygon_addpoint(&g, &p, lat, 60); - geod_polygon_addpoint(&g, &p, lat, 180); - geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, 0); - if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, 0); - result += checkEquals(area, -i*r + a0, 0.5); - geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, 0); - if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, 0); - result += checkEquals(area, -i*r + a0, 0.5); - geod_polygon_addpoint(&g, &p, lat, -60); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_compute(&g, &p, 0, 0, &area, 0); - if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_compute(&g, &p, 1, 1, &area, 0); - if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_compute(&g, &p, 1, 0, &area, 0); - result += checkEquals(area, -i*r + a0, 0.5); - } - return result; -} - -static int AddEdge1() { - /* Check fix to transitdirect vs transit zero handling inconsistency */ - struct geod_geodesic g; - struct geod_polygon p; - double area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_polygon_init(&p, 0); - geod_polygon_addpoint(&g, &p, 0, 0); - geod_polygon_addedge(&g, &p, 90, 1000); - geod_polygon_addedge(&g, &p, 0, 1000); - geod_polygon_addedge(&g, &p, -90, 1000); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); - result += checkEquals(area, 1000000.0, 0.01); - return result; -} - -static int EmptyPoly() { - struct geod_geodesic g; - struct geod_polygon p; - double perim, area; - int result = 0; - geod_init(&g, wgs84_a, wgs84_f); - geod_polygon_init(&p, 0); - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); - result += area == 0 ? 0 : 1; - result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); - result += checkNaN(area); - result += checkNaN(perim); - geod_polygon_compute(&g, &p, 0, 1, &area, &perim); - result += area == 0 ? 0 : 1; - result += perim == 0 ? 0 : 1; - geod_polygon_init(&p, 1); - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); - result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); - result += checkNaN(perim); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); - result += perim == 0 ? 0 : 1; - geod_polygon_addpoint(&g, &p, 1, 1); - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); - result += checkEquals(perim, 1000, 1e-10); - geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, 0, &perim); - result += checkEquals(perim, 156876.149, 0.5e-3); - return result; -} - -int main() { - int n = 0, i; - if ((i = testinverse())) {++n; printf("testinverse fail: %d\n", i);} - if ((i = testdirect())) {++n; printf("testdirect fail: %d\n", i);} - if ((i = testarcdirect())) {++n; printf("testarcdirect fail: %d\n", i);} - if ((i = GeodSolve0())) {++n; printf("GeodSolve0 fail: %d\n", i);} - if ((i = GeodSolve1())) {++n; printf("GeodSolve1 fail: %d\n", i);} - if ((i = GeodSolve2())) {++n; printf("GeodSolve2 fail: %d\n", i);} - if ((i = GeodSolve4())) {++n; printf("GeodSolve4 fail: %d\n", i);} - if ((i = GeodSolve5())) {++n; printf("GeodSolve5 fail: %d\n", i);} - if ((i = GeodSolve6())) {++n; printf("GeodSolve6 fail: %d\n", i);} - if ((i = GeodSolve9())) {++n; printf("GeodSolve9 fail: %d\n", i);} - if ((i = GeodSolve10())) {++n; printf("GeodSolve10 fail: %d\n", i);} - if ((i = GeodSolve11())) {++n; printf("GeodSolve11 fail: %d\n", i);} - if ((i = GeodSolve12())) {++n; printf("GeodSolve12 fail: %d\n", i);} - if ((i = GeodSolve14())) {++n; printf("GeodSolve14 fail: %d\n", i);} - if ((i = GeodSolve15())) {++n; printf("GeodSolve15 fail: %d\n", i);} - if ((i = GeodSolve17())) {++n; printf("GeodSolve17 fail: %d\n", i);} - if ((i = GeodSolve26())) {++n; printf("GeodSolve26 fail: %d\n", i);} - if ((i = GeodSolve28())) {++n; printf("GeodSolve28 fail: %d\n", i);} - if ((i = GeodSolve33())) {++n; printf("GeodSolve33 fail: %d\n", i);} - if ((i = GeodSolve55())) {++n; printf("GeodSolve55 fail: %d\n", i);} - if ((i = GeodSolve59())) {++n; printf("GeodSolve59 fail: %d\n", i);} - if ((i = GeodSolve61())) {++n; printf("GeodSolve61 fail: %d\n", i);} - if ((i = GeodSolve65())) {++n; printf("GeodSolve65 fail: %d\n", i);} - if ((i = GeodSolve67())) {++n; printf("GeodSolve67 fail: %d\n", i);} - if ((i = GeodSolve71())) {++n; printf("GeodSolve71 fail: %d\n", i);} - if ((i = GeodSolve73())) {++n; printf("GeodSolve73 fail: %d\n", i);} - if ((i = GeodSolve74())) {++n; printf("GeodSolve74 fail: %d\n", i);} - if ((i = GeodSolve76())) {++n; printf("GeodSolve76 fail: %d\n", i);} - if ((i = GeodSolve78())) {++n; printf("GeodSolve78 fail: %d\n", i);} - if ((i = GeodSolve80())) {++n; printf("GeodSolve80 fail: %d\n", i);} - if ((i = Planimeter0())) {++n; printf("Planimeter0 fail: %d\n", i);} - if ((i = Planimeter5())) {++n; printf("Planimeter5 fail: %d\n", i);} - if ((i = Planimeter6())) {++n; printf("Planimeter6 fail: %d\n", i);} - if ((i = Planimeter12())) {++n; printf("Planimeter12 fail: %d\n", i);} - if ((i = Planimeter13())) {++n; printf("Planimeter13 fail: %d\n", i);} - if ((i = Planimeter15())) {++n; printf("Planimeter15 fail: %d\n", i);} - if ((i = Planimeter19())) {++n; printf("Planimeter19 fail: %d\n", i);} - if ((i = Planimeter21())) {++n; printf("Planimeter21 fail: %d\n", i);} - if ((i = AddEdge1())) {++n; printf("AddEdge1 fail: %d\n", i);} - if ((i = EmptyPoly())) {++n; printf("EmptyPoly fail: %d\n", i);} - return n; -} - -/** @endcond */ diff --git a/src/geodtest.cpp b/src/geodtest.cpp new file mode 100644 index 00000000..0ee86d5c --- /dev/null +++ b/src/geodtest.cpp @@ -0,0 +1,1069 @@ +/** + * \file geodtest.c + * \brief Test suite for the geodesic routines in C + * + * Run these tests by configuring with cmake and running "make test". + * + * Copyright (c) Charles Karney (2015-2018) and licensed + * under the MIT/X11 License. For more information, see + * https://geographiclib.sourceforge.io/ + **********************************************************************/ + +/** @cond SKIP */ + +#include "geodesic.h" +#include +#include + +#if defined(_MSC_VER) +/* Squelch warnings about assignment within conditional expression */ +# pragma warning (disable: 4706) +#endif + +static const double wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ + +static int checkEquals(double x, double y, double d) { + if (fabs(x - y) <= d) + return 0; + printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); + return 1; +} + +static int checkNaN(double x) { + if (x != x) + return 0; + printf("checkNaN fails: %.7g\n", x); + return 1; +} + +static const int ncases = 20; +static const double testcases[20][12] = { + {35.60777, -139.44815, 111.098748429560326, + -11.17491, -69.95921, 129.289270889708762, + 8935244.5604818305, 80.50729714281974, 6273170.2055303837, + 0.16606318447386067, 0.16479116945612937, 12841384694976.432}, + {55.52454, 106.05087, 22.020059880982801, + 77.03196, 197.18234, 109.112041110671519, + 4105086.1713924406, 36.892740690445894, 3828869.3344387607, + 0.80076349608092607, 0.80101006984201008, 61674961290615.615}, + {-21.97856, 142.59065, -32.44456876433189, + 41.84138, 98.56635, -41.84359951440466, + 8394328.894657671, 75.62930491011522, 6161154.5773110616, + 0.24816339233950381, 0.24930251203627892, -6637997720646.717}, + {-66.99028, 112.2363, 173.73491240878403, + -12.70631, 285.90344, 2.512956620913668, + 11150344.2312080241, 100.278634181155759, 6289939.5670446687, + -0.17199490274700385, -0.17722569526345708, -121287239862139.744}, + {-17.42761, 173.34268, -159.033557661192928, + -15.84784, 5.93557, -20.787484651536988, + 16076603.1631180673, 144.640108810286253, 3732902.1583877189, + -0.81273638700070476, -0.81299800519154474, 97825992354058.708}, + {32.84994, 48.28919, 150.492927788121982, + -56.28556, 202.29132, 48.113449399816759, + 16727068.9438164461, 150.565799985466607, 3147838.1910180939, + -0.87334918086923126, -0.86505036767110637, -72445258525585.010}, + {6.96833, 52.74123, 92.581585386317712, + -7.39675, 206.17291, 90.721692165923907, + 17102477.2496958388, 154.147366239113561, 2772035.6169917581, + -0.89991282520302447, -0.89986892177110739, -1311796973197.995}, + {-50.56724, -16.30485, -105.439679907590164, + -33.56571, -94.97412, -47.348547835650331, + 6455670.5118668696, 58.083719495371259, 5409150.7979815838, + 0.53053508035997263, 0.52988722644436602, 41071447902810.047}, + {-58.93002, -8.90775, 140.965397902500679, + -8.91104, 133.13503, 19.255429433416599, + 11756066.0219864627, 105.755691241406877, 6151101.2270708536, + -0.26548622269867183, -0.27068483874510741, -86143460552774.735}, + {-68.82867, -74.28391, 93.774347763114881, + -50.63005, -8.36685, 34.65564085411343, + 3956936.926063544, 35.572254987389284, 3708890.9544062657, + 0.81443963736383502, 0.81420859815358342, -41845309450093.787}, + {-10.62672, -32.0898, -86.426713286747751, + 5.883, -134.31681, -80.473780971034875, + 11470869.3864563009, 103.387395634504061, 6184411.6622659713, + -0.23138683500430237, -0.23155097622286792, 4198803992123.548}, + {-21.76221, 166.90563, 29.319421206936428, + 48.72884, 213.97627, 43.508671946410168, + 9098627.3986554915, 81.963476716121964, 6299240.9166992283, + 0.13965943368590333, 0.14152969707656796, 10024709850277.476}, + {-19.79938, -174.47484, 71.167275780171533, + -11.99349, -154.35109, 65.589099775199228, + 2319004.8601169389, 20.896611684802389, 2267960.8703918325, + 0.93427001867125849, 0.93424887135032789, -3935477535005.785}, + {-11.95887, -116.94513, 92.712619830452549, + 4.57352, 7.16501, 78.64960934409585, + 13834722.5801401374, 124.688684161089762, 5228093.177931598, + -0.56879356755666463, -0.56918731952397221, -9919582785894.853}, + {-87.85331, 85.66836, -65.120313040242748, + 66.48646, 16.09921, -4.888658719272296, + 17286615.3147144645, 155.58592449699137, 2635887.4729110181, + -0.90697975771398578, -0.91095608883042767, 42667211366919.534}, + {1.74708, 128.32011, -101.584843631173858, + -11.16617, 11.87109, -86.325793296437476, + 12942901.1241347408, 116.650512484301857, 5682744.8413270572, + -0.44857868222697644, -0.44824490340007729, 10763055294345.653}, + {-25.72959, -144.90758, -153.647468693117198, + -57.70581, -269.17879, -48.343983158876487, + 9413446.7452453107, 84.664533838404295, 6356176.6898881281, + 0.09492245755254703, 0.09737058264766572, 74515122850712.444}, + {-41.22777, 122.32875, 14.285113402275739, + -7.57291, 130.37946, 10.805303085187369, + 3812686.035106021, 34.34330804743883, 3588703.8812128856, + 0.82605222593217889, 0.82572158200920196, -2456961531057.857}, + {11.01307, 138.25278, 79.43682622782374, + 6.62726, 247.05981, 103.708090215522657, + 11911190.819018408, 107.341669954114577, 6070904.722786735, + -0.29767608923657404, -0.29785143390252321, 17121631423099.696}, + {-29.47124, 95.14681, -163.779130441688382, + -27.46601, -69.15955, -15.909335945554969, + 13487015.8381145492, 121.294026715742277, 5481428.9945736388, + -0.51527225545373252, -0.51556587964721788, 104679964020340.318}}; + +static int testinverse() { + double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; + double azi1a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; + struct geod_geodesic g; + int i, result = 0; + geod_init(&g, wgs84_a, wgs84_f); + for (i = 0; i < ncases; ++i) { + lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; + lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; + s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; + M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; + a12a = geod_geninverse(&g, lat1, lon1, lat2, lon2, &s12a, &azi1a, &azi2a, + &m12a, &M12a, &M21a, &S12a); + result += checkEquals(azi1, azi1a, 1e-13); + result += checkEquals(azi2, azi2a, 1e-13); + result += checkEquals(s12, s12a, 1e-8); + result += checkEquals(a12, a12a, 1e-13); + result += checkEquals(m12, m12a, 1e-8); + result += checkEquals(M12, M12a, 1e-15); + result += checkEquals(M21, M21a, 1e-15); + result += checkEquals(S12, S12a, 0.1); + } + return result; +} + +static int testdirect() { + double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; + double lat2a, lon2a, azi2a, a12a, m12a, M12a, M21a, S12a; + struct geod_geodesic g; + int i, result = 0; + unsigned flags = GEOD_LONG_UNROLL; + geod_init(&g, wgs84_a, wgs84_f); + for (i = 0; i < ncases; ++i) { + lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; + lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; + s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; + M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; + a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, s12, + &lat2a, &lon2a, &azi2a, 0, + &m12a, &M12a, &M21a, &S12a); + result += checkEquals(lat2, lat2a, 1e-13); + result += checkEquals(lon2, lon2a, 1e-13); + result += checkEquals(azi2, azi2a, 1e-13); + result += checkEquals(a12, a12a, 1e-13); + result += checkEquals(m12, m12a, 1e-8); + result += checkEquals(M12, M12a, 1e-15); + result += checkEquals(M21, M21a, 1e-15); + result += checkEquals(S12, S12a, 0.1); + } + return result; +} + +static int testarcdirect() { + double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; + double lat2a, lon2a, azi2a, s12a, m12a, M12a, M21a, S12a; + struct geod_geodesic g; + int i, result = 0; + unsigned flags = GEOD_ARCMODE | GEOD_LONG_UNROLL; + geod_init(&g, wgs84_a, wgs84_f); + for (i = 0; i < ncases; ++i) { + lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; + lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; + s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; + M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; + geod_gendirect(&g, lat1, lon1, azi1, flags, a12, + &lat2a, &lon2a, &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); + result += checkEquals(lat2, lat2a, 1e-13); + result += checkEquals(lon2, lon2a, 1e-13); + result += checkEquals(azi2, azi2a, 1e-13); + result += checkEquals(s12, s12a, 1e-8); + result += checkEquals(m12, m12a, 1e-8); + result += checkEquals(M12, M12a, 1e-15); + result += checkEquals(M21, M21a, 1e-15); + result += checkEquals(S12, S12a, 0.1); + } + return result; +} + +static int GeodSolve0() { + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 40.6, -73.8, 49.01666667, 2.55, &s12, &azi1, &azi2); + result += checkEquals(azi1, 53.47022, 0.5e-5); + result += checkEquals(azi2, 111.59367, 0.5e-5); + result += checkEquals(s12, 5853226, 0.5); + return result; +} + +static int GeodSolve1() { + double lat2, lon2, azi2; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_direct(&g, 40.63972222, -73.77888889, 53.5, 5850e3, + &lat2, &lon2, &azi2); + result += checkEquals(lat2, 49.01467, 0.5e-5); + result += checkEquals(lon2, 2.56106, 0.5e-5); + result += checkEquals(azi2, 111.62947, 0.5e-5); + return result; +} + +static int GeodSolve2() { + /* Check fix for antipodal prolate bug found 2010-09-04 */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, 6.4e6, -1/150.0); + geod_inverse(&g, 0.07476, 0, -0.07476, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00078, 0.5e-5); + result += checkEquals(azi2, 90.00078, 0.5e-5); + result += checkEquals(s12, 20106193, 0.5); + geod_inverse(&g, 0.1, 0, -0.1, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00105, 0.5e-5); + result += checkEquals(azi2, 90.00105, 0.5e-5); + result += checkEquals(s12, 20106193, 0.5); + return result; +} + +static int GeodSolve4() { + /* Check fix for short line bug found 2010-05-21 */ + double s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 36.493349428792, 0, 36.49334942879201, .0000008, + &s12, 0, 0); + result += checkEquals(s12, 0.072, 0.5e-3); + return result; +} + +static int GeodSolve5() { + /* Check fix for point2=pole bug found 2010-05-03 */ + double lat2, lon2, azi2; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_direct(&g, 0.01777745589997, 30, 0, 10e6, &lat2, &lon2, &azi2); + result += checkEquals(lat2, 90, 0.5e-5); + if (lon2 < 0) { + result += checkEquals(lon2, -150, 0.5e-5); + result += checkEquals(fabs(azi2), 180, 0.5e-5); + } else { + result += checkEquals(lon2, 30, 0.5e-5); + result += checkEquals(azi2, 0, 0.5e-5); + } + return result; +} + +static int GeodSolve6() { + /* Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 + * x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). */ + double s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 88.202499451857, 0, + -88.202499451857, 179.981022032992859592, &s12, 0, 0); + result += checkEquals(s12, 20003898.214, 0.5e-3); + geod_inverse(&g, 89.262080389218, 0, + -89.262080389218, 179.992207982775375662, &s12, 0, 0); + result += checkEquals(s12, 20003925.854, 0.5e-3); + geod_inverse(&g, 89.333123580033, 0, + -89.333123580032997687, 179.99295812360148422, &s12, 0, 0); + result += checkEquals(s12, 20003926.881, 0.5e-3); + return result; +} + +static int GeodSolve9() { + /* Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) */ + double s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 56.320923501171, 0, + -56.320923501171, 179.664747671772880215, &s12, 0, 0); + result += checkEquals(s12, 19993558.287, 0.5e-3); + return result; +} + +static int GeodSolve10() { + /* Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio + * 10 rel + debug) */ + double s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 52.784459512564, 0, + -52.784459512563990912, 179.634407464943777557, &s12, 0, 0); + result += checkEquals(s12, 19991596.095, 0.5e-3); + return result; +} + +static int GeodSolve11() { + /* Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio + * 10 rel + debug) */ + double s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 48.522876735459, 0, + -48.52287673545898293, 179.599720456223079643, &s12, 0, 0); + result += checkEquals(s12, 19989144.774, 0.5e-3); + return result; +} + +static int GeodSolve12() { + /* Check fix for inverse geodesics on extreme prolate/oblate + * ellipsoids Reported 2012-08-29 Stefan Guenther + * ; fixed 2012-10-07 */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, 89.8, -1.83); + geod_inverse(&g, 0, 0, -10, 160, &s12, &azi1, &azi2); + result += checkEquals(azi1, 120.27, 1e-2); + result += checkEquals(azi2, 105.15, 1e-2); + result += checkEquals(s12, 266.7, 1e-1); + return result; +} + +static int GeodSolve14() { + /* Check fix for inverse ignoring lon12 = nan */ + double azi1, azi2, s12, nan; + struct geod_geodesic g; + int result = 0; + { + double minus1 = -1; + /* cppcheck-suppress wrongmathcall */ + nan = sqrt(minus1); + } + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 0, 0, 1, nan, &s12, &azi1, &azi2); + result += checkNaN(azi1); + result += checkNaN(azi2); + result += checkNaN(s12); + return result; +} + +static int GeodSolve15() { + /* Initial implementation of Math::eatanhe was wrong for e^2 < 0. This + * checks that this is fixed. */ + double S12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, 6.4e6, -1/150.0); + geod_gendirect(&g, 1, 2, 3, 0, 4, + 0, 0, 0, 0, 0, 0, 0, &S12); + result += checkEquals(S12, 23700, 0.5); + return result; +} + +static int GeodSolve17() { + /* Check fix for LONG_UNROLL bug found on 2015-05-07 */ + double lat2, lon2, azi2; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + unsigned flags = GEOD_LONG_UNROLL; + geod_init(&g, wgs84_a, wgs84_f); + geod_gendirect(&g, 40, -75, -10, flags, 2e7, + &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, -39, 1); + result += checkEquals(lon2, -254, 1); + result += checkEquals(azi2, -170, 1); + geod_lineinit(&l, &g, 40, -75, -10, 0); + geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, -39, 1); + result += checkEquals(lon2, -254, 1); + result += checkEquals(azi2, -170, 1); + geod_direct(&g, 40, -75, -10, 2e7, &lat2, &lon2, &azi2); + result += checkEquals(lat2, -39, 1); + result += checkEquals(lon2, 105, 1); + result += checkEquals(azi2, -170, 1); + geod_position(&l, 2e7, &lat2, &lon2, &azi2); + result += checkEquals(lat2, -39, 1); + result += checkEquals(lon2, 105, 1); + result += checkEquals(azi2, -170, 1); + return result; +} + +static int GeodSolve26() { + /* Check 0/0 problem with area calculation on sphere 2015-09-08 */ + double S12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, 6.4e6, 0); + geod_geninverse(&g, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, &S12); + result += checkEquals(S12, 49911046115.0, 0.5); + return result; +} + +static int GeodSolve28() { + /* Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in + * Java implementation fixed on 2015-05-19). */ + double a12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, 6.4e6, 0.1); + a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, 0, 0, 0, 0, 0, 0, 0, 0); + result += checkEquals(a12, 48.55570690, 0.5e-8); + return result; +} + +static int GeodSolve33() { + /* Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- + * sind(-0.0) = +0.0 -- and in some version of Visual Studio -- + * fmod(-0.0, 360.0) = +0.0. */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00000, 0.5e-5); + result += checkEquals(azi2, 90.00000, 0.5e-5); + result += checkEquals(s12, 19926189, 0.5); + geod_inverse(&g, 0, 0, 0, 179.5, &s12, &azi1, &azi2); + result += checkEquals(azi1, 55.96650, 0.5e-5); + result += checkEquals(azi2, 124.03350, 0.5e-5); + result += checkEquals(s12, 19980862, 0.5); + geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 20003931, 0.5); + geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 19893357, 0.5); + geod_init(&g, 6.4e6, 0); + geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00000, 0.5e-5); + result += checkEquals(azi2, 90.00000, 0.5e-5); + result += checkEquals(s12, 19994492, 0.5); + geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 20106193, 0.5); + geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 19994492, 0.5); + geod_init(&g, 6.4e6, -1/300.0); + geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00000, 0.5e-5); + result += checkEquals(azi2, 90.00000, 0.5e-5); + result += checkEquals(s12, 19994492, 0.5); + geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 90.00000, 0.5e-5); + result += checkEquals(azi2, 90.00000, 0.5e-5); + result += checkEquals(s12, 20106193, 0.5); + geod_inverse(&g, 0, 0, 0.5, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 33.02493, 0.5e-5); + result += checkEquals(azi2, 146.97364, 0.5e-5); + result += checkEquals(s12, 20082617, 0.5); + geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 20027270, 0.5); + + return result; +} + +static int GeodSolve55() { + /* Check fix for nan + point on equator or pole not returning all nans in + * Geodesic::Inverse, found 2015-09-23. */ + double azi1, azi2, s12, nan; + struct geod_geodesic g; + int result = 0; + { + double minus1 = -1; + /* cppcheck-suppress wrongmathcall */ + nan = sqrt(minus1); + } + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, nan, 0, 0, 90, &s12, &azi1, &azi2); + result += checkNaN(azi1); + result += checkNaN(azi2); + result += checkNaN(s12); + geod_inverse(&g, nan, 0, 90, 9, &s12, &azi1, &azi2); + result += checkNaN(azi1); + result += checkNaN(azi2); + result += checkNaN(s12); + return result; +} + +static int GeodSolve59() { + /* Check for points close with longitudes close to 180 deg apart. */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 5, 0.00000000000001, 10, 180, &s12, &azi1, &azi2); + result += checkEquals(azi1, 0.000000000000035, 1.5e-14); + result += checkEquals(azi2, 179.99999999999996, 1.5e-14); + result += checkEquals(s12, 18345191.174332713, 5e-9); + return result; +} + +static int GeodSolve61() { + /* Make sure small negative azimuths are west-going */ + double lat2, lon2, azi2; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + unsigned flags = GEOD_LONG_UNROLL; + geod_init(&g, wgs84_a, wgs84_f); + geod_gendirect(&g, 45, 0, -0.000000000000000003, flags, 1e7, + &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, 45.30632, 0.5e-5); + result += checkEquals(lon2, -180, 0.5e-5); + result += checkEquals(fabs(azi2), 180, 0.5e-5); + geod_inverseline(&l, &g, 45, 0, 80, -0.000000000000000003, 0); + geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, 45.30632, 0.5e-5); + result += checkEquals(lon2, -180, 0.5e-5); + result += checkEquals(fabs(azi2), 180, 0.5e-5); + return result; +} + +static int GeodSolve65() { + /* Check for bug in east-going check in GeodesicLine (needed to check for + * sign of 0) and sign error in area calculation due to a bogus override of + * the code for alp12. Found/fixed on 2015-12-19. */ + double lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + unsigned flags = GEOD_LONG_UNROLL, caps = GEOD_ALL; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverseline(&l, &g, 30, -0.000000000000000001, -31, 180, caps); + a12 = geod_genposition(&l, flags, 1e7, + &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); + result += checkEquals(lat2, -60.23169, 0.5e-5); + result += checkEquals(lon2, -0.00000, 0.5e-5); + result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); + result += checkEquals(s12, 10000000, 0.5); + result += checkEquals(a12, 90.06544, 0.5e-5); + result += checkEquals(m12, 6363636, 0.5); + result += checkEquals(M12, -0.0012834, 0.5e-7); + result += checkEquals(M21, 0.0013749, 0.5e-7); + result += checkEquals(S12, 0, 0.5); + a12 = geod_genposition(&l, flags, 2e7, + &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); + result += checkEquals(lat2, -30.03547, 0.5e-5); + result += checkEquals(lon2, -180.00000, 0.5e-5); + result += checkEquals(azi2, -0.00000, 0.5e-5); + result += checkEquals(s12, 20000000, 0.5); + result += checkEquals(a12, 179.96459, 0.5e-5); + result += checkEquals(m12, 54342, 0.5); + result += checkEquals(M12, -1.0045592, 0.5e-7); + result += checkEquals(M21, -0.9954339, 0.5e-7); + result += checkEquals(S12, 127516405431022.0, 0.5); + return result; +} + +static int GeodSolve67() { + /* Check for InverseLine if line is slightly west of S and that s13 is + correctly set. */ + double lat2, lon2, azi2; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + unsigned flags = GEOD_LONG_UNROLL; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverseline(&l, &g, -5, -0.000000000000002, -10, 180, 0); + geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, 4.96445, 0.5e-5); + result += checkEquals(lon2, -180.00000, 0.5e-5); + result += checkEquals(azi2, -0.00000, 0.5e-5); + geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + result += checkEquals(lat2, -87.52461, 0.5e-5); + result += checkEquals(lon2, -0.00000, 0.5e-5); + result += checkEquals(azi2, -180.00000, 0.5e-5); + return result; +} + +static int GeodSolve71() { + /* Check that DirectLine sets s13. */ + double lat2, lon2, azi2; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_directline(&l, &g, 1, 2, 45, 1e7, 0); + geod_position(&l, 0.5 * l.s13, &lat2, &lon2, &azi2); + result += checkEquals(lat2, 30.92625, 0.5e-5); + result += checkEquals(lon2, 37.54640, 0.5e-5); + result += checkEquals(azi2, 55.43104, 0.5e-5); + return result; +} + +static int GeodSolve73() { + /* Check for backwards from the pole bug reported by Anon on 2016-02-13. + * This only affected the Java implementation. It was introduced in Java + * version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. */ + double lat2, lon2, azi2; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_direct(&g, 90, 10, 180, -1e6, + &lat2, &lon2, &azi2); + result += checkEquals(lat2, 81.04623, 0.5e-5); + result += checkEquals(lon2, -170, 0.5e-5); + result += azi2 == 0 ? 0 : 1; + result += 1/azi2 > 0 ? 0 : 1; /* Check that azi2 = +0.0 not -0.0 */ + return result; +} + +static void planimeter(const struct geod_geodesic* g, + double points[][2], int N, + double* perimeter, double* area) { + struct geod_polygon p; + int i; + geod_polygon_init(&p, 0); + for (i = 0; i < N; ++i) + geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); + geod_polygon_compute(g, &p, 0, 1, area, perimeter); +} + +static void polylength(const struct geod_geodesic* g, + double points[][2], int N, + double* perimeter) { + struct geod_polygon p; + int i; + geod_polygon_init(&p, 1); + for (i = 0; i < N; ++i) + geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); + geod_polygon_compute(g, &p, 0, 1, 0, perimeter); +} + +static int GeodSolve74() { + /* Check fix for inaccurate areas, bug introduced in v1.46, fixed + 2015-10-16. */ + double a12, s12, azi1, azi2, m12, M12, M21, S12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + a12 = geod_geninverse(&g, 54.1589, 15.3872, 54.1591, 15.3877, + &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); + result += checkEquals(azi1, 55.723110355, 5e-9); + result += checkEquals(azi2, 55.723515675, 5e-9); + result += checkEquals(s12, 39.527686385, 5e-9); + result += checkEquals(a12, 0.000355495, 5e-9); + result += checkEquals(m12, 39.527686385, 5e-9); + result += checkEquals(M12, 0.999999995, 5e-9); + result += checkEquals(M21, 0.999999995, 5e-9); + result += checkEquals(S12, 286698586.30197, 5e-4); + return result; +} + +static int GeodSolve76() { + /* The distance from Wellington and Salamanca (a classic failure of + Vincenty) */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, -(41+19/60.0), 174+49/60.0, 40+58/60.0, -(5+30/60.0), + &s12, &azi1, &azi2); + result += checkEquals(azi1, 160.39137649664, 0.5e-11); + result += checkEquals(azi2, 19.50042925176, 0.5e-11); + result += checkEquals(s12, 19960543.857179, 0.5e-6); + return result; +} + +static int GeodSolve78() { + /* An example where the NGS calculator fails to converge */ + double azi1, azi2, s12; + struct geod_geodesic g; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_inverse(&g, 27.2, 0.0, -27.1, 179.5, &s12, &azi1, &azi2); + result += checkEquals(azi1, 45.82468716758, 0.5e-11); + result += checkEquals(azi2, 134.22776532670, 0.5e-11); + result += checkEquals(s12, 19974354.765767, 0.5e-6); + return result; +} + +static int GeodSolve80() { + /* Some tests to add code coverage: computing scale in special cases + zero + length geodesic (includes GeodSolve80 - GeodSolve83) + using an incapable + line. */ + double a12, s12, azi1, azi2, m12, M12, M21, S12; + struct geod_geodesic g; + struct geod_geodesicline l; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_geninverse(&g, 0, 0, 0, 90, 0, 0, 0, 0, &M12, &M21, 0); + result += checkEquals(M12, -0.00528427534, 0.5e-10); + result += checkEquals(M21, -0.00528427534, 0.5e-10); + geod_geninverse(&g, 0, 0, 1e-6, 1e-6, 0, 0, 0, 0, &M12, &M21, 0); + result += checkEquals(M12, 1, 0.5e-10); + result += checkEquals(M21, 1, 0.5e-10); + a12 = geod_geninverse(&g, 20.001, 0, 20.001, 0, + &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); + result += checkEquals(a12, 0, 1e-13); + result += checkEquals(s12, 0, 1e-8); + result += checkEquals(azi1, 180, 1e-13); + result += checkEquals(azi2, 180, 1e-13); + result += checkEquals(m12, 0, 1e-8); + result += checkEquals(M12, 1, 1e-15); + result += checkEquals(M21, 1, 1e-15); + result += checkEquals(S12, 0, 1e-10); + a12 = geod_geninverse(&g, 90, 0, 90, 180, + &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); + result += checkEquals(a12, 0, 1e-13); + result += checkEquals(s12, 0, 1e-8); + result += checkEquals(azi1, 0, 1e-13); + result += checkEquals(azi2, 180, 1e-13); + result += checkEquals(m12, 0, 1e-8); + result += checkEquals(M12, 1, 1e-15); + result += checkEquals(M21, 1, 1e-15); + result += checkEquals(S12, 127516405431022, 0.5); + /* An incapable line which can't take distance as input */ + geod_lineinit(&l, &g, 1, 2, 90, GEOD_LATITUDE); + a12 = geod_genposition(&l, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0); + result += checkNaN(a12); + return result; +} + +static int Planimeter0() { + /* Check fix for pole-encircling bug found 2011-03-16 */ + double pa[4][2] = {{89, 0}, {89, 90}, {89, 180}, {89, 270}}; + double pb[4][2] = {{-89, 0}, {-89, 90}, {-89, 180}, {-89, 270}}; + double pc[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; + double pd[3][2] = {{90, 0}, {0, 0}, {0, 90}}; + struct geod_geodesic g; + double perimeter, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + + planimeter(&g, pa, 4, &perimeter, &area); + result += checkEquals(perimeter, 631819.8745, 1e-4); + result += checkEquals(area, 24952305678.0, 1); + + planimeter(&g, pb, 4, &perimeter, &area); + result += checkEquals(perimeter, 631819.8745, 1e-4); + result += checkEquals(area, -24952305678.0, 1); + + planimeter(&g, pc, 4, &perimeter, &area); + result += checkEquals(perimeter, 627598.2731, 1e-4); + result += checkEquals(area, 24619419146.0, 1); + + planimeter(&g, pd, 3, &perimeter, &area); + result += checkEquals(perimeter, 30022685, 1); + result += checkEquals(area, 63758202715511.0, 1); + + polylength(&g, pd, 3, &perimeter); + result += checkEquals(perimeter, 20020719, 1); + + return result; +} + +static int Planimeter5() { + /* Check fix for Planimeter pole crossing bug found 2011-06-24 */ + double points[3][2] = {{89, 0.1}, {89, 90.1}, {89, -179.9}}; + struct geod_geodesic g; + double perimeter, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + planimeter(&g, points, 3, &perimeter, &area); + result += checkEquals(perimeter, 539297, 1); + result += checkEquals(area, 12476152838.5, 1); + return result; +} + +static int Planimeter6() { + /* Check fix for Planimeter lon12 rounding bug found 2012-12-03 */ + double pa[3][2] = {{9, -0.00000000000001}, {9, 180}, {9, 0}}; + double pb[3][2] = {{9, 0.00000000000001}, {9, 0}, {9, 180}}; + double pc[3][2] = {{9, 0.00000000000001}, {9, 180}, {9, 0}}; + double pd[3][2] = {{9, -0.00000000000001}, {9, 0}, {9, 180}}; + struct geod_geodesic g; + double perimeter, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + + planimeter(&g, pa, 3, &perimeter, &area); + result += checkEquals(perimeter, 36026861, 1); + result += checkEquals(area, 0, 1); + planimeter(&g, pb, 3, &perimeter, &area); + result += checkEquals(perimeter, 36026861, 1); + result += checkEquals(area, 0, 1); + planimeter(&g, pc, 3, &perimeter, &area); + result += checkEquals(perimeter, 36026861, 1); + result += checkEquals(area, 0, 1); + planimeter(&g, pd, 3, &perimeter, &area); + result += checkEquals(perimeter, 36026861, 1); + result += checkEquals(area, 0, 1); + return result; +} + +static int Planimeter12() { + /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ + double points[2][2] = {{66.562222222, 0}, {66.562222222, 180}}; + struct geod_geodesic g; + double perimeter, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + planimeter(&g, points, 2, &perimeter, &area); + result += checkEquals(perimeter, 10465729, 1); + result += checkEquals(area, 0, 1); + return result; +} + +static int Planimeter13() { + /* Check encircling pole twice */ + double points[6][2] = {{89,-360}, {89,-240}, {89,-120}, + {89,0}, {89,120}, {89,240}}; + struct geod_geodesic g; + double perimeter, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + planimeter(&g, points, 6, &perimeter, &area); + result += checkEquals(perimeter, 1160741, 1); + result += checkEquals(area, 32415230256.0, 1); + return result; +} + +static int Planimeter15() { + /* Coverage tests, includes Planimeter15 - Planimeter18 (combinations of + reverse and sign) + calls to testpoint, testedge, geod_polygonarea. */ + struct geod_geodesic g; + struct geod_polygon p; + double lat[] = {2, 1, 3}, lon[] = {1, 2, 3}; + double area, s12, azi1; + double r = 18454562325.45119, + a0 = 510065621724088.5093; /* ellipsoid area */ + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_polygon_init(&p, 0); + geod_polygon_addpoint(&g, &p, lat[0], lon[0]); + geod_polygon_addpoint(&g, &p, lat[1], lon[1]); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, 0); + result += checkEquals(area, -r, 0.5); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, 0); + result += checkEquals(area, a0-r, 0.5); + geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, 0); + geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, 0); + result += checkEquals(area, -r, 0.5); + geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, 0); + result += checkEquals(area, a0-r, 0.5); + geod_polygon_addpoint(&g, &p, lat[2], lon[2]); + geod_polygon_compute(&g, &p, 0, 1, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_compute(&g, &p, 0, 0, &area, 0); + result += checkEquals(area, r, 0.5); + geod_polygon_compute(&g, &p, 1, 1, &area, 0); + result += checkEquals(area, -r, 0.5); + geod_polygon_compute(&g, &p, 1, 0, &area, 0); + result += checkEquals(area, a0-r, 0.5); + geod_polygonarea(&g, lat, lon, 3, &area, 0); + result += checkEquals(area, r, 0.5); + return result; +} + +static int Planimeter19() { + /* Coverage tests, includes Planimeter19 - Planimeter20 (degenerate + polygons) + extra cases. */ + struct geod_geodesic g; + struct geod_polygon p; + double area, perim; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_polygon_init(&p, 0); + geod_polygon_compute(&g, &p, 0, 1, &area, &perim); + result += area == 0 ? 0 : 1; + result += perim == 0 ? 0 : 1; + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); + result += area == 0 ? 0 : 1; + result += perim == 0 ? 0 : 1; + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); + result += checkNaN(area); + result += checkNaN(perim); + geod_polygon_addpoint(&g, &p, 1, 1); + geod_polygon_compute(&g, &p, 0, 1, &area, &perim); + result += area == 0 ? 0 : 1; + result += perim == 0 ? 0 : 1; + geod_polygon_init(&p, 1); + geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + result += perim == 0 ? 0 : 1; + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); + result += perim == 0 ? 0 : 1; + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + result += checkNaN(perim); + geod_polygon_addpoint(&g, &p, 1, 1); + geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + result += perim == 0 ? 0 : 1; + return result; +} + +static int Planimeter21() { + /* Some test to add code coverage: multiple circlings of pole (includes + Planimeter21 - Planimeter28) + invocations via testpoint and testedge. + Some of the results for i = 4 in the loop are wrong because we don't + reduce the area to the allowed range correctly. However these cases are + not "simple" polygons, so we'll defer fixing the problem for now. + */ + struct geod_geodesic g; + struct geod_polygon p; + double area, lat = 45, + a = 39.2144607176828184218, s = 8420705.40957178156285, + r = 39433884866571.4277, /* Area for one circuit */ + a0 = 510065621724088.5093; /* Ellipsoid area */ + int result = 0, i; + geod_init(&g, wgs84_a, wgs84_f); + geod_polygon_init(&p, 0); + geod_polygon_addpoint(&g, &p, lat, 60); + geod_polygon_addpoint(&g, &p, lat, 180); + geod_polygon_addpoint(&g, &p, lat, -60); + geod_polygon_addpoint(&g, &p, lat, 60); + geod_polygon_addpoint(&g, &p, lat, 180); + geod_polygon_addpoint(&g, &p, lat, -60); + for (i = 3; i <= 4; ++i) { + geod_polygon_addpoint(&g, &p, lat, 60); + geod_polygon_addpoint(&g, &p, lat, 180); + geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, 0); + if (i != 4) result += checkEquals(area, -i*r, 0.5); + geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, 0); + result += checkEquals(area, -i*r + a0, 0.5); + geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, 0); + if (i != 4) result += checkEquals(area, -i*r, 0.5); + geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, 0); + result += checkEquals(area, -i*r + a0, 0.5); + geod_polygon_addpoint(&g, &p, lat, -60); + geod_polygon_compute(&g, &p, 0, 1, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_compute(&g, &p, 0, 0, &area, 0); + if (i != 4) result += checkEquals(area, i*r, 0.5); + geod_polygon_compute(&g, &p, 1, 1, &area, 0); + if (i != 4) result += checkEquals(area, -i*r, 0.5); + geod_polygon_compute(&g, &p, 1, 0, &area, 0); + result += checkEquals(area, -i*r + a0, 0.5); + } + return result; +} + +static int AddEdge1() { + /* Check fix to transitdirect vs transit zero handling inconsistency */ + struct geod_geodesic g; + struct geod_polygon p; + double area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_polygon_init(&p, 0); + geod_polygon_addpoint(&g, &p, 0, 0); + geod_polygon_addedge(&g, &p, 90, 1000); + geod_polygon_addedge(&g, &p, 0, 1000); + geod_polygon_addedge(&g, &p, -90, 1000); + geod_polygon_compute(&g, &p, 0, 1, &area, 0); + result += checkEquals(area, 1000000.0, 0.01); + return result; +} + +static int EmptyPoly() { + struct geod_geodesic g; + struct geod_polygon p; + double perim, area; + int result = 0; + geod_init(&g, wgs84_a, wgs84_f); + geod_polygon_init(&p, 0); + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); + result += area == 0 ? 0 : 1; + result += perim == 0 ? 0 : 1; + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); + result += checkNaN(area); + result += checkNaN(perim); + geod_polygon_compute(&g, &p, 0, 1, &area, &perim); + result += area == 0 ? 0 : 1; + result += perim == 0 ? 0 : 1; + geod_polygon_init(&p, 1); + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); + result += perim == 0 ? 0 : 1; + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + result += checkNaN(perim); + geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + result += perim == 0 ? 0 : 1; + geod_polygon_addpoint(&g, &p, 1, 1); + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + result += checkEquals(perim, 1000, 1e-10); + geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, 0, &perim); + result += checkEquals(perim, 156876.149, 0.5e-3); + return result; +} + +int main() { + int n = 0, i; + if ((i = testinverse())) {++n; printf("testinverse fail: %d\n", i);} + if ((i = testdirect())) {++n; printf("testdirect fail: %d\n", i);} + if ((i = testarcdirect())) {++n; printf("testarcdirect fail: %d\n", i);} + if ((i = GeodSolve0())) {++n; printf("GeodSolve0 fail: %d\n", i);} + if ((i = GeodSolve1())) {++n; printf("GeodSolve1 fail: %d\n", i);} + if ((i = GeodSolve2())) {++n; printf("GeodSolve2 fail: %d\n", i);} + if ((i = GeodSolve4())) {++n; printf("GeodSolve4 fail: %d\n", i);} + if ((i = GeodSolve5())) {++n; printf("GeodSolve5 fail: %d\n", i);} + if ((i = GeodSolve6())) {++n; printf("GeodSolve6 fail: %d\n", i);} + if ((i = GeodSolve9())) {++n; printf("GeodSolve9 fail: %d\n", i);} + if ((i = GeodSolve10())) {++n; printf("GeodSolve10 fail: %d\n", i);} + if ((i = GeodSolve11())) {++n; printf("GeodSolve11 fail: %d\n", i);} + if ((i = GeodSolve12())) {++n; printf("GeodSolve12 fail: %d\n", i);} + if ((i = GeodSolve14())) {++n; printf("GeodSolve14 fail: %d\n", i);} + if ((i = GeodSolve15())) {++n; printf("GeodSolve15 fail: %d\n", i);} + if ((i = GeodSolve17())) {++n; printf("GeodSolve17 fail: %d\n", i);} + if ((i = GeodSolve26())) {++n; printf("GeodSolve26 fail: %d\n", i);} + if ((i = GeodSolve28())) {++n; printf("GeodSolve28 fail: %d\n", i);} + if ((i = GeodSolve33())) {++n; printf("GeodSolve33 fail: %d\n", i);} + if ((i = GeodSolve55())) {++n; printf("GeodSolve55 fail: %d\n", i);} + if ((i = GeodSolve59())) {++n; printf("GeodSolve59 fail: %d\n", i);} + if ((i = GeodSolve61())) {++n; printf("GeodSolve61 fail: %d\n", i);} + if ((i = GeodSolve65())) {++n; printf("GeodSolve65 fail: %d\n", i);} + if ((i = GeodSolve67())) {++n; printf("GeodSolve67 fail: %d\n", i);} + if ((i = GeodSolve71())) {++n; printf("GeodSolve71 fail: %d\n", i);} + if ((i = GeodSolve73())) {++n; printf("GeodSolve73 fail: %d\n", i);} + if ((i = GeodSolve74())) {++n; printf("GeodSolve74 fail: %d\n", i);} + if ((i = GeodSolve76())) {++n; printf("GeodSolve76 fail: %d\n", i);} + if ((i = GeodSolve78())) {++n; printf("GeodSolve78 fail: %d\n", i);} + if ((i = GeodSolve80())) {++n; printf("GeodSolve80 fail: %d\n", i);} + if ((i = Planimeter0())) {++n; printf("Planimeter0 fail: %d\n", i);} + if ((i = Planimeter5())) {++n; printf("Planimeter5 fail: %d\n", i);} + if ((i = Planimeter6())) {++n; printf("Planimeter6 fail: %d\n", i);} + if ((i = Planimeter12())) {++n; printf("Planimeter12 fail: %d\n", i);} + if ((i = Planimeter13())) {++n; printf("Planimeter13 fail: %d\n", i);} + if ((i = Planimeter15())) {++n; printf("Planimeter15 fail: %d\n", i);} + if ((i = Planimeter19())) {++n; printf("Planimeter19 fail: %d\n", i);} + if ((i = Planimeter21())) {++n; printf("Planimeter21 fail: %d\n", i);} + if ((i = AddEdge1())) {++n; printf("AddEdge1 fail: %d\n", i);} + if ((i = EmptyPoly())) {++n; printf("EmptyPoly fail: %d\n", i);} + return n; +} + +/** @endcond */ diff --git a/src/gie.c b/src/gie.c deleted file mode 100644 index 4bb79f1f..00000000 --- a/src/gie.c +++ /dev/null @@ -1,1456 +0,0 @@ -/*********************************************************************** - - 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 = 0; - -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", 0}; - const char *longkeys[] = {"o=output", 0}; - 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 (0==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 (0==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 (0==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 = 0; - return 0; - } - - f = fopen (fname, "rt"); - if (0==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 = 0; - 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 (0); - proj_context_use_proj4_init_rules(0, T.use_proj4_init_rules); - - T.P = proj_create (0, 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; - 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; - 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 (0==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 (0==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; -} - - - - -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}, -}; - -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 = calloc (1, sizeof (ffio)); - if (0==G) - return 0; - - if (0==max_record_size) - max_record_size = 1000; - - G->args = calloc (1, 5*max_record_size); - if (0==G->args) { - free (G); - return 0; - } - - G->next_args = calloc (1, max_record_size); - if (0==G->args) { - free (G->args); - free (G); - return 0; - } - - 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 0; -} - - - -/***************************************************************************************/ -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 (0==G) - return 0; - c = G->next_args; - if (0==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 0; -} - - - -/***************************************************************************************/ -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==0) - 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 (0==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 (0==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) { - void *p = realloc (G->args, 2 * G->args_size); - if (0==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 (0==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/gie.cpp b/src/gie.cpp new file mode 100644 index 00000000..74d78db5 --- /dev/null +++ b/src/gie.cpp @@ -0,0 +1,1456 @@ +/*********************************************************************** + + 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 = 0; + +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", 0}; + const char *longkeys[] = {"o=output", 0}; + 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 (0==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 (0==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 (0==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 = 0; + return 0; + } + + f = fopen (fname, "rt"); + if (0==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 = 0; + 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 (0); + proj_context_use_proj4_init_rules(0, T.use_proj4_init_rules); + + T.P = proj_create (0, 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; + 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; + 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 (0==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 (0==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; +} + + + + +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}, +}; + +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 (0==G) + return 0; + + if (0==max_record_size) + max_record_size = 1000; + + G->args = static_cast(calloc (1, 5*max_record_size)); + if (0==G->args) { + free (G); + return 0; + } + + G->next_args = static_cast(calloc (1, max_record_size)); + if (0==G->args) { + free (G->args); + free (G); + return 0; + } + + 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 0; +} + + + +/***************************************************************************************/ +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 (0==G) + return 0; + c = G->next_args; + if (0==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 0; +} + + + +/***************************************************************************************/ +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==0) + 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 (0==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 (0==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 (0==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 (0==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/jniproj.c b/src/jniproj.c deleted file mode 100644 index 67aa2478..00000000 --- a/src/jniproj.c +++ /dev/null @@ -1,472 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Java/JNI wrappers for PROJ API. - * Author: Antonello Andrea - * Martin Desruisseaux - * - ****************************************************************************** - * Copyright (c) 2005, Andrea Antonello - * Copyright (c) 2011, Martin Desruisseaux - * 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. - *****************************************************************************/ - -/*! - * \file jniproj.c - * - * \brief - * Functions used by the Java Native Interface (JNI) wrappers of PROJ. - * - * - * \author Antonello Andrea - * \date Wed Oct 20 23:10:24 CEST 2004 - * - * \author Martin Desruisseaux - * \date August 2011 - */ - -#include "proj_config.h" - -#ifdef JNI_ENABLED - -#include -#include -#include "projects.h" -#include "org_proj4_PJ.h" -#include - -#define PJ_FIELD_NAME "ptr" -#define PJ_FIELD_TYPE "J" - -/*! - * \brief - * Internal method returning the address of the PJ structure wrapped by the given Java object. - * This function looks for a field named "ptr" and of type "long" (Java signature "J") in the - * given object. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The address of the PJ structure, or NULL if the operation fails (for example - * because the "ptr" field was not found). - */ -PJ *getPJ(JNIEnv *env, jobject object) -{ - jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE); - return (id) ? (PJ*) (*env)->GetLongField(env, object, id) : NULL; -} - -/*! - * \brief - * Internal method returning the java.lang.Double.NaN constant value. - * Efficiency is no a high concern for this particular method, because it - * is used mostly when the user wrongly attempt to use a disposed PJ object. - * - * \param env - The JNI environment. - * \return The java.lang.Double.NaN constant value. - */ -jdouble javaNaN(JNIEnv *env) -{ - jclass c = (*env)->FindClass(env, "java/lang/Double"); - if (c) { // Should never be NULL, but let be paranoiac. - jfieldID id = (*env)->GetStaticFieldID(env, c, "NaN", "D"); - if (id) { // Should never be NULL, but let be paranoiac. - return (*env)->GetStaticDoubleField(env, c, id); - } - } - return 0.0; // Should never happen. -} - -/*! - * \brief - * Returns the Proj4 release number. - * - * \param env - The JNI environment. - * \param class - The class from which this method has been invoked. - * \return The Proj4 release number, or NULL. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getVersion - (JNIEnv *env, jclass class) -{ - const char *desc = pj_get_release(); - return (desc) ? (*env)->NewStringUTF(env, desc) : NULL; -} - -/*! - * \brief - * Allocates a new PJ structure from a definition string. - * - * \param env - The JNI environment. - * \param class - The class from which this method has been invoked. - * \param definition - The string definition to be given to Proj4. - * \return The address of the new PJ structure, or 0 in case of failure. - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocatePJ - (JNIEnv *env, jclass class, jstring definition) -{ - const char *def_utf = (*env)->GetStringUTFChars(env, definition, NULL); - if (!def_utf) return 0; /* OutOfMemoryError already thrown. */ - PJ *pj = pj_init_plus(def_utf); - (*env)->ReleaseStringUTFChars(env, definition, def_utf); - return (jlong) pj; -} - -/*! - * \brief - * Allocates a new geographic PJ structure from an existing one. - * - * \param env - The JNI environment. - * \param class - The class from which this method has been invoked. - * \param projected - The PJ object from which to derive a new one. - * \return The address of the new PJ structure, or 0 in case of failure. - */ -JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocateGeoPJ - (JNIEnv *env, jclass class, jobject projected) -{ - PJ *pj = getPJ(env, projected); - return (pj) ? (jlong) pj_latlong_from_proj(pj) : 0; -} - -/*! - * \brief - * Returns the definition string. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The definition string. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getDefinition - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - char *desc = pj_get_def(pj, 0); - if (desc) { - jstring str = (*env)->NewStringUTF(env, desc); - pj_dalloc(desc); - return str; - } - } - return NULL; -} - -/*! - * \brief - * Returns the description associated to the PJ structure. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The description associated to the PJ structure. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_toString - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - const char *desc = pj->descr; - if (desc) { - return (*env)->NewStringUTF(env, desc); - } - } - return NULL; -} - -/*! - * \brief - * Returns the CRS type as one of the PJ.Type enum: GEOGRAPHIC, GEOCENTRIC or PROJECTED. - * This function should never return NULL, unless class or fields have been renamed in - * such a way that we can not find anymore the expected enum values. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The CRS type as one of the PJ.Type enum. - */ -JNIEXPORT jobject JNICALL Java_org_proj4_PJ_getType - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - const char *type; - if (pj_is_latlong(pj)) { - type = "GEOGRAPHIC"; - } else if (pj_is_geocent(pj)) { - type = "GEOCENTRIC"; - } else { - type = "PROJECTED"; - } - jclass c = (*env)->FindClass(env, "org/proj4/PJ$Type"); - if (c) { - jfieldID id = (*env)->GetStaticFieldID(env, c, type, "Lorg/proj4/PJ$Type;"); - if (id) { - return (*env)->GetStaticObjectField(env, c, id); - } - } - } - return NULL; -} - -/*! - * \brief - * Returns the semi-major axis length. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The semi-major axis length. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMajorAxis - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return pj ? pj->a_orig : javaNaN(env); -} - -/*! - * \brief - * Computes the semi-minor axis length from the semi-major axis length and the eccentricity - * squared. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The semi-minor axis length. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMinorAxis - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (!pj) return javaNaN(env); - double a = pj->a_orig; - return sqrt(a*a * (1.0 - pj->es_orig)); -} - -/*! - * \brief - * Returns the eccentricity squared. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The eccentricity. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getEccentricitySquared - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return pj ? pj->es_orig : javaNaN(env); -} - -/*! - * \brief - * Returns an array of character indicating the direction of each axis. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The axis directions. - */ -JNIEXPORT jcharArray JNICALL Java_org_proj4_PJ_getAxisDirections - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - int length = strlen(pj->axis); - jcharArray array = (*env)->NewCharArray(env, length); - if (array) { - jchar* axis = (*env)->GetCharArrayElements(env, array, NULL); - if (axis) { - /* Don't use memcp because the type may not be the same. */ - int i; - for (i=0; iaxis[i]; - } - (*env)->ReleaseCharArrayElements(env, array, axis, 0); - } - return array; - } - } - return NULL; -} - -/*! - * \brief - * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The prime meridian longitude, in degrees. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getGreenwichLongitude - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - return (pj) ? (pj->from_greenwich)*(180/M_PI) : javaNaN(env); -} - -/*! - * \brief - * Returns the conversion factor from linear units to metres. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \param vertical - JNI_FALSE for horizontal axes, or JNI_TRUE for the vertical axis. - * \return The conversion factor to metres. - */ -JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getLinearUnitToMetre - (JNIEnv *env, jobject object, jboolean vertical) -{ - PJ *pj = getPJ(env, object); - if (pj) { - return (vertical) ? pj->vto_meter : pj->to_meter; - } - return javaNaN(env); -} - -/*! - * \brief - * Converts input values from degrees to radians before coordinate operation, or the output - * values from radians to degrees after the coordinate operation. - * - * \param pj - The PROJ.4 PJ structure. - * \param data - The coordinate array to transform. - * \param numPts - Number of points to transform. - * \param dimension - Dimension of points in the coordinate array. - * \param factor - The scale factor to apply: M_PI/180 for inputs or 180/M_PI for outputs. - */ -void convertAngularOrdinates(PJ *pj, double* data, jint numPts, int dimension, double factor) { - int dimToSkip; - if (pj_is_latlong(pj)) { - /* Convert only the 2 first ordinates and skip all the other dimensions. */ - dimToSkip = dimension - 2; - } else { - /* Not a geographic CRS: nothing to convert. */ - return; - } - double *stop = data + dimension*numPts; - if (dimToSkip > 0) { - while (data != stop) { - (*data++) *= factor; - (*data++) *= factor; - data += dimToSkip; - } - } else { - while (data != stop) { - (*data++) *= factor; - } - } -} - -/*! - * \brief - * Transforms in-place the coordinates in the given array. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \param target - The target CRS. - * \param dimension - The dimension of each coordinate value. Must be equals or greater than 2. - * \param coordinates - The coordinates to transform, as a sequence of (x,y,,...) tuples. - * \param offset - Offset of the first coordinate in the given array. - * \param numPts - Number of points to transform. - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_transform - (JNIEnv *env, jobject object, jobject target, jint dimension, jdoubleArray coordinates, jint offset, jint numPts) -{ - if (!target || !coordinates) { - jclass c = (*env)->FindClass(env, "java/lang/NullPointerException"); - if (c) (*env)->ThrowNew(env, c, "The target CRS and the coordinates array can not be null."); - return; - } - if (dimension < 2 || dimension > org_proj4_PJ_DIMENSION_MAX) { /* Arbitrary upper value for catching potential misuse. */ - jclass c = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); - if (c) (*env)->ThrowNew(env, c, "Illegal number of dimensions."); - return; - } - if ((offset < 0) || (numPts < 0) || (offset + dimension*numPts) > (*env)->GetArrayLength(env, coordinates)) { - jclass c = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException"); - if (c) (*env)->ThrowNew(env, c, "Illegal offset or illegal number of points."); - return; - } - PJ *src_pj = getPJ(env, object); - PJ *dst_pj = getPJ(env, target); - if (src_pj && dst_pj) { - /* Using GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical rather than - GetDoubleArrayElements/ReleaseDoubleArrayElements increase the chances that - the JVM returns direct reference to its internal array without copying data. - However we must promise to run the "critical" code fast, to not make any - system call that may wait for the JVM and to not invoke any other JNI method. */ - double *data = (*env)->GetPrimitiveArrayCritical(env, coordinates, NULL); - if (data) { - double *x = data + offset; - double *y = x + 1; - double *z = (dimension >= 3) ? y+1 : NULL; - convertAngularOrdinates(src_pj, x, numPts, dimension, M_PI/180); - int err = pj_transform(src_pj, dst_pj, numPts, dimension, x, y, z); - convertAngularOrdinates(dst_pj, x, numPts, dimension, 180/M_PI); - (*env)->ReleasePrimitiveArrayCritical(env, coordinates, data, 0); - if (err) { - jclass c = (*env)->FindClass(env, "org/proj4/PJException"); - if (c) (*env)->ThrowNew(env, c, pj_strerrno(err)); - } - } - } -} - -/*! - * \brief - * Returns a description of the last error that occurred, or NULL if none. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - * \return The last error, or NULL. - */ -JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getLastError - (JNIEnv *env, jobject object) -{ - PJ *pj = getPJ(env, object); - if (pj) { - int err = pj_ctx_get_errno(pj->ctx); - if (err) { - return (*env)->NewStringUTF(env, pj_strerrno(err)); - } - } - return NULL; -} - -/*! - * \brief - * Deallocate the PJ structure. This method is invoked by the garbage collector exactly once. - * This method will also set the Java "ptr" final field to 0 as a safety. In theory we are not - * supposed to change the value of a final field. But no Java code should use this field, and - * the PJ object is being garbage collected anyway. We set the field to 0 as a safety in case - * some user invoked the finalize() method explicitly despite our warning in the Javadoc to - * never do such thing. - * - * \param env - The JNI environment. - * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). - */ -JNIEXPORT void JNICALL Java_org_proj4_PJ_finalize - (JNIEnv *env, jobject object) -{ - jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE); - if (id) { - PJ *pj = (PJ*) (*env)->GetLongField(env, object, id); - if (pj) { - (*env)->SetLongField(env, object, id, (jlong) 0); - pj_free(pj); - } - } -} - -#endif diff --git a/src/jniproj.cpp b/src/jniproj.cpp new file mode 100644 index 00000000..67aa2478 --- /dev/null +++ b/src/jniproj.cpp @@ -0,0 +1,472 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Java/JNI wrappers for PROJ API. + * Author: Antonello Andrea + * Martin Desruisseaux + * + ****************************************************************************** + * Copyright (c) 2005, Andrea Antonello + * Copyright (c) 2011, Martin Desruisseaux + * 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. + *****************************************************************************/ + +/*! + * \file jniproj.c + * + * \brief + * Functions used by the Java Native Interface (JNI) wrappers of PROJ. + * + * + * \author Antonello Andrea + * \date Wed Oct 20 23:10:24 CEST 2004 + * + * \author Martin Desruisseaux + * \date August 2011 + */ + +#include "proj_config.h" + +#ifdef JNI_ENABLED + +#include +#include +#include "projects.h" +#include "org_proj4_PJ.h" +#include + +#define PJ_FIELD_NAME "ptr" +#define PJ_FIELD_TYPE "J" + +/*! + * \brief + * Internal method returning the address of the PJ structure wrapped by the given Java object. + * This function looks for a field named "ptr" and of type "long" (Java signature "J") in the + * given object. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The address of the PJ structure, or NULL if the operation fails (for example + * because the "ptr" field was not found). + */ +PJ *getPJ(JNIEnv *env, jobject object) +{ + jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE); + return (id) ? (PJ*) (*env)->GetLongField(env, object, id) : NULL; +} + +/*! + * \brief + * Internal method returning the java.lang.Double.NaN constant value. + * Efficiency is no a high concern for this particular method, because it + * is used mostly when the user wrongly attempt to use a disposed PJ object. + * + * \param env - The JNI environment. + * \return The java.lang.Double.NaN constant value. + */ +jdouble javaNaN(JNIEnv *env) +{ + jclass c = (*env)->FindClass(env, "java/lang/Double"); + if (c) { // Should never be NULL, but let be paranoiac. + jfieldID id = (*env)->GetStaticFieldID(env, c, "NaN", "D"); + if (id) { // Should never be NULL, but let be paranoiac. + return (*env)->GetStaticDoubleField(env, c, id); + } + } + return 0.0; // Should never happen. +} + +/*! + * \brief + * Returns the Proj4 release number. + * + * \param env - The JNI environment. + * \param class - The class from which this method has been invoked. + * \return The Proj4 release number, or NULL. + */ +JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getVersion + (JNIEnv *env, jclass class) +{ + const char *desc = pj_get_release(); + return (desc) ? (*env)->NewStringUTF(env, desc) : NULL; +} + +/*! + * \brief + * Allocates a new PJ structure from a definition string. + * + * \param env - The JNI environment. + * \param class - The class from which this method has been invoked. + * \param definition - The string definition to be given to Proj4. + * \return The address of the new PJ structure, or 0 in case of failure. + */ +JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocatePJ + (JNIEnv *env, jclass class, jstring definition) +{ + const char *def_utf = (*env)->GetStringUTFChars(env, definition, NULL); + if (!def_utf) return 0; /* OutOfMemoryError already thrown. */ + PJ *pj = pj_init_plus(def_utf); + (*env)->ReleaseStringUTFChars(env, definition, def_utf); + return (jlong) pj; +} + +/*! + * \brief + * Allocates a new geographic PJ structure from an existing one. + * + * \param env - The JNI environment. + * \param class - The class from which this method has been invoked. + * \param projected - The PJ object from which to derive a new one. + * \return The address of the new PJ structure, or 0 in case of failure. + */ +JNIEXPORT jlong JNICALL Java_org_proj4_PJ_allocateGeoPJ + (JNIEnv *env, jclass class, jobject projected) +{ + PJ *pj = getPJ(env, projected); + return (pj) ? (jlong) pj_latlong_from_proj(pj) : 0; +} + +/*! + * \brief + * Returns the definition string. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The definition string. + */ +JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getDefinition + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (pj) { + char *desc = pj_get_def(pj, 0); + if (desc) { + jstring str = (*env)->NewStringUTF(env, desc); + pj_dalloc(desc); + return str; + } + } + return NULL; +} + +/*! + * \brief + * Returns the description associated to the PJ structure. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The description associated to the PJ structure. + */ +JNIEXPORT jstring JNICALL Java_org_proj4_PJ_toString + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (pj) { + const char *desc = pj->descr; + if (desc) { + return (*env)->NewStringUTF(env, desc); + } + } + return NULL; +} + +/*! + * \brief + * Returns the CRS type as one of the PJ.Type enum: GEOGRAPHIC, GEOCENTRIC or PROJECTED. + * This function should never return NULL, unless class or fields have been renamed in + * such a way that we can not find anymore the expected enum values. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The CRS type as one of the PJ.Type enum. + */ +JNIEXPORT jobject JNICALL Java_org_proj4_PJ_getType + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (pj) { + const char *type; + if (pj_is_latlong(pj)) { + type = "GEOGRAPHIC"; + } else if (pj_is_geocent(pj)) { + type = "GEOCENTRIC"; + } else { + type = "PROJECTED"; + } + jclass c = (*env)->FindClass(env, "org/proj4/PJ$Type"); + if (c) { + jfieldID id = (*env)->GetStaticFieldID(env, c, type, "Lorg/proj4/PJ$Type;"); + if (id) { + return (*env)->GetStaticObjectField(env, c, id); + } + } + } + return NULL; +} + +/*! + * \brief + * Returns the semi-major axis length. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The semi-major axis length. + */ +JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMajorAxis + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + return pj ? pj->a_orig : javaNaN(env); +} + +/*! + * \brief + * Computes the semi-minor axis length from the semi-major axis length and the eccentricity + * squared. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The semi-minor axis length. + */ +JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getSemiMinorAxis + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (!pj) return javaNaN(env); + double a = pj->a_orig; + return sqrt(a*a * (1.0 - pj->es_orig)); +} + +/*! + * \brief + * Returns the eccentricity squared. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The eccentricity. + */ +JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getEccentricitySquared + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + return pj ? pj->es_orig : javaNaN(env); +} + +/*! + * \brief + * Returns an array of character indicating the direction of each axis. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The axis directions. + */ +JNIEXPORT jcharArray JNICALL Java_org_proj4_PJ_getAxisDirections + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (pj) { + int length = strlen(pj->axis); + jcharArray array = (*env)->NewCharArray(env, length); + if (array) { + jchar* axis = (*env)->GetCharArrayElements(env, array, NULL); + if (axis) { + /* Don't use memcp because the type may not be the same. */ + int i; + for (i=0; iaxis[i]; + } + (*env)->ReleaseCharArrayElements(env, array, axis, 0); + } + return array; + } + } + return NULL; +} + +/*! + * \brief + * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The prime meridian longitude, in degrees. + */ +JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getGreenwichLongitude + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + return (pj) ? (pj->from_greenwich)*(180/M_PI) : javaNaN(env); +} + +/*! + * \brief + * Returns the conversion factor from linear units to metres. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \param vertical - JNI_FALSE for horizontal axes, or JNI_TRUE for the vertical axis. + * \return The conversion factor to metres. + */ +JNIEXPORT jdouble JNICALL Java_org_proj4_PJ_getLinearUnitToMetre + (JNIEnv *env, jobject object, jboolean vertical) +{ + PJ *pj = getPJ(env, object); + if (pj) { + return (vertical) ? pj->vto_meter : pj->to_meter; + } + return javaNaN(env); +} + +/*! + * \brief + * Converts input values from degrees to radians before coordinate operation, or the output + * values from radians to degrees after the coordinate operation. + * + * \param pj - The PROJ.4 PJ structure. + * \param data - The coordinate array to transform. + * \param numPts - Number of points to transform. + * \param dimension - Dimension of points in the coordinate array. + * \param factor - The scale factor to apply: M_PI/180 for inputs or 180/M_PI for outputs. + */ +void convertAngularOrdinates(PJ *pj, double* data, jint numPts, int dimension, double factor) { + int dimToSkip; + if (pj_is_latlong(pj)) { + /* Convert only the 2 first ordinates and skip all the other dimensions. */ + dimToSkip = dimension - 2; + } else { + /* Not a geographic CRS: nothing to convert. */ + return; + } + double *stop = data + dimension*numPts; + if (dimToSkip > 0) { + while (data != stop) { + (*data++) *= factor; + (*data++) *= factor; + data += dimToSkip; + } + } else { + while (data != stop) { + (*data++) *= factor; + } + } +} + +/*! + * \brief + * Transforms in-place the coordinates in the given array. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \param target - The target CRS. + * \param dimension - The dimension of each coordinate value. Must be equals or greater than 2. + * \param coordinates - The coordinates to transform, as a sequence of (x,y,,...) tuples. + * \param offset - Offset of the first coordinate in the given array. + * \param numPts - Number of points to transform. + */ +JNIEXPORT void JNICALL Java_org_proj4_PJ_transform + (JNIEnv *env, jobject object, jobject target, jint dimension, jdoubleArray coordinates, jint offset, jint numPts) +{ + if (!target || !coordinates) { + jclass c = (*env)->FindClass(env, "java/lang/NullPointerException"); + if (c) (*env)->ThrowNew(env, c, "The target CRS and the coordinates array can not be null."); + return; + } + if (dimension < 2 || dimension > org_proj4_PJ_DIMENSION_MAX) { /* Arbitrary upper value for catching potential misuse. */ + jclass c = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); + if (c) (*env)->ThrowNew(env, c, "Illegal number of dimensions."); + return; + } + if ((offset < 0) || (numPts < 0) || (offset + dimension*numPts) > (*env)->GetArrayLength(env, coordinates)) { + jclass c = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException"); + if (c) (*env)->ThrowNew(env, c, "Illegal offset or illegal number of points."); + return; + } + PJ *src_pj = getPJ(env, object); + PJ *dst_pj = getPJ(env, target); + if (src_pj && dst_pj) { + /* Using GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical rather than + GetDoubleArrayElements/ReleaseDoubleArrayElements increase the chances that + the JVM returns direct reference to its internal array without copying data. + However we must promise to run the "critical" code fast, to not make any + system call that may wait for the JVM and to not invoke any other JNI method. */ + double *data = (*env)->GetPrimitiveArrayCritical(env, coordinates, NULL); + if (data) { + double *x = data + offset; + double *y = x + 1; + double *z = (dimension >= 3) ? y+1 : NULL; + convertAngularOrdinates(src_pj, x, numPts, dimension, M_PI/180); + int err = pj_transform(src_pj, dst_pj, numPts, dimension, x, y, z); + convertAngularOrdinates(dst_pj, x, numPts, dimension, 180/M_PI); + (*env)->ReleasePrimitiveArrayCritical(env, coordinates, data, 0); + if (err) { + jclass c = (*env)->FindClass(env, "org/proj4/PJException"); + if (c) (*env)->ThrowNew(env, c, pj_strerrno(err)); + } + } + } +} + +/*! + * \brief + * Returns a description of the last error that occurred, or NULL if none. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + * \return The last error, or NULL. + */ +JNIEXPORT jstring JNICALL Java_org_proj4_PJ_getLastError + (JNIEnv *env, jobject object) +{ + PJ *pj = getPJ(env, object); + if (pj) { + int err = pj_ctx_get_errno(pj->ctx); + if (err) { + return (*env)->NewStringUTF(env, pj_strerrno(err)); + } + } + return NULL; +} + +/*! + * \brief + * Deallocate the PJ structure. This method is invoked by the garbage collector exactly once. + * This method will also set the Java "ptr" final field to 0 as a safety. In theory we are not + * supposed to change the value of a final field. But no Java code should use this field, and + * the PJ object is being garbage collected anyway. We set the field to 0 as a safety in case + * some user invoked the finalize() method explicitly despite our warning in the Javadoc to + * never do such thing. + * + * \param env - The JNI environment. + * \param object - The Java object wrapping the PJ structure (not allowed to be NULL). + */ +JNIEXPORT void JNICALL Java_org_proj4_PJ_finalize + (JNIEnv *env, jobject object) +{ + jfieldID id = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, object), PJ_FIELD_NAME, PJ_FIELD_TYPE); + if (id) { + PJ *pj = (PJ*) (*env)->GetLongField(env, object, id); + if (pj) { + (*env)->SetLongField(env, object, id, (jlong) 0); + pj_free(pj); + } + } +} + +#endif diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 4f29f4d5..7be6302b 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -55,195 +55,195 @@ endif() ### library source list and include_list ### ############################################## SET(SRC_LIBPROJ_PJ - nad_init.c - PJ_aea.c - PJ_aeqd.c - PJ_affine.c - PJ_airy.c - PJ_aitoff.c - PJ_august.c - PJ_axisswap.c - PJ_bacon.c - PJ_bertin1953.c - PJ_bipc.c - PJ_boggs.c - PJ_bonne.c - PJ_calcofi.c - PJ_cart.c - PJ_cass.c - PJ_cc.c - PJ_ccon.c - PJ_cea.c - PJ_chamb.c - PJ_collg.c - PJ_comill.c - PJ_crast.c - PJ_deformation.c - PJ_denoy.c - PJ_eck1.c - PJ_eck2.c - PJ_eck3.c - PJ_eck4.c - PJ_eck5.c - PJ_eqc.c - PJ_eqdc.c - PJ_eqearth.c - PJ_fahey.c - PJ_fouc_s.c - PJ_gall.c - PJ_geoc.c - PJ_geos.c - PJ_gins8.c - PJ_gnom.c - PJ_gn_sinu.c - PJ_goode.c - PJ_gstmerc.c - PJ_hammer.c - PJ_hatano.c - PJ_helmert.c - PJ_hgridshift.c - PJ_horner.c - PJ_igh.c - PJ_isea.c - PJ_imw_p.c - PJ_krovak.c - PJ_labrd.c - PJ_laea.c - PJ_lagrng.c - PJ_larr.c - PJ_lask.c - PJ_latlong.c - PJ_lcca.c - PJ_lcc.c - PJ_loxim.c - PJ_lsat.c - PJ_misrsom.c - PJ_mbt_fps.c - PJ_mbtfpp.c - PJ_mbtfpq.c - PJ_merc.c - PJ_mill.c - PJ_mod_ster.c - PJ_moll.c - PJ_molodensky.c - PJ_natearth.c - PJ_natearth2.c - PJ_nell.c - PJ_nell_h.c - PJ_nocol.c - PJ_nsper.c - PJ_nzmg.c - PJ_ob_tran.c - PJ_ocea.c - PJ_oea.c - PJ_omerc.c - PJ_ortho.c - PJ_patterson.c - PJ_pipeline.c - PJ_poly.c - PJ_putp2.c - PJ_putp3.c - PJ_putp4p.c - PJ_putp5.c - PJ_putp6.c - PJ_qsc.c - PJ_robin.c - PJ_rpoly.c - PJ_sch.c - PJ_sconics.c - PJ_somerc.c - PJ_sterea.c - PJ_stere.c - PJ_sts.c - PJ_tcc.c - PJ_tcea.c - PJ_times.c - PJ_tmerc.c - PJ_tobmerc.c - PJ_tpeqd.c - PJ_unitconvert.c - PJ_urm5.c - PJ_urmfps.c - PJ_vandg.c - PJ_vandg2.c - PJ_vandg4.c - PJ_vgridshift.c - PJ_wag2.c - PJ_wag3.c - PJ_wag7.c - PJ_wink1.c - PJ_wink2.c - proj_etmerc.c + 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_CORE - aasincos.c - adjlon.c - bch2bps.c - bchgen.c - biveval.c - dmstor.c - emess.c + aasincos.cpp + adjlon.cpp + bch2bps.cpp + bchgen.cpp + biveval.cpp + dmstor.cpp + emess.cpp emess.h - geocent.c + geocent.cpp geocent.h - geodesic.c - mk_cheby.c - nad_cvt.c - nad_init.c - nad_intr.c - pj_apply_gridshift.c - pj_apply_vgridshift.c - pj_auth.c - pj_ctx.c - pj_fileapi.c - pj_datum_set.c - pj_datums.c - pj_deriv.c - pj_ell_set.c - pj_ellps.c - pj_errno.c - pj_factors.c - pj_fwd.c - pj_gauss.c - pj_gc_reader.c - pj_geocent.c - pj_gridcatalog.c - pj_gridinfo.c - pj_gridlist.c - PJ_healpix.c - pj_init.c - pj_initcache.c - pj_inv.c - pj_list.c + 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_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.c - pj_malloc.c - pj_math.c - pj_mlfn.c - pj_msfn.c - pj_mutex.c - proj_4D_api.c - pj_internal.c + pj_log.cpp + pj_malloc.cpp + pj_math.cpp + pj_mlfn.cpp + pj_msfn.cpp + pj_mutex.cpp + proj_4D_api.cpp + pj_internal.cpp proj_internal.h - pj_open_lib.c - pj_param.c - pj_phi2.c - pj_pr_list.c - pj_qsfn.c - pj_release.c - pj_strerrno.c - pj_transform.c - pj_tsfn.c - pj_units.c - pj_utils.c - pj_zpoly1.c - proj_mdist.c + 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.c - rtodms.c - vector1.c - pj_strtod.c + proj_rouss.cpp + rtodms.cpp + vector1.cpp + pj_strtod.cpp pj_wkt1_generated_parser.c pj_wkt2_generated_parser.c ${CMAKE_CURRENT_BINARY_DIR}/proj_config.h @@ -301,7 +301,7 @@ endif(JNI_SUPPORT AND NOT JNI_FOUND) boost_report_value(JNI_SUPPORT) if(JNI_SUPPORT) set(SRC_LIBPROJ_CORE ${SRC_LIBPROJ_CORE} - jniproj.c ) + jniproj.cpp ) set(HEADERS_LIBPROJ ${HEADERS_LIBPROJ} org_proj4_PJ.h) source_group("Source Files\\JNI" FILES ${SRC_LIBPROJ_JNI}) diff --git a/src/mk_cheby.c b/src/mk_cheby.c deleted file mode 100644 index a2f90bef..00000000 --- a/src/mk_cheby.c +++ /dev/null @@ -1,192 +0,0 @@ -#include "projects.h" -static void /* sum coefficients less than res */ -eval(projUV **w, int nu, int nv, double res, projUV *resid) { - int i, j; - double ab; - projUV *s; - - resid->u = resid->v = 0.; - for (i = 0; i < nu; ++i) { - s = w[i]; - for (j = 0; j < nv; ++j) { - if ((ab = fabs(s->u)) < res) - resid->u += ab; - if ((ab = fabs(s->v)) < res) - resid->v += ab; - ++s; - } - } -} -static Tseries * /* create power series structure */ -makeT(int nru, int nrv) { - Tseries *T; - int i; - - if (!(T = (Tseries *)pj_malloc(sizeof(Tseries)))) - return 0; - if (!(T->cu = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nru))) { - pj_dalloc(T); - return 0; - } - if (!(T->cv = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nrv))) { - pj_dalloc(T->cu); - pj_dalloc(T); - return 0; - } - - for (i = 0; i < nru; ++i) - T->cu[i].c = 0; - for (i = 0; i < nrv; ++i) - T->cv[i].c = 0; - return T; -} -Tseries * -mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), - int nu, int nv, int power) { - int j, i, nru, nrv, *ncu, *ncv; - Tseries *T = NULL; - projUV **w; - double cutres; - - if (!(w = (projUV **)vector2(nu, nv, sizeof(projUV)))) - return 0; - if (!(ncu = (int *)vector1(nu + nv, sizeof(int)))) { - freev2((void **)w, nu); - return 0; - } - ncv = ncu + nu; - if (!bchgen(a, b, nu, nv, w, func)) { - projUV *s; - double ab, *p; - - /* analyse coefficients and adjust until residual OK */ - cutres = res; - for (i = 4; i ; --i) { - eval(w, nu, nv, cutres, resid); - if (resid->u < res && resid->v < res) - break; - cutres *= 0.5; - } - if (i <= 0) /* warn of too many tries */ - resid->u = - resid->u; - /* apply cut resolution and set pointers */ - nru = nrv = 0; - for (j = 0; j < nu; ++j) { - ncu[j] = ncv[j] = 0; /* clear column maxes */ - s = w[j]; - for (i = 0; i < nv; ++i) { - if ((ab = fabs(s->u)) < cutres) /* < resolution ? */ - s->u = 0.; /* clear coefficient */ - else - ncu[j] = i + 1; /* update column max */ - if ((ab = fabs(s->v)) < cutres) /* same for v coef's */ - s->v = 0.; - else - ncv[j] = i + 1; - ++s; - } - if (ncu[j]) nru = j + 1; /* update row max */ - if (ncv[j]) nrv = j + 1; - } - if (power) { /* convert to bivariate power series */ - if (!bch2bps(a, b, w, nu, nv)) - goto error; - /* possible change in some row counts, so readjust */ - nru = nrv = 0; - for (j = 0; j < nu; ++j) { - ncu[j] = ncv[j] = 0; /* clear column maxes */ - s = w[j]; - for (i = 0; i < nv; ++i) { - if (s->u != 0.0) - ncu[j] = i + 1; /* update column max */ - if (s->v != 0.0) - ncv[j] = i + 1; - ++s; - } - if (ncu[j]) nru = j + 1; /* update row max */ - if (ncv[j]) nrv = j + 1; - } - if ((T = makeT(nru, nrv)) != NULL ) { - T->a = a; - T->b = b; - T->mu = nru - 1; - T->mv = nrv - 1; - T->power = 1; - for (i = 0; i < nru; ++i) /* store coefficient rows for u */ - { - if ((T->cu[i].m = ncu[i]) != 0) - { - if ((p = T->cu[i].c = - (double *)pj_malloc(sizeof(double) * ncu[i]))) - for (j = 0; j < ncu[i]; ++j) - *p++ = (w[i] + j)->u; - else - goto error; - } - } - for (i = 0; i < nrv; ++i) /* same for v */ - { - if ((T->cv[i].m = ncv[i]) != 0) - { - if ((p = T->cv[i].c = - (double *)pj_malloc(sizeof(double) * ncv[i]))) - for (j = 0; j < ncv[i]; ++j) - *p++ = (w[i] + j)->v; - else - goto error; - } - } - } - } else if ((T = makeT(nru, nrv)) != NULL) { - /* else make returned Chebyshev coefficient structure */ - T->mu = nru - 1; /* save row degree */ - T->mv = nrv - 1; - T->a.u = a.u + b.u; /* set argument scaling */ - T->a.v = a.v + b.v; - T->b.u = 1. / (b.u - a.u); - T->b.v = 1. / (b.v - a.v); - T->power = 0; - for (i = 0; i < nru; ++i) /* store coefficient rows for u */ - { - if ((T->cu[i].m = ncu[i]) != 0) - { - if ((p = T->cu[i].c = - (double *)pj_malloc(sizeof(double) * ncu[i]))) - for (j = 0; j < ncu[i]; ++j) - *p++ = (w[i] + j)->u; - else - goto error; - } - } - for (i = 0; i < nrv; ++i) /* same for v */ - { - if ((T->cv[i].m = ncv[i]) != 0) - { - if ((p = T->cv[i].c = - (double *)pj_malloc(sizeof(double) * ncv[i]))) - for (j = 0; j < ncv[i]; ++j) - *p++ = (w[i] + j)->v; - else - goto error; - } - } - } else - goto error; - } - goto gohome; - error: - if (T) { /* pj_dalloc up possible allocations */ - for (i = 0; i <= T->mu; ++i) - if (T->cu[i].c) - pj_dalloc(T->cu[i].c); - for (i = 0; i <= T->mv; ++i) - if (T->cv[i].c) - pj_dalloc(T->cv[i].c); - pj_dalloc(T); - } - T = 0; - gohome: - freev2((void **) w, nu); - pj_dalloc(ncu); - return T; -} diff --git a/src/mk_cheby.cpp b/src/mk_cheby.cpp new file mode 100644 index 00000000..a2f90bef --- /dev/null +++ b/src/mk_cheby.cpp @@ -0,0 +1,192 @@ +#include "projects.h" +static void /* sum coefficients less than res */ +eval(projUV **w, int nu, int nv, double res, projUV *resid) { + int i, j; + double ab; + projUV *s; + + resid->u = resid->v = 0.; + for (i = 0; i < nu; ++i) { + s = w[i]; + for (j = 0; j < nv; ++j) { + if ((ab = fabs(s->u)) < res) + resid->u += ab; + if ((ab = fabs(s->v)) < res) + resid->v += ab; + ++s; + } + } +} +static Tseries * /* create power series structure */ +makeT(int nru, int nrv) { + Tseries *T; + int i; + + if (!(T = (Tseries *)pj_malloc(sizeof(Tseries)))) + return 0; + if (!(T->cu = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nru))) { + pj_dalloc(T); + return 0; + } + if (!(T->cv = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nrv))) { + pj_dalloc(T->cu); + pj_dalloc(T); + return 0; + } + + for (i = 0; i < nru; ++i) + T->cu[i].c = 0; + for (i = 0; i < nrv; ++i) + T->cv[i].c = 0; + return T; +} +Tseries * +mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), + int nu, int nv, int power) { + int j, i, nru, nrv, *ncu, *ncv; + Tseries *T = NULL; + projUV **w; + double cutres; + + if (!(w = (projUV **)vector2(nu, nv, sizeof(projUV)))) + return 0; + if (!(ncu = (int *)vector1(nu + nv, sizeof(int)))) { + freev2((void **)w, nu); + return 0; + } + ncv = ncu + nu; + if (!bchgen(a, b, nu, nv, w, func)) { + projUV *s; + double ab, *p; + + /* analyse coefficients and adjust until residual OK */ + cutres = res; + for (i = 4; i ; --i) { + eval(w, nu, nv, cutres, resid); + if (resid->u < res && resid->v < res) + break; + cutres *= 0.5; + } + if (i <= 0) /* warn of too many tries */ + resid->u = - resid->u; + /* apply cut resolution and set pointers */ + nru = nrv = 0; + for (j = 0; j < nu; ++j) { + ncu[j] = ncv[j] = 0; /* clear column maxes */ + s = w[j]; + for (i = 0; i < nv; ++i) { + if ((ab = fabs(s->u)) < cutres) /* < resolution ? */ + s->u = 0.; /* clear coefficient */ + else + ncu[j] = i + 1; /* update column max */ + if ((ab = fabs(s->v)) < cutres) /* same for v coef's */ + s->v = 0.; + else + ncv[j] = i + 1; + ++s; + } + if (ncu[j]) nru = j + 1; /* update row max */ + if (ncv[j]) nrv = j + 1; + } + if (power) { /* convert to bivariate power series */ + if (!bch2bps(a, b, w, nu, nv)) + goto error; + /* possible change in some row counts, so readjust */ + nru = nrv = 0; + for (j = 0; j < nu; ++j) { + ncu[j] = ncv[j] = 0; /* clear column maxes */ + s = w[j]; + for (i = 0; i < nv; ++i) { + if (s->u != 0.0) + ncu[j] = i + 1; /* update column max */ + if (s->v != 0.0) + ncv[j] = i + 1; + ++s; + } + if (ncu[j]) nru = j + 1; /* update row max */ + if (ncv[j]) nrv = j + 1; + } + if ((T = makeT(nru, nrv)) != NULL ) { + T->a = a; + T->b = b; + T->mu = nru - 1; + T->mv = nrv - 1; + T->power = 1; + for (i = 0; i < nru; ++i) /* store coefficient rows for u */ + { + if ((T->cu[i].m = ncu[i]) != 0) + { + if ((p = T->cu[i].c = + (double *)pj_malloc(sizeof(double) * ncu[i]))) + for (j = 0; j < ncu[i]; ++j) + *p++ = (w[i] + j)->u; + else + goto error; + } + } + for (i = 0; i < nrv; ++i) /* same for v */ + { + if ((T->cv[i].m = ncv[i]) != 0) + { + if ((p = T->cv[i].c = + (double *)pj_malloc(sizeof(double) * ncv[i]))) + for (j = 0; j < ncv[i]; ++j) + *p++ = (w[i] + j)->v; + else + goto error; + } + } + } + } else if ((T = makeT(nru, nrv)) != NULL) { + /* else make returned Chebyshev coefficient structure */ + T->mu = nru - 1; /* save row degree */ + T->mv = nrv - 1; + T->a.u = a.u + b.u; /* set argument scaling */ + T->a.v = a.v + b.v; + T->b.u = 1. / (b.u - a.u); + T->b.v = 1. / (b.v - a.v); + T->power = 0; + for (i = 0; i < nru; ++i) /* store coefficient rows for u */ + { + if ((T->cu[i].m = ncu[i]) != 0) + { + if ((p = T->cu[i].c = + (double *)pj_malloc(sizeof(double) * ncu[i]))) + for (j = 0; j < ncu[i]; ++j) + *p++ = (w[i] + j)->u; + else + goto error; + } + } + for (i = 0; i < nrv; ++i) /* same for v */ + { + if ((T->cv[i].m = ncv[i]) != 0) + { + if ((p = T->cv[i].c = + (double *)pj_malloc(sizeof(double) * ncv[i]))) + for (j = 0; j < ncv[i]; ++j) + *p++ = (w[i] + j)->v; + else + goto error; + } + } + } else + goto error; + } + goto gohome; + error: + if (T) { /* pj_dalloc up possible allocations */ + for (i = 0; i <= T->mu; ++i) + if (T->cu[i].c) + pj_dalloc(T->cu[i].c); + for (i = 0; i <= T->mv; ++i) + if (T->cv[i].c) + pj_dalloc(T->cv[i].c); + pj_dalloc(T); + } + T = 0; + gohome: + freev2((void **) w, nu); + pj_dalloc(ncu); + return T; +} diff --git a/src/multistresstest.c b/src/multistresstest.c deleted file mode 100644 index b0bd5c9c..00000000 --- a/src/multistresstest.c +++ /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] == NULL || dst_pj_list[i] == NULL); - - 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 NULL; -} -#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 == NULL ) - { - printf( "Unable to translate:\n%s\n", test->src_def ); - test->skip = 1; - pj_free (dst_pj); - continue; - } - - if( dst_pj == NULL ) - { - 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, NULL ); - } - - 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 +#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] == NULL || dst_pj_list[i] == NULL); + + 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 NULL; +} +#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 == NULL ) + { + printf( "Unable to translate:\n%s\n", test->src_def ); + test->skip = 1; + pj_free (dst_pj); + continue; + } + + if( dst_pj == NULL ) + { + 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, NULL ); + } + + 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 = NULL; - - 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 == NULL ) - { - output_file = argv[i]; - } - else - Usage(); - } - - if( output_file == NULL ) - Usage(); - - fprintf( stdout, "Output Binary File Format: %s\n", format ); - -/* ==================================================================== */ -/* Read the ASCII Table */ -/* ==================================================================== */ - - memset(ct.id,0,MAX_TAB_ID); - if ( NULL == 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/nad2bin.cpp b/src/nad2bin.cpp new file mode 100644 index 00000000..eb8672a5 --- /dev/null +++ b/src/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 = NULL; + + 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 == NULL ) + { + output_file = argv[i]; + } + else + Usage(); + } + + if( output_file == NULL ) + Usage(); + + fprintf( stdout, "Output Binary File Format: %s\n", format ); + +/* ==================================================================== */ +/* Read the ASCII Table */ +/* ==================================================================== */ + + memset(ct.id,0,MAX_TAB_ID); + if ( NULL == 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/nad_cvt.c b/src/nad_cvt.c deleted file mode 100644 index ec4a2b47..00000000 --- a/src/nad_cvt.c +++ /dev/null @@ -1,76 +0,0 @@ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" -#include "proj_math.h" - -#define MAX_ITERATIONS 10 -#define TOL 1e-12 - -LP nad_cvt(LP in, int inverse, struct CTABLE *ct) { - LP t, tb,del, dif; - int i = MAX_ITERATIONS; - const double toltol = TOL*TOL; - - if (in.lam == HUGE_VAL) - return in; - - /* normalize input to ll origin */ - tb = in; - tb.lam -= ct->ll.lam; - tb.phi -= ct->ll.phi; - tb.lam = adjlon (tb.lam - M_PI) + M_PI; - - t = nad_intr (tb, ct); - if (t.lam == HUGE_VAL) - return t; - - if (!inverse) { - in.lam -= t.lam; - in.phi += t.phi; - return in; - } - - t.lam = tb.lam + t.lam; - t.phi = tb.phi - t.phi; - - do { - del = nad_intr(t, ct); - - /* This case used to return failure, but I have - changed it to return the first order approximation - of the inverse shift. This avoids cases where the - grid shift *into* this grid came from another grid. - While we aren't returning optimally correct results - I feel a close result in this case is better than - no result. NFW - To demonstrate use -112.5839956 49.4914451 against - the NTv2 grid shift file from Canada. */ - if (del.lam == HUGE_VAL) - break; - - dif.lam = t.lam - del.lam - tb.lam; - dif.phi = t.phi + del.phi - tb.phi; - t.lam -= dif.lam; - t.phi -= dif.phi; - - } while (--i && (dif.lam*dif.lam + dif.phi*dif.phi > toltol)); /* prob. slightly faster than hypot() */ - - if (i==0) { - /* If we had access to a context, this should go through pj_log, and we should set ctx->errno */ - if (getenv ("PROJ_DEBUG")) - fprintf( stderr, "Inverse grid shift iterator failed to converge.\n" ); - t.lam = t.phi = HUGE_VAL; - return t; - } - - /* and again: pj_log and ctx->errno */ - if (del.lam==HUGE_VAL && getenv ("PROJ_DEBUG")) - fprintf (stderr, "Inverse grid shift iteration failed, presumably at grid edge.\nUsing first approximation.\n"); - - in.lam = adjlon (t.lam + ct->ll.lam); - in.phi = t.phi + ct->ll.phi; - return in; -} diff --git a/src/nad_cvt.cpp b/src/nad_cvt.cpp new file mode 100644 index 00000000..ec4a2b47 --- /dev/null +++ b/src/nad_cvt.cpp @@ -0,0 +1,76 @@ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" +#include "proj_math.h" + +#define MAX_ITERATIONS 10 +#define TOL 1e-12 + +LP nad_cvt(LP in, int inverse, struct CTABLE *ct) { + LP t, tb,del, dif; + int i = MAX_ITERATIONS; + const double toltol = TOL*TOL; + + if (in.lam == HUGE_VAL) + return in; + + /* normalize input to ll origin */ + tb = in; + tb.lam -= ct->ll.lam; + tb.phi -= ct->ll.phi; + tb.lam = adjlon (tb.lam - M_PI) + M_PI; + + t = nad_intr (tb, ct); + if (t.lam == HUGE_VAL) + return t; + + if (!inverse) { + in.lam -= t.lam; + in.phi += t.phi; + return in; + } + + t.lam = tb.lam + t.lam; + t.phi = tb.phi - t.phi; + + do { + del = nad_intr(t, ct); + + /* This case used to return failure, but I have + changed it to return the first order approximation + of the inverse shift. This avoids cases where the + grid shift *into* this grid came from another grid. + While we aren't returning optimally correct results + I feel a close result in this case is better than + no result. NFW + To demonstrate use -112.5839956 49.4914451 against + the NTv2 grid shift file from Canada. */ + if (del.lam == HUGE_VAL) + break; + + dif.lam = t.lam - del.lam - tb.lam; + dif.phi = t.phi + del.phi - tb.phi; + t.lam -= dif.lam; + t.phi -= dif.phi; + + } while (--i && (dif.lam*dif.lam + dif.phi*dif.phi > toltol)); /* prob. slightly faster than hypot() */ + + if (i==0) { + /* If we had access to a context, this should go through pj_log, and we should set ctx->errno */ + if (getenv ("PROJ_DEBUG")) + fprintf( stderr, "Inverse grid shift iterator failed to converge.\n" ); + t.lam = t.phi = HUGE_VAL; + return t; + } + + /* and again: pj_log and ctx->errno */ + if (del.lam==HUGE_VAL && getenv ("PROJ_DEBUG")) + fprintf (stderr, "Inverse grid shift iteration failed, presumably at grid edge.\nUsing first approximation.\n"); + + in.lam = adjlon (t.lam + ct->ll.lam); + in.phi = t.phi + ct->ll.phi; + return in; +} diff --git a/src/nad_init.c b/src/nad_init.c deleted file mode 100644 index 8b024ac7..00000000 --- a/src/nad_init.c +++ /dev/null @@ -1,305 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Load datum shift files into memory. - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include -#include -#include - -#include "projects.h" - -/************************************************************************/ -/* 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; - } -} - -/************************************************************************/ -/* nad_ctable_load() */ -/* */ -/* Load the data portion of a ctable formatted grid. */ -/************************************************************************/ - -int nad_ctable_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) - -{ - size_t a_size; - - pj_ctx_fseek( ctx, fid, sizeof(struct CTABLE), SEEK_SET ); - - /* read all the actual shift values */ - a_size = ct->lim.lam * ct->lim.phi; - ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); - if( ct->cvs == NULL - || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) - { - pj_dalloc( ct->cvs ); - ct->cvs = NULL; - - pj_log( ctx, PJ_LOG_ERROR, - "ctable loading failed on fread() - binary incompatible?" ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - - return 1; -} - -/************************************************************************/ -/* nad_ctable_init() */ -/* */ -/* Read the header portion of a "ctable" format grid. */ -/************************************************************************/ - -struct CTABLE *nad_ctable_init( projCtx ctx, PAFile fid ) -{ - struct CTABLE *ct; - int id_end; - - /* read the table header */ - ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if( ct == NULL - || pj_ctx_fread( ctx, ct, sizeof(struct CTABLE), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_dalloc( ct ); - return NULL; - } - - /* do some minimal validation to ensure the structure isn't corrupt */ - if( ct->lim.lam < 1 || ct->lim.lam > 100000 - || ct->lim.phi < 1 || ct->lim.phi > 100000 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_dalloc( ct ); - return NULL; - } - - /* trim white space and newlines off id */ - for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- ) - { - if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' ) - ct->id[id_end] = '\0'; - else - break; - } - - ct->cvs = NULL; - - return ct; -} - -/************************************************************************/ -/* nad_ctable2_load() */ -/* */ -/* Load the data portion of a ctable2 formatted grid. */ -/************************************************************************/ - -int nad_ctable2_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) - -{ - size_t a_size; - - pj_ctx_fseek( ctx, fid, 160, SEEK_SET ); - - /* read all the actual shift values */ - a_size = ct->lim.lam * ct->lim.phi; - ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); - if( ct->cvs == NULL - || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) - { - pj_dalloc( ct->cvs ); - ct->cvs = NULL; - - if( getenv("PROJ_DEBUG") != NULL ) - { - fprintf( stderr, - "ctable2 loading failed on fread() - binary incompatible?\n" ); - } - - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - - if( !IS_LSB ) - { - swap_words( ct->cvs, 4, (int)a_size * 2 ); - } - - return 1; -} - -/************************************************************************/ -/* nad_ctable2_init() */ -/* */ -/* Read the header portion of a "ctable2" format grid. */ -/************************************************************************/ - -struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) -{ - struct CTABLE *ct; - int id_end; - char header[160]; - - if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; - } - - if( !IS_LSB ) - { - swap_words( header + 96, 8, 4 ); - swap_words( header + 128, 4, 2 ); - } - - if( strncmp(header,"CTABLE V2",9) != 0 ) - { - pj_log( ctx, PJ_LOG_ERROR, "ctable2 - wrong header!" ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; - } - - /* read the table header */ - ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if( ct == NULL ) - { - pj_ctx_set_errno( ctx, ENOMEM ); - return NULL; - } - - memcpy( ct->id, header + 16, 80 ); - memcpy( &ct->ll.lam, header + 96, 8 ); - memcpy( &ct->ll.phi, header + 104, 8 ); - memcpy( &ct->del.lam, header + 112, 8 ); - memcpy( &ct->del.phi, header + 120, 8 ); - memcpy( &ct->lim.lam, header + 128, 4 ); - memcpy( &ct->lim.phi, header + 132, 4 ); - - /* do some minimal validation to ensure the structure isn't corrupt */ - if( ct->lim.lam < 1 || ct->lim.lam > 100000 - || ct->lim.phi < 1 || ct->lim.phi > 100000 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_dalloc( ct ); - return NULL; - } - - /* trim white space and newlines off id */ - for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- ) - { - if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' ) - ct->id[id_end] = '\0'; - else - break; - } - - ct->cvs = NULL; - - return ct; -} - -/************************************************************************/ -/* nad_init() */ -/* */ -/* Read a datum shift file in any of the supported binary formats. */ -/************************************************************************/ - -struct CTABLE *nad_init(projCtx ctx, char *name) -{ - char fname[MAX_PATH_FILENAME+1]; - struct CTABLE *ct; - PAFile fid; - - ctx->last_errno = 0; - -/* -------------------------------------------------------------------- */ -/* Open the file using the usual search rules. */ -/* -------------------------------------------------------------------- */ - strcpy(fname, name); - if (!(fid = pj_open_lib(ctx, fname, "rb"))) { - return 0; - } - - ct = nad_ctable_init( ctx, fid ); - if( ct != NULL ) - { - if( !nad_ctable_load( ctx, ct, fid ) ) - { - nad_free( ct ); - ct = NULL; - } - } - - pj_ctx_fclose(ctx, fid); - return ct; -} - -/************************************************************************/ -/* nad_free() */ -/* */ -/* Free a CTABLE grid shift structure produced by nad_init(). */ -/************************************************************************/ - -void nad_free(struct CTABLE *ct) -{ - if (ct) { - if( ct->cvs != NULL ) - pj_dalloc(ct->cvs); - - pj_dalloc(ct); - } -} diff --git a/src/nad_init.cpp b/src/nad_init.cpp new file mode 100644 index 00000000..8b024ac7 --- /dev/null +++ b/src/nad_init.cpp @@ -0,0 +1,305 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Load datum shift files into memory. + * 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. + *****************************************************************************/ + +#define PJ_LIB__ + +#include +#include +#include +#include +#include + +#include "projects.h" + +/************************************************************************/ +/* 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; + } +} + +/************************************************************************/ +/* nad_ctable_load() */ +/* */ +/* Load the data portion of a ctable formatted grid. */ +/************************************************************************/ + +int nad_ctable_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) + +{ + size_t a_size; + + pj_ctx_fseek( ctx, fid, sizeof(struct CTABLE), SEEK_SET ); + + /* read all the actual shift values */ + a_size = ct->lim.lam * ct->lim.phi; + ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); + if( ct->cvs == NULL + || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) + { + pj_dalloc( ct->cvs ); + ct->cvs = NULL; + + pj_log( ctx, PJ_LOG_ERROR, + "ctable loading failed on fread() - binary incompatible?" ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + + return 1; +} + +/************************************************************************/ +/* nad_ctable_init() */ +/* */ +/* Read the header portion of a "ctable" format grid. */ +/************************************************************************/ + +struct CTABLE *nad_ctable_init( projCtx ctx, PAFile fid ) +{ + struct CTABLE *ct; + int id_end; + + /* read the table header */ + ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); + if( ct == NULL + || pj_ctx_fread( ctx, ct, sizeof(struct CTABLE), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_dalloc( ct ); + return NULL; + } + + /* do some minimal validation to ensure the structure isn't corrupt */ + if( ct->lim.lam < 1 || ct->lim.lam > 100000 + || ct->lim.phi < 1 || ct->lim.phi > 100000 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_dalloc( ct ); + return NULL; + } + + /* trim white space and newlines off id */ + for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- ) + { + if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' ) + ct->id[id_end] = '\0'; + else + break; + } + + ct->cvs = NULL; + + return ct; +} + +/************************************************************************/ +/* nad_ctable2_load() */ +/* */ +/* Load the data portion of a ctable2 formatted grid. */ +/************************************************************************/ + +int nad_ctable2_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) + +{ + size_t a_size; + + pj_ctx_fseek( ctx, fid, 160, SEEK_SET ); + + /* read all the actual shift values */ + a_size = ct->lim.lam * ct->lim.phi; + ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); + if( ct->cvs == NULL + || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) + { + pj_dalloc( ct->cvs ); + ct->cvs = NULL; + + if( getenv("PROJ_DEBUG") != NULL ) + { + fprintf( stderr, + "ctable2 loading failed on fread() - binary incompatible?\n" ); + } + + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + + if( !IS_LSB ) + { + swap_words( ct->cvs, 4, (int)a_size * 2 ); + } + + return 1; +} + +/************************************************************************/ +/* nad_ctable2_init() */ +/* */ +/* Read the header portion of a "ctable2" format grid. */ +/************************************************************************/ + +struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) +{ + struct CTABLE *ct; + int id_end; + char header[160]; + + if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return NULL; + } + + if( !IS_LSB ) + { + swap_words( header + 96, 8, 4 ); + swap_words( header + 128, 4, 2 ); + } + + if( strncmp(header,"CTABLE V2",9) != 0 ) + { + pj_log( ctx, PJ_LOG_ERROR, "ctable2 - wrong header!" ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return NULL; + } + + /* read the table header */ + ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); + if( ct == NULL ) + { + pj_ctx_set_errno( ctx, ENOMEM ); + return NULL; + } + + memcpy( ct->id, header + 16, 80 ); + memcpy( &ct->ll.lam, header + 96, 8 ); + memcpy( &ct->ll.phi, header + 104, 8 ); + memcpy( &ct->del.lam, header + 112, 8 ); + memcpy( &ct->del.phi, header + 120, 8 ); + memcpy( &ct->lim.lam, header + 128, 4 ); + memcpy( &ct->lim.phi, header + 132, 4 ); + + /* do some minimal validation to ensure the structure isn't corrupt */ + if( ct->lim.lam < 1 || ct->lim.lam > 100000 + || ct->lim.phi < 1 || ct->lim.phi > 100000 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_dalloc( ct ); + return NULL; + } + + /* trim white space and newlines off id */ + for( id_end = (int)strlen(ct->id)-1; id_end > 0; id_end-- ) + { + if( ct->id[id_end] == '\n' || ct->id[id_end] == ' ' ) + ct->id[id_end] = '\0'; + else + break; + } + + ct->cvs = NULL; + + return ct; +} + +/************************************************************************/ +/* nad_init() */ +/* */ +/* Read a datum shift file in any of the supported binary formats. */ +/************************************************************************/ + +struct CTABLE *nad_init(projCtx ctx, char *name) +{ + char fname[MAX_PATH_FILENAME+1]; + struct CTABLE *ct; + PAFile fid; + + ctx->last_errno = 0; + +/* -------------------------------------------------------------------- */ +/* Open the file using the usual search rules. */ +/* -------------------------------------------------------------------- */ + strcpy(fname, name); + if (!(fid = pj_open_lib(ctx, fname, "rb"))) { + return 0; + } + + ct = nad_ctable_init( ctx, fid ); + if( ct != NULL ) + { + if( !nad_ctable_load( ctx, ct, fid ) ) + { + nad_free( ct ); + ct = NULL; + } + } + + pj_ctx_fclose(ctx, fid); + return ct; +} + +/************************************************************************/ +/* nad_free() */ +/* */ +/* Free a CTABLE grid shift structure produced by nad_init(). */ +/************************************************************************/ + +void nad_free(struct CTABLE *ct) +{ + if (ct) { + if( ct->cvs != NULL ) + pj_dalloc(ct->cvs); + + pj_dalloc(ct); + } +} diff --git a/src/nad_intr.c b/src/nad_intr.c deleted file mode 100644 index 1f9d1e0c..00000000 --- a/src/nad_intr.c +++ /dev/null @@ -1,68 +0,0 @@ -/* Determine nad table correction value */ -#define PJ_LIB__ -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - - LP -nad_intr(LP t, struct CTABLE *ct) { - LP val, frct; - ILP indx; - double m00, m10, m01, m11; - FLP *f00, *f10, *f01, *f11; - long index; - int in; - - t.lam /= ct->del.lam; - indx.lam = isnan(t.lam) ? 0 : (pj_int32)lround(floor(t.lam)); - t.phi /= ct->del.phi; - indx.phi = isnan(t.phi) ? 0 : (pj_int32)lround(floor(t.phi)); - - frct.lam = t.lam - indx.lam; - frct.phi = t.phi - indx.phi; - val.lam = val.phi = HUGE_VAL; - if (indx.lam < 0) { - if (indx.lam == -1 && frct.lam > 0.99999999999) { - ++indx.lam; - frct.lam = 0.; - } else - return val; - } else if ((in = indx.lam + 1) >= ct->lim.lam) { - if (in == ct->lim.lam && frct.lam < 1e-11) { - --indx.lam; - frct.lam = 1.; - } else - return val; - } - if (indx.phi < 0) { - if (indx.phi == -1 && frct.phi > 0.99999999999) { - ++indx.phi; - frct.phi = 0.; - } else - return val; - } else if ((in = indx.phi + 1) >= ct->lim.phi) { - if (in == ct->lim.phi && frct.phi < 1e-11) { - --indx.phi; - frct.phi = 1.; - } else - return val; - } - index = indx.phi * ct->lim.lam + indx.lam; - f00 = ct->cvs + index++; - f10 = ct->cvs + index; - index += ct->lim.lam; - f11 = ct->cvs + index--; - f01 = ct->cvs + index; - m11 = m10 = frct.lam; - m00 = m01 = 1. - frct.lam; - m11 *= frct.phi; - m01 *= frct.phi; - frct.phi = 1. - frct.phi; - m00 *= frct.phi; - m10 *= frct.phi; - val.lam = m00 * f00->lam + m10 * f10->lam + - m01 * f01->lam + m11 * f11->lam; - val.phi = m00 * f00->phi + m10 * f10->phi + - m01 * f01->phi + m11 * f11->phi; - return val; -} diff --git a/src/nad_intr.cpp b/src/nad_intr.cpp new file mode 100644 index 00000000..1f9d1e0c --- /dev/null +++ b/src/nad_intr.cpp @@ -0,0 +1,68 @@ +/* Determine nad table correction value */ +#define PJ_LIB__ +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" + + LP +nad_intr(LP t, struct CTABLE *ct) { + LP val, frct; + ILP indx; + double m00, m10, m01, m11; + FLP *f00, *f10, *f01, *f11; + long index; + int in; + + t.lam /= ct->del.lam; + indx.lam = isnan(t.lam) ? 0 : (pj_int32)lround(floor(t.lam)); + t.phi /= ct->del.phi; + indx.phi = isnan(t.phi) ? 0 : (pj_int32)lround(floor(t.phi)); + + frct.lam = t.lam - indx.lam; + frct.phi = t.phi - indx.phi; + val.lam = val.phi = HUGE_VAL; + if (indx.lam < 0) { + if (indx.lam == -1 && frct.lam > 0.99999999999) { + ++indx.lam; + frct.lam = 0.; + } else + return val; + } else if ((in = indx.lam + 1) >= ct->lim.lam) { + if (in == ct->lim.lam && frct.lam < 1e-11) { + --indx.lam; + frct.lam = 1.; + } else + return val; + } + if (indx.phi < 0) { + if (indx.phi == -1 && frct.phi > 0.99999999999) { + ++indx.phi; + frct.phi = 0.; + } else + return val; + } else if ((in = indx.phi + 1) >= ct->lim.phi) { + if (in == ct->lim.phi && frct.phi < 1e-11) { + --indx.phi; + frct.phi = 1.; + } else + return val; + } + index = indx.phi * ct->lim.lam + indx.lam; + f00 = ct->cvs + index++; + f10 = ct->cvs + index; + index += ct->lim.lam; + f11 = ct->cvs + index--; + f01 = ct->cvs + index; + m11 = m10 = frct.lam; + m00 = m01 = 1. - frct.lam; + m11 *= frct.phi; + m01 *= frct.phi; + frct.phi = 1. - frct.phi; + m00 *= frct.phi; + m10 *= frct.phi; + val.lam = m00 * f00->lam + m10 * f10->lam + + m01 * f01->lam + m11 * f11->lam; + val.phi = m00 * f00->phi + m10 * f10->phi + + m01 * f01->phi + m11 * f11->phi; + return val; +} diff --git a/src/p_series.c b/src/p_series.c deleted file mode 100644 index cddea888..00000000 --- a/src/p_series.c +++ /dev/null @@ -1,42 +0,0 @@ -/* print row coefficients of Tseries structure */ -#include "projects.h" -#include -#include -#define NF 20 /* length of final format string */ -#define CUT 60 /* check length of line */ - -/* FIXME: put the declaration in a header. Also used in gen_cheb.c */ -void p_series(Tseries *T, FILE *file, char *fmt); - -void p_series(Tseries *T, FILE *file, char *fmt) { - int i, j, n, L; - char format[NF+1]; - - *format = ' '; - strncpy(format + 1, fmt, NF - 3); - strcat(format, "%n"); - fprintf(file, "u: %d\n", T->mu+1); - for (i = 0; i <= T->mu; ++i) - if (T->cu[i].m) { - fprintf(file, "%d %d%n", i, T->cu[i].m, &L); - n = 0; - for (j = 0; j < T->cu[i].m; ++j) { - if ((L += n) > CUT) - fprintf(file, "\n %n", &L); - fprintf(file, format, T->cu[i].c[j], &n); - } - fputc('\n', file); - } - fprintf(file, "v: %d\n", T->mv+1); - for (i = 0; i <= T->mv; ++i) - if (T->cv[i].m) { - fprintf(file, "%d %d%n", i, T->cv[i].m, &L); - n = 0; - for (j = 0; j < T->cv[i].m; ++j) { - if ((L += n) > 60) - fprintf(file, "\n %n", &L); - fprintf(file, format, T->cv[i].c[j], &n); - } - fputc('\n', file); - } -} diff --git a/src/p_series.cpp b/src/p_series.cpp new file mode 100644 index 00000000..cddea888 --- /dev/null +++ b/src/p_series.cpp @@ -0,0 +1,42 @@ +/* print row coefficients of Tseries structure */ +#include "projects.h" +#include +#include +#define NF 20 /* length of final format string */ +#define CUT 60 /* check length of line */ + +/* FIXME: put the declaration in a header. Also used in gen_cheb.c */ +void p_series(Tseries *T, FILE *file, char *fmt); + +void p_series(Tseries *T, FILE *file, char *fmt) { + int i, j, n, L; + char format[NF+1]; + + *format = ' '; + strncpy(format + 1, fmt, NF - 3); + strcat(format, "%n"); + fprintf(file, "u: %d\n", T->mu+1); + for (i = 0; i <= T->mu; ++i) + if (T->cu[i].m) { + fprintf(file, "%d %d%n", i, T->cu[i].m, &L); + n = 0; + for (j = 0; j < T->cu[i].m; ++j) { + if ((L += n) > CUT) + fprintf(file, "\n %n", &L); + fprintf(file, format, T->cu[i].c[j], &n); + } + fputc('\n', file); + } + fprintf(file, "v: %d\n", T->mv+1); + for (i = 0; i <= T->mv; ++i) + if (T->cv[i].m) { + fprintf(file, "%d %d%n", i, T->cv[i].m, &L); + n = 0; + for (j = 0; j < T->cv[i].m; ++j) { + if ((L += n) > 60) + fprintf(file, "\n %n", &L); + fprintf(file, format, T->cv[i].c[j], &n); + } + fputc('\n', file); + } +} diff --git a/src/pj_apply_gridshift.c b/src/pj_apply_gridshift.c deleted file mode 100644 index 45ce5c8e..00000000 --- a/src/pj_apply_gridshift.c +++ /dev/null @@ -1,356 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Apply datum shifts based on grid shift files (normally NAD27 to - * NAD83 or the reverse). This module is responsible for keeping - * a list of loaded grids, and calling with each one that is - * allowed for a given datum (expressed as the nadgrids= parameter). - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -/************************************************************************/ -/* pj_apply_gridshift() */ -/* */ -/* This is the externally callable interface - part of the */ -/* public API - though it is not used internally any more and I */ -/* doubt it is used by any other applications. But we preserve */ -/* it to honour our public api. */ -/************************************************************************/ - -int pj_apply_gridshift( projCtx ctx, const char *nadgrids, int inverse, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - PJ_GRIDINFO **gridlist; - int grid_count; - int ret; - - gridlist = pj_gridlist_from_nadgrids( ctx, nadgrids, &grid_count ); - - if( gridlist == NULL || grid_count == 0 ) - return ctx->last_errno; - - ret = pj_apply_gridshift_3( ctx, gridlist, grid_count, inverse, - point_count, point_offset, x, y, z ); - - /* - ** Note this frees the array of grid list pointers, but not the grids - ** which is as intended. The grids themselves live on. - */ - pj_dalloc( gridlist ); - - return ret; -} - -/************************************************************************/ -/* pj_apply_gridshift_2() */ -/* */ -/* This implementation uses the gridlist from a coordinate */ -/* system definition. If the gridlist has not yet been */ -/* populated in the coordinate system definition we set it up */ -/* now. */ -/************************************************************************/ - -int pj_apply_gridshift_2( PJ *defn, int inverse, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - if( defn->catalog_name != NULL ) - return pj_gc_apply_gridshift( defn, inverse, point_count, point_offset, - x, y, z ); - - if( defn->gridlist == NULL ) - { - defn->gridlist = - pj_gridlist_from_nadgrids( pj_get_ctx( defn ), - pj_param(defn->ctx, defn->params,"snadgrids").s, - &(defn->gridlist_count) ); - - if( defn->gridlist == NULL || defn->gridlist_count == 0 ) - return defn->ctx->last_errno; - } - - return pj_apply_gridshift_3( pj_get_ctx( defn ), - defn->gridlist, defn->gridlist_count, inverse, - point_count, point_offset, x, y, z ); -} - -/************************************************************************/ -/* find_ctable() */ -/* */ -/* Determine which grid is the correct given an input coordinate. */ -/************************************************************************/ - -static struct CTABLE* find_ctable(projCtx ctx, LP input, int grid_count, PJ_GRIDINFO **tables) { - int itable; - - /* keep trying till we find a table that works */ - for( itable = 0; itable < grid_count; itable++ ) - { - - PJ_GRIDINFO *gi = tables[itable]; - struct CTABLE *ct = gi->ct; - double epsilon = (fabs(ct->del.phi)+fabs(ct->del.lam))/10000.0; - /* skip tables that don't match our point at all. */ - if ( ct->ll.phi - epsilon > input.phi - || ct->ll.lam - epsilon > input.lam - || (ct->ll.phi + (ct->lim.phi-1) * ct->del.phi + epsilon < input.phi) - || (ct->ll.lam + (ct->lim.lam-1) * ct->del.lam + epsilon < input.lam) ) { - continue; - } - - /* If we have child nodes, check to see if any of them apply. */ - while( gi->child ) - { - PJ_GRIDINFO *child; - - for( child = gi->child; child != NULL; child = child->next ) - { - struct CTABLE *ct1 = child->ct; - epsilon = (fabs(ct1->del.phi)+fabs(ct1->del.lam))/10000.0; - - if( ct1->ll.phi - epsilon > input.phi - || ct1->ll.lam - epsilon > input.lam - || (ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi + epsilon < input.phi) - || (ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam + epsilon < input.lam) ) { - continue; - } - break; - } - - /* If we didn't find a child then nothing more to do */ - if( child == NULL ) break; - - /* Otherwise use the child, first checking it's children */ - gi = child; - ct = child->ct; - } - /* load the grid shift info if we don't have it. */ - if( ct->cvs == NULL) { - if (!pj_gridinfo_load( ctx, gi ) ) { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; - } - } - /* if we get this far we have found a suitable grid */ - return ct; - } - - return NULL; -} - -/************************************************************************/ -/* pj_apply_gridshift_3() */ -/* */ -/* This is the real workhorse, given a gridlist. */ -/************************************************************************/ - -int pj_apply_gridshift_3( projCtx ctx, PJ_GRIDINFO **gridlist, int gridlist_count, - int inverse, long point_count, int point_offset, - double *x, double *y, double *z ) -{ - int i; - struct CTABLE *ct; - static int debug_count = 0; - (void) z; - - if( gridlist== NULL || gridlist_count == 0 ) - { - pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - - ctx->last_errno = 0; - - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - LP input, output; - int itable; - - input.phi = y[io]; - input.lam = x[io]; - output.phi = HUGE_VAL; - output.lam = HUGE_VAL; - - ct = find_ctable(ctx, input, gridlist_count, gridlist); - if( ct != NULL ) - { - output = nad_cvt( input, inverse, ct ); - - if ( output.lam != HUGE_VAL && debug_count++ < 20 ) - pj_log( ctx, PJ_LOG_DEBUG_MINOR, "pj_apply_gridshift(): used %s", ct->id ); - } - - if ( output.lam == HUGE_VAL ) - { - if( ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) - { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "pj_apply_gridshift(): failed to find a grid shift table for\n" - " location (%.7fdW,%.7fdN)", - x[io] * RAD_TO_DEG, - y[io] * RAD_TO_DEG ); - for( itable = 0; itable < gridlist_count; itable++ ) - { - PJ_GRIDINFO *gi = gridlist[itable]; - if( itable == 0 ) - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, " tried: %s", gi->gridname ); - else - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, ",%s", gi->gridname ); - } - } - - /* - * We don't actually have any machinery currently to set the - * following macro, so this is mostly kept here to make it clear - * how we ought to operate if we wanted to make it super clear - * that an error has occurred when points are outside our available - * datum shift areas. But if this is on, we will find that "low - * value" points on the fringes of some datasets will completely - * fail causing lots of problems when it is more or less ok to - * just not apply a datum shift. So rather than deal with - * that we just fallback to no shift. (see also bug #45). - */ -#ifdef ERR_GRID_AREA_TRANSIENT_SEVERE - y[io] = HUGE_VAL; - x[io] = HUGE_VAL; -#else - /* leave x/y unshifted. */ -#endif - } - else - { - y[io] = output.phi; - x[io] = output.lam; - } - } - - return 0; -} - -/**********************************************/ -int proj_hgrid_init(PJ* P, const char *grids) { -/********************************************** - - Initizalize and populate list of horizontal - grids. - - Takes a PJ-object and the plus-parameter - name that is used in the proj-string to - specify the grids to load, e.g. "+grids". - The + should be left out here. - - Returns the number of loaded grids. - -***********************************************/ - - /* prepend "s" to the "grids" string to allow usage with pj_param */ - char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); - sprintf(sgrids, "%s%s", "s", grids); - - if (P->gridlist == NULL) { - P->gridlist = pj_gridlist_from_nadgrids( - P->ctx, - pj_param(P->ctx, P->params, sgrids).s, - &(P->gridlist_count) - ); - - if( P->gridlist == NULL || P->gridlist_count == 0 ) { - pj_dealloc(sgrids); - return 0; - } - } - - if (P->gridlist_count == 0) { - proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - pj_dealloc(sgrids); - return P->gridlist_count; -} - -/********************************************/ -/* proj_hgrid_value() */ -/* */ -/* Return coordinate offset in grid */ -/********************************************/ -LP proj_hgrid_value(PJ *P, LP lp) { - struct CTABLE *ct; - LP out = proj_coord_error().lp; - - ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); - if (ct == 0) { - pj_ctx_set_errno( P->ctx, PJD_ERR_GRID_AREA); - return out; - } - - /* normalize input to ll origin */ - lp.lam -= ct->ll.lam; - lp.phi -= ct->ll.phi; - - lp.lam = adjlon(lp.lam - M_PI) + M_PI; - - out = nad_intr(lp, ct); - - if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) { - pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA); - } - - return out; -} - -LP proj_hgrid_apply(PJ *P, LP lp, PJ_DIRECTION direction) { - struct CTABLE *ct; - int inverse; - LP out; - - out.lam = HUGE_VAL; out.phi = HUGE_VAL; - - ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); - - if (ct == NULL || ct->cvs == NULL) { - pj_ctx_set_errno( P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return out; - } - - inverse = direction == PJ_FWD ? 0 : 1; - out = nad_cvt(lp, inverse, ct); - - if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) - pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA); - - return out; - -} diff --git a/src/pj_apply_gridshift.cpp b/src/pj_apply_gridshift.cpp new file mode 100644 index 00000000..45ce5c8e --- /dev/null +++ b/src/pj_apply_gridshift.cpp @@ -0,0 +1,356 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Apply datum shifts based on grid shift files (normally NAD27 to + * NAD83 or the reverse). This module is responsible for keeping + * a list of loaded grids, and calling with each one that is + * allowed for a given datum (expressed as the nadgrids= parameter). + * 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. + *****************************************************************************/ + +#define PJ_LIB__ + +#include +#include +#include + +#include "proj_internal.h" +#include "projects.h" + +/************************************************************************/ +/* pj_apply_gridshift() */ +/* */ +/* This is the externally callable interface - part of the */ +/* public API - though it is not used internally any more and I */ +/* doubt it is used by any other applications. But we preserve */ +/* it to honour our public api. */ +/************************************************************************/ + +int pj_apply_gridshift( projCtx ctx, const char *nadgrids, int inverse, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + PJ_GRIDINFO **gridlist; + int grid_count; + int ret; + + gridlist = pj_gridlist_from_nadgrids( ctx, nadgrids, &grid_count ); + + if( gridlist == NULL || grid_count == 0 ) + return ctx->last_errno; + + ret = pj_apply_gridshift_3( ctx, gridlist, grid_count, inverse, + point_count, point_offset, x, y, z ); + + /* + ** Note this frees the array of grid list pointers, but not the grids + ** which is as intended. The grids themselves live on. + */ + pj_dalloc( gridlist ); + + return ret; +} + +/************************************************************************/ +/* pj_apply_gridshift_2() */ +/* */ +/* This implementation uses the gridlist from a coordinate */ +/* system definition. If the gridlist has not yet been */ +/* populated in the coordinate system definition we set it up */ +/* now. */ +/************************************************************************/ + +int pj_apply_gridshift_2( PJ *defn, int inverse, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + if( defn->catalog_name != NULL ) + return pj_gc_apply_gridshift( defn, inverse, point_count, point_offset, + x, y, z ); + + if( defn->gridlist == NULL ) + { + defn->gridlist = + pj_gridlist_from_nadgrids( pj_get_ctx( defn ), + pj_param(defn->ctx, defn->params,"snadgrids").s, + &(defn->gridlist_count) ); + + if( defn->gridlist == NULL || defn->gridlist_count == 0 ) + return defn->ctx->last_errno; + } + + return pj_apply_gridshift_3( pj_get_ctx( defn ), + defn->gridlist, defn->gridlist_count, inverse, + point_count, point_offset, x, y, z ); +} + +/************************************************************************/ +/* find_ctable() */ +/* */ +/* Determine which grid is the correct given an input coordinate. */ +/************************************************************************/ + +static struct CTABLE* find_ctable(projCtx ctx, LP input, int grid_count, PJ_GRIDINFO **tables) { + int itable; + + /* keep trying till we find a table that works */ + for( itable = 0; itable < grid_count; itable++ ) + { + + PJ_GRIDINFO *gi = tables[itable]; + struct CTABLE *ct = gi->ct; + double epsilon = (fabs(ct->del.phi)+fabs(ct->del.lam))/10000.0; + /* skip tables that don't match our point at all. */ + if ( ct->ll.phi - epsilon > input.phi + || ct->ll.lam - epsilon > input.lam + || (ct->ll.phi + (ct->lim.phi-1) * ct->del.phi + epsilon < input.phi) + || (ct->ll.lam + (ct->lim.lam-1) * ct->del.lam + epsilon < input.lam) ) { + continue; + } + + /* If we have child nodes, check to see if any of them apply. */ + while( gi->child ) + { + PJ_GRIDINFO *child; + + for( child = gi->child; child != NULL; child = child->next ) + { + struct CTABLE *ct1 = child->ct; + epsilon = (fabs(ct1->del.phi)+fabs(ct1->del.lam))/10000.0; + + if( ct1->ll.phi - epsilon > input.phi + || ct1->ll.lam - epsilon > input.lam + || (ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi + epsilon < input.phi) + || (ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam + epsilon < input.lam) ) { + continue; + } + break; + } + + /* If we didn't find a child then nothing more to do */ + if( child == NULL ) break; + + /* Otherwise use the child, first checking it's children */ + gi = child; + ct = child->ct; + } + /* load the grid shift info if we don't have it. */ + if( ct->cvs == NULL) { + if (!pj_gridinfo_load( ctx, gi ) ) { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return NULL; + } + } + /* if we get this far we have found a suitable grid */ + return ct; + } + + return NULL; +} + +/************************************************************************/ +/* pj_apply_gridshift_3() */ +/* */ +/* This is the real workhorse, given a gridlist. */ +/************************************************************************/ + +int pj_apply_gridshift_3( projCtx ctx, PJ_GRIDINFO **gridlist, int gridlist_count, + int inverse, long point_count, int point_offset, + double *x, double *y, double *z ) +{ + int i; + struct CTABLE *ct; + static int debug_count = 0; + (void) z; + + if( gridlist== NULL || gridlist_count == 0 ) + { + pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + + ctx->last_errno = 0; + + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + LP input, output; + int itable; + + input.phi = y[io]; + input.lam = x[io]; + output.phi = HUGE_VAL; + output.lam = HUGE_VAL; + + ct = find_ctable(ctx, input, gridlist_count, gridlist); + if( ct != NULL ) + { + output = nad_cvt( input, inverse, ct ); + + if ( output.lam != HUGE_VAL && debug_count++ < 20 ) + pj_log( ctx, PJ_LOG_DEBUG_MINOR, "pj_apply_gridshift(): used %s", ct->id ); + } + + if ( output.lam == HUGE_VAL ) + { + if( ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) + { + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "pj_apply_gridshift(): failed to find a grid shift table for\n" + " location (%.7fdW,%.7fdN)", + x[io] * RAD_TO_DEG, + y[io] * RAD_TO_DEG ); + for( itable = 0; itable < gridlist_count; itable++ ) + { + PJ_GRIDINFO *gi = gridlist[itable]; + if( itable == 0 ) + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, " tried: %s", gi->gridname ); + else + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, ",%s", gi->gridname ); + } + } + + /* + * We don't actually have any machinery currently to set the + * following macro, so this is mostly kept here to make it clear + * how we ought to operate if we wanted to make it super clear + * that an error has occurred when points are outside our available + * datum shift areas. But if this is on, we will find that "low + * value" points on the fringes of some datasets will completely + * fail causing lots of problems when it is more or less ok to + * just not apply a datum shift. So rather than deal with + * that we just fallback to no shift. (see also bug #45). + */ +#ifdef ERR_GRID_AREA_TRANSIENT_SEVERE + y[io] = HUGE_VAL; + x[io] = HUGE_VAL; +#else + /* leave x/y unshifted. */ +#endif + } + else + { + y[io] = output.phi; + x[io] = output.lam; + } + } + + return 0; +} + +/**********************************************/ +int proj_hgrid_init(PJ* P, const char *grids) { +/********************************************** + + Initizalize and populate list of horizontal + grids. + + Takes a PJ-object and the plus-parameter + name that is used in the proj-string to + specify the grids to load, e.g. "+grids". + The + should be left out here. + + Returns the number of loaded grids. + +***********************************************/ + + /* prepend "s" to the "grids" string to allow usage with pj_param */ + char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); + sprintf(sgrids, "%s%s", "s", grids); + + if (P->gridlist == NULL) { + P->gridlist = pj_gridlist_from_nadgrids( + P->ctx, + pj_param(P->ctx, P->params, sgrids).s, + &(P->gridlist_count) + ); + + if( P->gridlist == NULL || P->gridlist_count == 0 ) { + pj_dealloc(sgrids); + return 0; + } + } + + if (P->gridlist_count == 0) { + proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID); + } + + pj_dealloc(sgrids); + return P->gridlist_count; +} + +/********************************************/ +/* proj_hgrid_value() */ +/* */ +/* Return coordinate offset in grid */ +/********************************************/ +LP proj_hgrid_value(PJ *P, LP lp) { + struct CTABLE *ct; + LP out = proj_coord_error().lp; + + ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); + if (ct == 0) { + pj_ctx_set_errno( P->ctx, PJD_ERR_GRID_AREA); + return out; + } + + /* normalize input to ll origin */ + lp.lam -= ct->ll.lam; + lp.phi -= ct->ll.phi; + + lp.lam = adjlon(lp.lam - M_PI) + M_PI; + + out = nad_intr(lp, ct); + + if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) { + pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA); + } + + return out; +} + +LP proj_hgrid_apply(PJ *P, LP lp, PJ_DIRECTION direction) { + struct CTABLE *ct; + int inverse; + LP out; + + out.lam = HUGE_VAL; out.phi = HUGE_VAL; + + ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); + + if (ct == NULL || ct->cvs == NULL) { + pj_ctx_set_errno( P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return out; + } + + inverse = direction == PJ_FWD ? 0 : 1; + out = nad_cvt(lp, inverse, ct); + + if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) + pj_ctx_set_errno(P->ctx, PJD_ERR_GRID_AREA); + + return out; + +} diff --git a/src/pj_apply_vgridshift.c b/src/pj_apply_vgridshift.c deleted file mode 100644 index c1344951..00000000 --- a/src/pj_apply_vgridshift.c +++ /dev/null @@ -1,331 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Apply vertical datum shifts based on grid shift files, normally - * geoid grids mapping WGS84 to NAVD88 or something similar. - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include - -#include "proj_math.h" -#include "proj_internal.h" -#include "projects.h" - -static int is_nodata(float value) -{ - /* nodata? */ - /* GTX official nodata value if -88.88880f, but some grids also */ - /* use other big values for nodata (e.g naptrans2008.gtx has */ - /* nodata values like -2147479936), so test them too */ - return value > 1000 || value < -1000 || value == -88.88880f; -} - -static double read_vgrid_value( PJ *defn, LP input, int *gridlist_count_p, PJ_GRIDINFO **tables, struct CTABLE *ct) { - int itable = 0; - double value = HUGE_VAL; - double grid_x, grid_y; - long grid_ix, grid_iy; - long grid_ix2, grid_iy2; - float *cvs; - /* do not deal with NaN coordinates */ - /* cppcheck-suppress duplicateExpression */ - if( isnan(input.phi) || isnan(input.lam) ) - itable = *gridlist_count_p; - - /* keep trying till we find a table that works */ - for ( ; itable < *gridlist_count_p; itable++ ) - { - PJ_GRIDINFO *gi = tables[itable]; - - ct = gi->ct; - - /* skip tables that don't match our point at all. */ - if( ct->ll.phi > input.phi || ct->ll.lam > input.lam - || ct->ll.phi + (ct->lim.phi-1) * ct->del.phi < input.phi - || ct->ll.lam + (ct->lim.lam-1) * ct->del.lam < input.lam ) - continue; - - /* If we have child nodes, check to see if any of them apply. */ - while( gi->child != NULL ) - { - PJ_GRIDINFO *child; - - for( child = gi->child; child != NULL; child = child->next ) - { - struct CTABLE *ct1 = child->ct; - - if( ct1->ll.phi > input.phi || ct1->ll.lam > input.lam - || ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi < input.phi - || ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam < input.lam) - continue; - - break; - } - - /* we didn't find a more refined child node to use, so go with current grid */ - if( child == NULL ) - { - break; - } - - /* Otherwise let's try for childrens children .. */ - gi = child; - ct = child->ct; - } - - /* load the grid shift info if we don't have it. */ - if( ct->cvs == NULL && !pj_gridinfo_load( pj_get_ctx(defn), gi ) ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - - - /* Interpolation a location within the grid */ - grid_x = (input.lam - ct->ll.lam) / ct->del.lam; - grid_y = (input.phi - ct->ll.phi) / ct->del.phi; - grid_ix = lround(floor(grid_x)); - grid_iy = lround(floor(grid_y)); - grid_x -= grid_ix; - grid_y -= grid_iy; - - grid_ix2 = grid_ix + 1; - if( grid_ix2 >= ct->lim.lam ) - grid_ix2 = ct->lim.lam - 1; - grid_iy2 = grid_iy + 1; - if( grid_iy2 >= ct->lim.phi ) - grid_iy2 = ct->lim.phi - 1; - - cvs = (float *) ct->cvs; - { - float value_a = cvs[grid_ix + grid_iy * ct->lim.lam]; - float value_b = cvs[grid_ix2 + grid_iy * ct->lim.lam]; - float value_c = cvs[grid_ix + grid_iy2 * ct->lim.lam]; - float value_d = cvs[grid_ix2 + grid_iy2 * ct->lim.lam]; - double total_weight = 0.0; - int n_weights = 0; - value = 0.0f; - if( !is_nodata(value_a) ) - { - double weight = (1.0-grid_x) * (1.0-grid_y); - value += value_a * weight; - total_weight += weight; - n_weights ++; - } - if( !is_nodata(value_b) ) - { - double weight = (grid_x) * (1.0-grid_y); - value += value_b * weight; - total_weight += weight; - n_weights ++; - } - if( !is_nodata(value_c) ) - { - double weight = (1.0-grid_x) * (grid_y); - value += value_c * weight; - total_weight += weight; - n_weights ++; - } - if( !is_nodata(value_d) ) - { - double weight = (grid_x) * (grid_y); - value += value_d * weight; - total_weight += weight; - n_weights ++; - } - if( n_weights == 0 ) - value = HUGE_VAL; - else if( n_weights != 4 ) - value /= total_weight; - } - - } - - return value; -} - -/************************************************************************/ -/* pj_apply_vgridshift() */ -/* */ -/* This implementation takes uses the gridlist from a coordinate */ -/* system definition. If the gridlist has not yet been */ -/* populated in the coordinate system definition we set it up */ -/* now. */ -/************************************************************************/ -int pj_apply_vgridshift( PJ *defn, const char *listname, - PJ_GRIDINFO ***gridlist_p, - int *gridlist_count_p, - int inverse, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - int i; - static int debug_count = 0; - PJ_GRIDINFO **tables; - struct CTABLE ct; - - if( *gridlist_p == NULL ) - { - *gridlist_p = - pj_gridlist_from_nadgrids( pj_get_ctx(defn), - pj_param(defn->ctx,defn->params,listname).s, - gridlist_count_p ); - - if( *gridlist_p == NULL || *gridlist_count_p == 0 ) - return defn->ctx->last_errno; - } - - if( *gridlist_count_p == 0 ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - - tables = *gridlist_p; - defn->ctx->last_errno = 0; - - for( i = 0; i < point_count; i++ ) - { - double value; - long io = i * point_offset; - LP input; - - input.phi = y[io]; - input.lam = x[io]; - - value = read_vgrid_value(defn, input, gridlist_count_p, tables, &ct); - - if( inverse ) - z[io] -= value; - else - z[io] += value; - if( value != HUGE_VAL ) - { - if( debug_count++ < 20 ) { - proj_log_trace(defn, "pj_apply_gridshift(): used %s", ct.id); - break; - } - } - - if( value == HUGE_VAL ) - { - int itable; - char gridlist[3000]; - - proj_log_debug(defn, - "pj_apply_vgridshift(): failed to find a grid shift table for\n" - " location (%.7fdW,%.7fdN)", - x[io] * RAD_TO_DEG, - y[io] * RAD_TO_DEG ); - - gridlist[0] = '\0'; - for( itable = 0; itable < *gridlist_count_p; itable++ ) - { - PJ_GRIDINFO *gi = tables[itable]; - if( strlen(gridlist) + strlen(gi->gridname) > sizeof(gridlist)-100 ) - { - strcat( gridlist, "..." ); - break; - } - - if( itable == 0 ) - sprintf( gridlist, " tried: %s", gi->gridname ); - else - sprintf( gridlist+strlen(gridlist), ",%s", gi->gridname ); - } - - proj_log_debug(defn, "%s", gridlist); - pj_ctx_set_errno( defn->ctx, PJD_ERR_GRID_AREA ); - - return PJD_ERR_GRID_AREA; - } - } - - return 0; -} - -/**********************************************/ -int proj_vgrid_init(PJ* P, const char *grids) { -/********************************************** - - Initizalize and populate gridlist. - - Takes a PJ-object and the plus-parameter - name that is used in the proj-string to - specify the grids to load, e.g. "+grids". - The + should be left out here. - - Returns the number of loaded grids. - -***********************************************/ - - /* prepend "s" to the "grids" string to allow usage with pj_param */ - char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); - sprintf(sgrids, "%s%s", "s", grids); - - if (P->vgridlist_geoid == NULL) { - P->vgridlist_geoid = pj_gridlist_from_nadgrids( - P->ctx, - pj_param(P->ctx, P->params, sgrids).s, - &(P->vgridlist_geoid_count) - ); - - if( P->vgridlist_geoid == NULL || P->vgridlist_geoid_count == 0 ) { - pj_dealloc(sgrids); - return 0; - } - } - - if (P->vgridlist_geoid_count == 0) { - proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID); - } - - pj_dealloc(sgrids); - return P->vgridlist_geoid_count; -} - -/***********************************************/ -double proj_vgrid_value(PJ *P, LP lp){ -/*********************************************** - - Read grid value at position lp in grids loaded - with proj_grid_init. - - Returns the grid value of the given coordinate. - -************************************************/ - - struct CTABLE used_grid; - double value; - memset(&used_grid, 0, sizeof(struct CTABLE)); - - value = read_vgrid_value(P, lp, &(P->vgridlist_geoid_count), P->vgridlist_geoid, &used_grid); - proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f", lp.lam*RAD_TO_DEG, lp.phi*RAD_TO_DEG, value); - - return value; -} diff --git a/src/pj_apply_vgridshift.cpp b/src/pj_apply_vgridshift.cpp new file mode 100644 index 00000000..c1344951 --- /dev/null +++ b/src/pj_apply_vgridshift.cpp @@ -0,0 +1,331 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Apply vertical datum shifts based on grid shift files, normally + * geoid grids mapping WGS84 to NAVD88 or something similar. + * 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. + *****************************************************************************/ + +#define PJ_LIB__ + +#include +#include + +#include "proj_math.h" +#include "proj_internal.h" +#include "projects.h" + +static int is_nodata(float value) +{ + /* nodata? */ + /* GTX official nodata value if -88.88880f, but some grids also */ + /* use other big values for nodata (e.g naptrans2008.gtx has */ + /* nodata values like -2147479936), so test them too */ + return value > 1000 || value < -1000 || value == -88.88880f; +} + +static double read_vgrid_value( PJ *defn, LP input, int *gridlist_count_p, PJ_GRIDINFO **tables, struct CTABLE *ct) { + int itable = 0; + double value = HUGE_VAL; + double grid_x, grid_y; + long grid_ix, grid_iy; + long grid_ix2, grid_iy2; + float *cvs; + /* do not deal with NaN coordinates */ + /* cppcheck-suppress duplicateExpression */ + if( isnan(input.phi) || isnan(input.lam) ) + itable = *gridlist_count_p; + + /* keep trying till we find a table that works */ + for ( ; itable < *gridlist_count_p; itable++ ) + { + PJ_GRIDINFO *gi = tables[itable]; + + ct = gi->ct; + + /* skip tables that don't match our point at all. */ + if( ct->ll.phi > input.phi || ct->ll.lam > input.lam + || ct->ll.phi + (ct->lim.phi-1) * ct->del.phi < input.phi + || ct->ll.lam + (ct->lim.lam-1) * ct->del.lam < input.lam ) + continue; + + /* If we have child nodes, check to see if any of them apply. */ + while( gi->child != NULL ) + { + PJ_GRIDINFO *child; + + for( child = gi->child; child != NULL; child = child->next ) + { + struct CTABLE *ct1 = child->ct; + + if( ct1->ll.phi > input.phi || ct1->ll.lam > input.lam + || ct1->ll.phi+(ct1->lim.phi-1)*ct1->del.phi < input.phi + || ct1->ll.lam+(ct1->lim.lam-1)*ct1->del.lam < input.lam) + continue; + + break; + } + + /* we didn't find a more refined child node to use, so go with current grid */ + if( child == NULL ) + { + break; + } + + /* Otherwise let's try for childrens children .. */ + gi = child; + ct = child->ct; + } + + /* load the grid shift info if we don't have it. */ + if( ct->cvs == NULL && !pj_gridinfo_load( pj_get_ctx(defn), gi ) ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + + + /* Interpolation a location within the grid */ + grid_x = (input.lam - ct->ll.lam) / ct->del.lam; + grid_y = (input.phi - ct->ll.phi) / ct->del.phi; + grid_ix = lround(floor(grid_x)); + grid_iy = lround(floor(grid_y)); + grid_x -= grid_ix; + grid_y -= grid_iy; + + grid_ix2 = grid_ix + 1; + if( grid_ix2 >= ct->lim.lam ) + grid_ix2 = ct->lim.lam - 1; + grid_iy2 = grid_iy + 1; + if( grid_iy2 >= ct->lim.phi ) + grid_iy2 = ct->lim.phi - 1; + + cvs = (float *) ct->cvs; + { + float value_a = cvs[grid_ix + grid_iy * ct->lim.lam]; + float value_b = cvs[grid_ix2 + grid_iy * ct->lim.lam]; + float value_c = cvs[grid_ix + grid_iy2 * ct->lim.lam]; + float value_d = cvs[grid_ix2 + grid_iy2 * ct->lim.lam]; + double total_weight = 0.0; + int n_weights = 0; + value = 0.0f; + if( !is_nodata(value_a) ) + { + double weight = (1.0-grid_x) * (1.0-grid_y); + value += value_a * weight; + total_weight += weight; + n_weights ++; + } + if( !is_nodata(value_b) ) + { + double weight = (grid_x) * (1.0-grid_y); + value += value_b * weight; + total_weight += weight; + n_weights ++; + } + if( !is_nodata(value_c) ) + { + double weight = (1.0-grid_x) * (grid_y); + value += value_c * weight; + total_weight += weight; + n_weights ++; + } + if( !is_nodata(value_d) ) + { + double weight = (grid_x) * (grid_y); + value += value_d * weight; + total_weight += weight; + n_weights ++; + } + if( n_weights == 0 ) + value = HUGE_VAL; + else if( n_weights != 4 ) + value /= total_weight; + } + + } + + return value; +} + +/************************************************************************/ +/* pj_apply_vgridshift() */ +/* */ +/* This implementation takes uses the gridlist from a coordinate */ +/* system definition. If the gridlist has not yet been */ +/* populated in the coordinate system definition we set it up */ +/* now. */ +/************************************************************************/ +int pj_apply_vgridshift( PJ *defn, const char *listname, + PJ_GRIDINFO ***gridlist_p, + int *gridlist_count_p, + int inverse, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + int i; + static int debug_count = 0; + PJ_GRIDINFO **tables; + struct CTABLE ct; + + if( *gridlist_p == NULL ) + { + *gridlist_p = + pj_gridlist_from_nadgrids( pj_get_ctx(defn), + pj_param(defn->ctx,defn->params,listname).s, + gridlist_count_p ); + + if( *gridlist_p == NULL || *gridlist_count_p == 0 ) + return defn->ctx->last_errno; + } + + if( *gridlist_count_p == 0 ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + + tables = *gridlist_p; + defn->ctx->last_errno = 0; + + for( i = 0; i < point_count; i++ ) + { + double value; + long io = i * point_offset; + LP input; + + input.phi = y[io]; + input.lam = x[io]; + + value = read_vgrid_value(defn, input, gridlist_count_p, tables, &ct); + + if( inverse ) + z[io] -= value; + else + z[io] += value; + if( value != HUGE_VAL ) + { + if( debug_count++ < 20 ) { + proj_log_trace(defn, "pj_apply_gridshift(): used %s", ct.id); + break; + } + } + + if( value == HUGE_VAL ) + { + int itable; + char gridlist[3000]; + + proj_log_debug(defn, + "pj_apply_vgridshift(): failed to find a grid shift table for\n" + " location (%.7fdW,%.7fdN)", + x[io] * RAD_TO_DEG, + y[io] * RAD_TO_DEG ); + + gridlist[0] = '\0'; + for( itable = 0; itable < *gridlist_count_p; itable++ ) + { + PJ_GRIDINFO *gi = tables[itable]; + if( strlen(gridlist) + strlen(gi->gridname) > sizeof(gridlist)-100 ) + { + strcat( gridlist, "..." ); + break; + } + + if( itable == 0 ) + sprintf( gridlist, " tried: %s", gi->gridname ); + else + sprintf( gridlist+strlen(gridlist), ",%s", gi->gridname ); + } + + proj_log_debug(defn, "%s", gridlist); + pj_ctx_set_errno( defn->ctx, PJD_ERR_GRID_AREA ); + + return PJD_ERR_GRID_AREA; + } + } + + return 0; +} + +/**********************************************/ +int proj_vgrid_init(PJ* P, const char *grids) { +/********************************************** + + Initizalize and populate gridlist. + + Takes a PJ-object and the plus-parameter + name that is used in the proj-string to + specify the grids to load, e.g. "+grids". + The + should be left out here. + + Returns the number of loaded grids. + +***********************************************/ + + /* prepend "s" to the "grids" string to allow usage with pj_param */ + char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); + sprintf(sgrids, "%s%s", "s", grids); + + if (P->vgridlist_geoid == NULL) { + P->vgridlist_geoid = pj_gridlist_from_nadgrids( + P->ctx, + pj_param(P->ctx, P->params, sgrids).s, + &(P->vgridlist_geoid_count) + ); + + if( P->vgridlist_geoid == NULL || P->vgridlist_geoid_count == 0 ) { + pj_dealloc(sgrids); + return 0; + } + } + + if (P->vgridlist_geoid_count == 0) { + proj_errno_set(P, PJD_ERR_FAILED_TO_LOAD_GRID); + } + + pj_dealloc(sgrids); + return P->vgridlist_geoid_count; +} + +/***********************************************/ +double proj_vgrid_value(PJ *P, LP lp){ +/*********************************************** + + Read grid value at position lp in grids loaded + with proj_grid_init. + + Returns the grid value of the given coordinate. + +************************************************/ + + struct CTABLE used_grid; + double value; + memset(&used_grid, 0, sizeof(struct CTABLE)); + + value = read_vgrid_value(P, lp, &(P->vgridlist_geoid_count), P->vgridlist_geoid, &used_grid); + proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f", lp.lam*RAD_TO_DEG, lp.phi*RAD_TO_DEG, value); + + return value; +} diff --git a/src/pj_auth.c b/src/pj_auth.c deleted file mode 100644 index d6024671..00000000 --- a/src/pj_auth.c +++ /dev/null @@ -1,36 +0,0 @@ -/* determine latitude from authalic latitude */ - -#include -#include - -#include "projects.h" - -# define P00 .33333333333333333333 /* 1 / 3 */ -# define P01 .17222222222222222222 /* 31 / 180 */ -# define P02 .10257936507936507937 /* 517 / 5040 */ -# define P10 .06388888888888888888 /* 23 / 360 */ -# define P11 .06640211640211640212 /* 251 / 3780 */ -# define P20 .01677689594356261023 /* 761 / 45360 */ -#define APA_SIZE 3 - - double * -pj_authset(double es) { - double t, *APA; - - if ((APA = (double *)pj_malloc(APA_SIZE * sizeof(double))) != NULL) { - APA[0] = es * P00; - t = es * es; - APA[0] += t * P01; - APA[1] = t * P10; - t *= es; - APA[0] += t * P02; - APA[1] += t * P11; - APA[2] = t * P20; - } - return APA; -} - double -pj_authlat(double beta, double *APA) { - double t = beta+beta; - return(beta + APA[0] * sin(t) + APA[1] * sin(t+t) + APA[2] * sin(t+t+t)); -} diff --git a/src/pj_auth.cpp b/src/pj_auth.cpp new file mode 100644 index 00000000..d6024671 --- /dev/null +++ b/src/pj_auth.cpp @@ -0,0 +1,36 @@ +/* determine latitude from authalic latitude */ + +#include +#include + +#include "projects.h" + +# define P00 .33333333333333333333 /* 1 / 3 */ +# define P01 .17222222222222222222 /* 31 / 180 */ +# define P02 .10257936507936507937 /* 517 / 5040 */ +# define P10 .06388888888888888888 /* 23 / 360 */ +# define P11 .06640211640211640212 /* 251 / 3780 */ +# define P20 .01677689594356261023 /* 761 / 45360 */ +#define APA_SIZE 3 + + double * +pj_authset(double es) { + double t, *APA; + + if ((APA = (double *)pj_malloc(APA_SIZE * sizeof(double))) != NULL) { + APA[0] = es * P00; + t = es * es; + APA[0] += t * P01; + APA[1] = t * P10; + t *= es; + APA[0] += t * P02; + APA[1] += t * P11; + APA[2] = t * P20; + } + return APA; +} + double +pj_authlat(double beta, double *APA) { + double t = beta+beta; + return(beta + APA[0] * sin(t) + APA[1] * sin(t+t) + APA[2] * sin(t+t+t)); +} diff --git a/src/pj_ctx.c b/src/pj_ctx.c deleted file mode 100644 index 1c99e921..00000000 --- a/src/pj_ctx.c +++ /dev/null @@ -1,233 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the projCtx thread context object. - * 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 - -#include "proj_internal.h" -#include "projects.h" - -static projCtx_t default_context; -static volatile int default_context_initialized = 0; - -/************************************************************************/ -/* pj_get_ctx() */ -/************************************************************************/ - -projCtx pj_get_ctx( projPJ pj ) - -{ - if (0==pj) - return pj_get_default_ctx (); - if (0==pj->ctx) - return pj_get_default_ctx (); - return pj->ctx; -} - -/************************************************************************/ -/* pj_set_ctx() */ -/* */ -/* Note we do not deallocate the old context! */ -/************************************************************************/ - -void pj_set_ctx( projPJ pj, projCtx ctx ) - -{ - if (pj==0) - return; - pj->ctx = ctx; -} - -/************************************************************************/ -/* pj_get_default_ctx() */ -/************************************************************************/ - -projCtx pj_get_default_ctx() - -{ - /* If already initialized, don't bother locking */ - if( default_context_initialized ) - return &default_context; - - pj_acquire_lock(); - - /* Ask again, since it may have been initialized in another thread */ - if( !default_context_initialized ) - { - default_context.last_errno = 0; - default_context.debug_level = PJ_LOG_NONE; - default_context.logger = pj_stderr_logger; - default_context.app_data = NULL; - default_context.fileapi = pj_get_default_fileapi(); - default_context.cpp_context = NULL; - default_context.use_proj4_init_rules = -1; - default_context.epsg_file_exists = -1; - - if( getenv("PROJ_DEBUG") != NULL ) - { - if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_DEBUG_MINOR ) - default_context.debug_level = atoi(getenv("PROJ_DEBUG")); - else - default_context.debug_level = PJ_LOG_DEBUG_MINOR; - } - default_context_initialized = 1; - } - - pj_release_lock(); - - return &default_context; -} - -/************************************************************************/ -/* pj_ctx_alloc() */ -/************************************************************************/ - -projCtx pj_ctx_alloc() - -{ - projCtx ctx = (projCtx_t *) malloc(sizeof(projCtx_t)); - if (0==ctx) - return 0; - memcpy( ctx, pj_get_default_ctx(), sizeof(projCtx_t) ); - ctx->last_errno = 0; - ctx->cpp_context = NULL; - ctx->use_proj4_init_rules = -1; - - return ctx; -} - -/************************************************************************/ -/* pj_ctx_free() */ -/************************************************************************/ - -void pj_ctx_free( projCtx ctx ) - -{ - proj_context_delete_cpp_context( ctx->cpp_context ); - pj_dealloc( ctx ); -} - -/************************************************************************/ -/* pj_ctx_get_errno() */ -/************************************************************************/ - -int pj_ctx_get_errno( projCtx ctx ) - -{ - if (0==ctx) - return pj_get_default_ctx ()->last_errno; - return ctx->last_errno; -} - -/************************************************************************/ -/* pj_ctx_set_errno() */ -/* */ -/* Also sets the global errno */ -/************************************************************************/ - -void pj_ctx_set_errno( projCtx ctx, int new_errno ) - -{ - ctx->last_errno = new_errno; - if( new_errno == 0 ) - return; - errno = new_errno; - pj_errno = new_errno; -} - -/************************************************************************/ -/* pj_ctx_set_debug() */ -/************************************************************************/ - -void pj_ctx_set_debug( projCtx ctx, int new_debug ) - -{ - if (0==ctx) - return; - ctx->debug_level = new_debug; -} - -/************************************************************************/ -/* pj_ctx_set_logger() */ -/************************************************************************/ - -void pj_ctx_set_logger( projCtx ctx, void (*new_logger)(void*,int,const char*) ) - -{ - if (0==ctx) - return; - ctx->logger = new_logger; -} - -/************************************************************************/ -/* pj_ctx_set_app_data() */ -/************************************************************************/ - -void pj_ctx_set_app_data( projCtx ctx, void *new_app_data ) - -{ - if (0==ctx) - return; - ctx->app_data = new_app_data; -} - -/************************************************************************/ -/* pj_ctx_get_app_data() */ -/************************************************************************/ - -void *pj_ctx_get_app_data( projCtx ctx ) - -{ - if (0==ctx) - return 0; - return ctx->app_data; -} - -/************************************************************************/ -/* pj_ctx_set_fileapi() */ -/************************************************************************/ - -void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi ) - -{ - if (0==ctx) - return; - ctx->fileapi = fileapi; -} - -/************************************************************************/ -/* pj_ctx_get_fileapi() */ -/************************************************************************/ - -projFileAPI *pj_ctx_get_fileapi( projCtx ctx ) - -{ - if (0==ctx) - return 0; - return ctx->fileapi; -} diff --git a/src/pj_ctx.cpp b/src/pj_ctx.cpp new file mode 100644 index 00000000..1c99e921 --- /dev/null +++ b/src/pj_ctx.cpp @@ -0,0 +1,233 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Implementation of the projCtx thread context object. + * 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 + +#include "proj_internal.h" +#include "projects.h" + +static projCtx_t default_context; +static volatile int default_context_initialized = 0; + +/************************************************************************/ +/* pj_get_ctx() */ +/************************************************************************/ + +projCtx pj_get_ctx( projPJ pj ) + +{ + if (0==pj) + return pj_get_default_ctx (); + if (0==pj->ctx) + return pj_get_default_ctx (); + return pj->ctx; +} + +/************************************************************************/ +/* pj_set_ctx() */ +/* */ +/* Note we do not deallocate the old context! */ +/************************************************************************/ + +void pj_set_ctx( projPJ pj, projCtx ctx ) + +{ + if (pj==0) + return; + pj->ctx = ctx; +} + +/************************************************************************/ +/* pj_get_default_ctx() */ +/************************************************************************/ + +projCtx pj_get_default_ctx() + +{ + /* If already initialized, don't bother locking */ + if( default_context_initialized ) + return &default_context; + + pj_acquire_lock(); + + /* Ask again, since it may have been initialized in another thread */ + if( !default_context_initialized ) + { + default_context.last_errno = 0; + default_context.debug_level = PJ_LOG_NONE; + default_context.logger = pj_stderr_logger; + default_context.app_data = NULL; + default_context.fileapi = pj_get_default_fileapi(); + default_context.cpp_context = NULL; + default_context.use_proj4_init_rules = -1; + default_context.epsg_file_exists = -1; + + if( getenv("PROJ_DEBUG") != NULL ) + { + if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_DEBUG_MINOR ) + default_context.debug_level = atoi(getenv("PROJ_DEBUG")); + else + default_context.debug_level = PJ_LOG_DEBUG_MINOR; + } + default_context_initialized = 1; + } + + pj_release_lock(); + + return &default_context; +} + +/************************************************************************/ +/* pj_ctx_alloc() */ +/************************************************************************/ + +projCtx pj_ctx_alloc() + +{ + projCtx ctx = (projCtx_t *) malloc(sizeof(projCtx_t)); + if (0==ctx) + return 0; + memcpy( ctx, pj_get_default_ctx(), sizeof(projCtx_t) ); + ctx->last_errno = 0; + ctx->cpp_context = NULL; + ctx->use_proj4_init_rules = -1; + + return ctx; +} + +/************************************************************************/ +/* pj_ctx_free() */ +/************************************************************************/ + +void pj_ctx_free( projCtx ctx ) + +{ + proj_context_delete_cpp_context( ctx->cpp_context ); + pj_dealloc( ctx ); +} + +/************************************************************************/ +/* pj_ctx_get_errno() */ +/************************************************************************/ + +int pj_ctx_get_errno( projCtx ctx ) + +{ + if (0==ctx) + return pj_get_default_ctx ()->last_errno; + return ctx->last_errno; +} + +/************************************************************************/ +/* pj_ctx_set_errno() */ +/* */ +/* Also sets the global errno */ +/************************************************************************/ + +void pj_ctx_set_errno( projCtx ctx, int new_errno ) + +{ + ctx->last_errno = new_errno; + if( new_errno == 0 ) + return; + errno = new_errno; + pj_errno = new_errno; +} + +/************************************************************************/ +/* pj_ctx_set_debug() */ +/************************************************************************/ + +void pj_ctx_set_debug( projCtx ctx, int new_debug ) + +{ + if (0==ctx) + return; + ctx->debug_level = new_debug; +} + +/************************************************************************/ +/* pj_ctx_set_logger() */ +/************************************************************************/ + +void pj_ctx_set_logger( projCtx ctx, void (*new_logger)(void*,int,const char*) ) + +{ + if (0==ctx) + return; + ctx->logger = new_logger; +} + +/************************************************************************/ +/* pj_ctx_set_app_data() */ +/************************************************************************/ + +void pj_ctx_set_app_data( projCtx ctx, void *new_app_data ) + +{ + if (0==ctx) + return; + ctx->app_data = new_app_data; +} + +/************************************************************************/ +/* pj_ctx_get_app_data() */ +/************************************************************************/ + +void *pj_ctx_get_app_data( projCtx ctx ) + +{ + if (0==ctx) + return 0; + return ctx->app_data; +} + +/************************************************************************/ +/* pj_ctx_set_fileapi() */ +/************************************************************************/ + +void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi ) + +{ + if (0==ctx) + return; + ctx->fileapi = fileapi; +} + +/************************************************************************/ +/* pj_ctx_get_fileapi() */ +/************************************************************************/ + +projFileAPI *pj_ctx_get_fileapi( projCtx ctx ) + +{ + if (0==ctx) + return 0; + return ctx->fileapi; +} diff --git a/src/pj_datum_set.c b/src/pj_datum_set.c deleted file mode 100644 index 466b56c5..00000000 --- a/src/pj_datum_set.c +++ /dev/null @@ -1,169 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Apply datum definition to PJ structure from initialization string. - * 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. - *****************************************************************************/ - -#include -#include - -#include "projects.h" - -/* SEC_TO_RAD = Pi/180/3600 */ -#define SEC_TO_RAD 4.84813681109535993589914102357e-6 - -/************************************************************************/ -/* pj_datum_set() */ -/************************************************************************/ - -int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) - -{ - const char *name, *towgs84, *nadgrids, *catalog; - - projdef->datum_type = PJD_UNKNOWN; - -/* -------------------------------------------------------------------- */ -/* Is there a datum definition in the parameters list? If so, */ -/* add the defining values to the parameter list. Note that */ -/* this will append the ellipse definition as well as the */ -/* towgs84= and related parameters. It should also be pointed */ -/* out that the addition is permanent rather than temporary */ -/* like most other keyword expansion so that the ellipse */ -/* definition will last into the pj_ell_set() function called */ -/* after this one. */ -/* -------------------------------------------------------------------- */ - if( (name = pj_param(ctx, pl,"sdatum").s) != NULL ) - { - paralist *curr; - const char *s; - int i; - - /* find the end of the list, so we can add to it */ - for (curr = pl; curr && curr->next ; curr = curr->next) {} - - /* cannot happen in practice, but makes static analyzers happy */ - if( !curr ) return -1; - - /* find the datum definition */ - for (i = 0; (s = pj_datums[i].id) && strcmp(name, s) ; ++i) {} - - if (!s) { - pj_ctx_set_errno(ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); - return 1; - } - - if( pj_datums[i].ellipse_id && strlen(pj_datums[i].ellipse_id) > 0 ) - { - char entry[100]; - - strcpy( entry, "ellps=" ); - strncpy( entry + strlen(entry), pj_datums[i].ellipse_id, - sizeof(entry) - 1 - strlen(entry) ); - entry[ sizeof(entry) - 1 ] = '\0'; - - curr = curr->next = pj_mkparam(entry); - } - - if( pj_datums[i].defn && strlen(pj_datums[i].defn) > 0 ) - curr = curr->next = pj_mkparam(pj_datums[i].defn); - - (void)curr; /* make clang static analyzer happy */ - } - -/* -------------------------------------------------------------------- */ -/* Check for nadgrids parameter. */ -/* -------------------------------------------------------------------- */ - nadgrids = pj_param(ctx, pl,"snadgrids").s; - if( nadgrids != NULL ) - { - /* We don't actually save the value separately. It will continue - to exist int he param list for use in pj_apply_gridshift.c */ - - projdef->datum_type = PJD_GRIDSHIFT; - } - -/* -------------------------------------------------------------------- */ -/* Check for grid catalog parameter, and optional date. */ -/* -------------------------------------------------------------------- */ - else if( (catalog = pj_param(ctx, pl,"scatalog").s) != NULL ) - { - const char *date; - - projdef->datum_type = PJD_GRIDSHIFT; - projdef->catalog_name = pj_strdup(catalog); - if (!projdef->catalog_name) { - pj_ctx_set_errno(ctx, ENOMEM); - return 1; - } - - date = pj_param(ctx, pl, "sdate").s; - if( date != NULL) - projdef->datum_date = pj_gc_parsedate( ctx, date); - } - -/* -------------------------------------------------------------------- */ -/* Check for towgs84 parameter. */ -/* -------------------------------------------------------------------- */ - else if( (towgs84 = pj_param(ctx, pl,"stowgs84").s) != NULL ) - { - int parm_count = 0; - const char *s; - - memset( projdef->datum_params, 0, sizeof(double) * 7); - - /* parse out the parameters */ - for( s = towgs84; *s != '\0' && parm_count < 7; ) - { - projdef->datum_params[parm_count++] = pj_atof(s); - while( *s != '\0' && *s != ',' ) - s++; - if( *s == ',' ) - s++; - } - - if( projdef->datum_params[3] != 0.0 - || projdef->datum_params[4] != 0.0 - || projdef->datum_params[5] != 0.0 - || projdef->datum_params[6] != 0.0 ) - { - projdef->datum_type = PJD_7PARAM; - - /* transform from arc seconds to radians */ - projdef->datum_params[3] *= SEC_TO_RAD; - projdef->datum_params[4] *= SEC_TO_RAD; - projdef->datum_params[5] *= SEC_TO_RAD; - /* transform from parts per million to scaling factor */ - projdef->datum_params[6] = - (projdef->datum_params[6]/1000000.0) + 1; - } - else - projdef->datum_type = PJD_3PARAM; - - /* Note that pj_init() will later switch datum_type to - PJD_WGS84 if shifts are all zero, and ellipsoid is WGS84 or GRS80 */ - } - - return 0; -} diff --git a/src/pj_datum_set.cpp b/src/pj_datum_set.cpp new file mode 100644 index 00000000..466b56c5 --- /dev/null +++ b/src/pj_datum_set.cpp @@ -0,0 +1,169 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Apply datum definition to PJ structure from initialization string. + * 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. + *****************************************************************************/ + +#include +#include + +#include "projects.h" + +/* SEC_TO_RAD = Pi/180/3600 */ +#define SEC_TO_RAD 4.84813681109535993589914102357e-6 + +/************************************************************************/ +/* pj_datum_set() */ +/************************************************************************/ + +int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) + +{ + const char *name, *towgs84, *nadgrids, *catalog; + + projdef->datum_type = PJD_UNKNOWN; + +/* -------------------------------------------------------------------- */ +/* Is there a datum definition in the parameters list? If so, */ +/* add the defining values to the parameter list. Note that */ +/* this will append the ellipse definition as well as the */ +/* towgs84= and related parameters. It should also be pointed */ +/* out that the addition is permanent rather than temporary */ +/* like most other keyword expansion so that the ellipse */ +/* definition will last into the pj_ell_set() function called */ +/* after this one. */ +/* -------------------------------------------------------------------- */ + if( (name = pj_param(ctx, pl,"sdatum").s) != NULL ) + { + paralist *curr; + const char *s; + int i; + + /* find the end of the list, so we can add to it */ + for (curr = pl; curr && curr->next ; curr = curr->next) {} + + /* cannot happen in practice, but makes static analyzers happy */ + if( !curr ) return -1; + + /* find the datum definition */ + for (i = 0; (s = pj_datums[i].id) && strcmp(name, s) ; ++i) {} + + if (!s) { + pj_ctx_set_errno(ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); + return 1; + } + + if( pj_datums[i].ellipse_id && strlen(pj_datums[i].ellipse_id) > 0 ) + { + char entry[100]; + + strcpy( entry, "ellps=" ); + strncpy( entry + strlen(entry), pj_datums[i].ellipse_id, + sizeof(entry) - 1 - strlen(entry) ); + entry[ sizeof(entry) - 1 ] = '\0'; + + curr = curr->next = pj_mkparam(entry); + } + + if( pj_datums[i].defn && strlen(pj_datums[i].defn) > 0 ) + curr = curr->next = pj_mkparam(pj_datums[i].defn); + + (void)curr; /* make clang static analyzer happy */ + } + +/* -------------------------------------------------------------------- */ +/* Check for nadgrids parameter. */ +/* -------------------------------------------------------------------- */ + nadgrids = pj_param(ctx, pl,"snadgrids").s; + if( nadgrids != NULL ) + { + /* We don't actually save the value separately. It will continue + to exist int he param list for use in pj_apply_gridshift.c */ + + projdef->datum_type = PJD_GRIDSHIFT; + } + +/* -------------------------------------------------------------------- */ +/* Check for grid catalog parameter, and optional date. */ +/* -------------------------------------------------------------------- */ + else if( (catalog = pj_param(ctx, pl,"scatalog").s) != NULL ) + { + const char *date; + + projdef->datum_type = PJD_GRIDSHIFT; + projdef->catalog_name = pj_strdup(catalog); + if (!projdef->catalog_name) { + pj_ctx_set_errno(ctx, ENOMEM); + return 1; + } + + date = pj_param(ctx, pl, "sdate").s; + if( date != NULL) + projdef->datum_date = pj_gc_parsedate( ctx, date); + } + +/* -------------------------------------------------------------------- */ +/* Check for towgs84 parameter. */ +/* -------------------------------------------------------------------- */ + else if( (towgs84 = pj_param(ctx, pl,"stowgs84").s) != NULL ) + { + int parm_count = 0; + const char *s; + + memset( projdef->datum_params, 0, sizeof(double) * 7); + + /* parse out the parameters */ + for( s = towgs84; *s != '\0' && parm_count < 7; ) + { + projdef->datum_params[parm_count++] = pj_atof(s); + while( *s != '\0' && *s != ',' ) + s++; + if( *s == ',' ) + s++; + } + + if( projdef->datum_params[3] != 0.0 + || projdef->datum_params[4] != 0.0 + || projdef->datum_params[5] != 0.0 + || projdef->datum_params[6] != 0.0 ) + { + projdef->datum_type = PJD_7PARAM; + + /* transform from arc seconds to radians */ + projdef->datum_params[3] *= SEC_TO_RAD; + projdef->datum_params[4] *= SEC_TO_RAD; + projdef->datum_params[5] *= SEC_TO_RAD; + /* transform from parts per million to scaling factor */ + projdef->datum_params[6] = + (projdef->datum_params[6]/1000000.0) + 1; + } + else + projdef->datum_type = PJD_3PARAM; + + /* Note that pj_init() will later switch datum_type to + PJD_WGS84 if shifts are all zero, and ellipsoid is WGS84 or GRS80 */ + } + + return 0; +} diff --git a/src/pj_datums.c b/src/pj_datums.c deleted file mode 100644 index 2951b7bd..00000000 --- a/src/pj_datums.c +++ /dev/null @@ -1,99 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Built in datum list. - * 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. - *****************************************************************************/ - -#include - -#include "proj.h" - -#define PJ_DATUMS__ -#include "projects.h" - -/* - * The ellipse code must match one from pj_ellps.c. The datum id should - * be kept to 12 characters or less if possible. Use the official OGC - * datum name for the comments if available. - */ - -C_NAMESPACE_VAR const struct PJ_DATUMS pj_datums[] = { -/* id definition ellipse comments */ -/* -- ---------- ------- -------- */ -{"WGS84", "towgs84=0,0,0", "WGS84", ""}, -{"GGRS87", "towgs84=-199.87,74.79,246.62", "GRS80", - "Greek_Geodetic_Reference_System_1987"}, -{"NAD83", "towgs84=0,0,0", "GRS80", - "North_American_Datum_1983"}, -{"NAD27", "nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", - "clrk66", - "North_American_Datum_1927"}, -{"potsdam", /*"towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7",*/ - "nadgrids=@BETA2007.gsb", - "bessel", - "Potsdam Rauenberg 1950 DHDN"}, -{"carthage","towgs84=-263.0,6.0,431.0", "clrk80ign", - "Carthage 1934 Tunisia"}, -{"hermannskogel", "towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232", - "bessel", - "Hermannskogel"}, -{"ire65", "towgs84=482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", - "mod_airy", - "Ireland 1965"}, -{"nzgd49", "towgs84=59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", - "intl", "New Zealand Geodetic Datum 1949"}, -{"OSGB36", "towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", - "airy", "Airy 1830"}, -{NULL, NULL, NULL, NULL} -}; - -struct PJ_DATUMS *pj_get_datums_ref() -{ - return (struct PJ_DATUMS *)pj_datums; -} - -static const struct PJ_PRIME_MERIDIANS pj_prime_meridians[] = { - /* id definition */ - /* -- ---------- */ - {"greenwich", "0dE"}, - {"lisbon", "9d07'54.862\"W"}, - {"paris", "2d20'14.025\"E"}, - {"bogota", "74d04'51.3\"W"}, - {"madrid", "3d41'16.58\"W"}, - {"rome", "12d27'8.4\"E"}, - {"bern", "7d26'22.5\"E"}, - {"jakarta", "106d48'27.79\"E"}, - {"ferro", "17d40'W"}, - {"brussels", "4d22'4.71\"E"}, - {"stockholm", "18d3'29.8\"E"}, - {"athens", "23d42'58.815\"E"}, - {"oslo", "10d43'22.5\"E"}, - {"copenhagen","12d34'40.35\"E"}, - {NULL, NULL} -}; - -const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void) -{ - return pj_prime_meridians; -} diff --git a/src/pj_datums.cpp b/src/pj_datums.cpp new file mode 100644 index 00000000..2951b7bd --- /dev/null +++ b/src/pj_datums.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Built in datum list. + * 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. + *****************************************************************************/ + +#include + +#include "proj.h" + +#define PJ_DATUMS__ +#include "projects.h" + +/* + * The ellipse code must match one from pj_ellps.c. The datum id should + * be kept to 12 characters or less if possible. Use the official OGC + * datum name for the comments if available. + */ + +C_NAMESPACE_VAR const struct PJ_DATUMS pj_datums[] = { +/* id definition ellipse comments */ +/* -- ---------- ------- -------- */ +{"WGS84", "towgs84=0,0,0", "WGS84", ""}, +{"GGRS87", "towgs84=-199.87,74.79,246.62", "GRS80", + "Greek_Geodetic_Reference_System_1987"}, +{"NAD83", "towgs84=0,0,0", "GRS80", + "North_American_Datum_1983"}, +{"NAD27", "nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", + "clrk66", + "North_American_Datum_1927"}, +{"potsdam", /*"towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7",*/ + "nadgrids=@BETA2007.gsb", + "bessel", + "Potsdam Rauenberg 1950 DHDN"}, +{"carthage","towgs84=-263.0,6.0,431.0", "clrk80ign", + "Carthage 1934 Tunisia"}, +{"hermannskogel", "towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232", + "bessel", + "Hermannskogel"}, +{"ire65", "towgs84=482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", + "mod_airy", + "Ireland 1965"}, +{"nzgd49", "towgs84=59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", + "intl", "New Zealand Geodetic Datum 1949"}, +{"OSGB36", "towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", + "airy", "Airy 1830"}, +{NULL, NULL, NULL, NULL} +}; + +struct PJ_DATUMS *pj_get_datums_ref() +{ + return (struct PJ_DATUMS *)pj_datums; +} + +static const struct PJ_PRIME_MERIDIANS pj_prime_meridians[] = { + /* id definition */ + /* -- ---------- */ + {"greenwich", "0dE"}, + {"lisbon", "9d07'54.862\"W"}, + {"paris", "2d20'14.025\"E"}, + {"bogota", "74d04'51.3\"W"}, + {"madrid", "3d41'16.58\"W"}, + {"rome", "12d27'8.4\"E"}, + {"bern", "7d26'22.5\"E"}, + {"jakarta", "106d48'27.79\"E"}, + {"ferro", "17d40'W"}, + {"brussels", "4d22'4.71\"E"}, + {"stockholm", "18d3'29.8\"E"}, + {"athens", "23d42'58.815\"E"}, + {"oslo", "10d43'22.5\"E"}, + {"copenhagen","12d34'40.35\"E"}, + {NULL, NULL} +}; + +const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void) +{ + return pj_prime_meridians; +} diff --git a/src/pj_deriv.c b/src/pj_deriv.c deleted file mode 100644 index 2c91e0cc..00000000 --- a/src/pj_deriv.c +++ /dev/null @@ -1,70 +0,0 @@ -/* dervative of (*P->fwd) projection */ -#define PJ_LIB__ - -#include - -#include "projects.h" - -int pj_deriv(LP lp, double h, const PJ *P, struct DERIVS *der) { - XY t; - /* get rid of constness until we can do it for real */ - PJ *Q = (PJ *) P; - if (0==Q->fwd) - return 1; - - lp.lam += h; - lp.phi += h; - if (fabs(lp.phi) > M_HALFPI) - return 1; - - h += h; - t = (*Q->fwd)(lp, Q); - if (t.x == HUGE_VAL) - return 1; - - der->x_l = t.x; - der->y_p = t.y; - der->x_p = t.x; - der->y_l = t.y; - - lp.phi -= h; - if (fabs(lp.phi) > M_HALFPI) - return 1; - - t = (*Q->fwd)(lp, Q); - if (t.x == HUGE_VAL) - return 1; - - der->x_l += t.x; - der->y_p -= t.y; - der->x_p -= t.x; - der->y_l += t.y; - - lp.lam -= h; - t = (*Q->fwd)(lp, Q); - if (t.x == HUGE_VAL) - return 1; - - der->x_l -= t.x; - der->y_p -= t.y; - der->x_p -= t.x; - der->y_l -= t.y; - - lp.phi += h; - t = (*Q->fwd)(lp, Q); - if (t.x == HUGE_VAL) - return 1; - - der->x_l -= t.x; - der->y_p += t.y; - der->x_p += t.x; - der->y_l -= t.y; - - h += h; - der->x_l /= h; - der->y_p /= h; - der->x_p /= h; - der->y_l /= h; - - return 0; -} diff --git a/src/pj_deriv.cpp b/src/pj_deriv.cpp new file mode 100644 index 00000000..2c91e0cc --- /dev/null +++ b/src/pj_deriv.cpp @@ -0,0 +1,70 @@ +/* dervative of (*P->fwd) projection */ +#define PJ_LIB__ + +#include + +#include "projects.h" + +int pj_deriv(LP lp, double h, const PJ *P, struct DERIVS *der) { + XY t; + /* get rid of constness until we can do it for real */ + PJ *Q = (PJ *) P; + if (0==Q->fwd) + return 1; + + lp.lam += h; + lp.phi += h; + if (fabs(lp.phi) > M_HALFPI) + return 1; + + h += h; + t = (*Q->fwd)(lp, Q); + if (t.x == HUGE_VAL) + return 1; + + der->x_l = t.x; + der->y_p = t.y; + der->x_p = t.x; + der->y_l = t.y; + + lp.phi -= h; + if (fabs(lp.phi) > M_HALFPI) + return 1; + + t = (*Q->fwd)(lp, Q); + if (t.x == HUGE_VAL) + return 1; + + der->x_l += t.x; + der->y_p -= t.y; + der->x_p -= t.x; + der->y_l += t.y; + + lp.lam -= h; + t = (*Q->fwd)(lp, Q); + if (t.x == HUGE_VAL) + return 1; + + der->x_l -= t.x; + der->y_p -= t.y; + der->x_p -= t.x; + der->y_l -= t.y; + + lp.phi += h; + t = (*Q->fwd)(lp, Q); + if (t.x == HUGE_VAL) + return 1; + + der->x_l -= t.x; + der->y_p += t.y; + der->x_p += t.x; + der->y_l -= t.y; + + h += h; + der->x_l /= h; + der->y_p /= h; + der->x_p /= h; + der->y_l /= h; + + return 0; +} diff --git a/src/pj_ell_set.c b/src/pj_ell_set.c deleted file mode 100644 index e2d2750c..00000000 --- a/src/pj_ell_set.c +++ /dev/null @@ -1,727 +0,0 @@ -/* set ellipsoid parameters a and es */ - -#include -#include -#include - -#include "proj.h" -#include "proj_internal.h" -#include "projects.h" - - -/* Prototypes of the pj_ellipsoid helper functions */ -static int ellps_ellps (PJ *P); -static int ellps_size (PJ *P); -static int ellps_shape (PJ *P); -static int ellps_spherification (PJ *P); - -static paralist *pj_get_param (paralist *list, char *key); -static char *pj_param_value (paralist *list); -static const PJ_ELLPS *pj_find_ellps (char *name); - - -/***************************************************************************************/ -int pj_ellipsoid (PJ *P) { -/**************************************************************************************** - This is a replacement for the classic PROJ pj_ell_set function. The main difference - is that pj_ellipsoid augments the PJ object with a copy of the exact tags used to - define its related ellipsoid. - - This makes it possible to let a new PJ object inherit the geometrical properties - of an existing one. - - A complete ellipsoid definition comprises a size (primary) and a shape (secondary) - parameter. - - Size parameters supported are: - R, defining the radius of a spherical planet - a, defining the semimajor axis of an ellipsoidal planet - - Shape parameters supported are: - rf, the reverse flattening of the ellipsoid - f, the flattening of the ellipsoid - es, the eccentricity squared - e, the eccentricity - b, the semiminor axis - - The ellps=xxx parameter provides both size and shape for a number of built in - ellipsoid definitions. - - The ellipsoid definition may be augmented with a spherification flag, turning - the ellipsoid into a sphere with features defined by the ellipsoid. - - Spherification parameters supported are: - R_A, which gives a sphere with the same surface area as the ellipsoid - R_A, which gives a sphere with the same volume as the ellipsoid - - R_a, which gives a sphere with R = (a + b)/2 (arithmetic mean) - R_g, which gives a sphere with R = sqrt(a*b) (geometric mean) - R_h, which gives a sphere with R = 2*a*b/(a+b) (harmonic mean) - - R_lat_a=phi, which gives a sphere with R being the arithmetic mean of - of the corresponding ellipsoid at latitude phi. - R_lat_g=phi, which gives a sphere with R being the geometric mean of - of the corresponding ellipsoid at latitude phi. - - If R is given as size parameter, any shape and spherification parameters - given are ignored. - - If size and shape are given as ellps=xxx, later shape and size parameters - are are taken into account as modifiers for the built in ellipsoid definition. - - While this may seem strange, it is in accordance with historical PROJ - behaviour. It can e.g. be used to define coordinates on the ellipsoid - scaled to unit semimajor axis by specifying "+ellps=xxx +a=1" - -****************************************************************************************/ - int err = proj_errno_reset (P); - char *empty = {""}; - - P->def_size = P->def_shape = P->def_spherification = P->def_ellps = 0; - - /* Specifying R overrules everything */ - if (pj_get_param (P->params, "R")) { - ellps_size (P); - pj_calc_ellipsoid_params (P, P->a, 0); - if (proj_errno (P)) - return 1; - return proj_errno_restore (P, err); - } - - - /* If an ellps argument is specified, start by using that */ - if (0 != ellps_ellps (P)) - return 1; - - /* We may overwrite the size */ - if (0 != ellps_size (P)) - return 2; - - /* We may also overwrite the shape */ - if (0 != ellps_shape (P)) - return 3; - - /* When we're done with it, we compute all related ellipsoid parameters */ - pj_calc_ellipsoid_params (P, P->a, P->es); - - /* And finally, we may turn it into a sphere */ - if (0 != ellps_spherification (P)) - return 4; - - proj_log_debug (P, "pj_ellipsoid - final: a=%.3f f=1/%7.3f, errno=%d", - P->a, P->f!=0? 1/P->f: 0, proj_errno (P)); - proj_log_debug (P, "pj_ellipsoid - final: %s %s %s %s", - P->def_size? P->def_size: empty, - P->def_shape? P->def_shape: empty, - P->def_spherification? P->def_spherification: empty, - P->def_ellps? P->def_ellps: empty ); - - if (proj_errno (P)) - return 5; - - /* success */ - return proj_errno_restore (P, err); -} - - -/***************************************************************************************/ -static int ellps_ellps (PJ *P) { -/***************************************************************************************/ - PJ B; - const PJ_ELLPS *ellps; - paralist *par = 0; - char *name; - int err; - - /* Sail home if ellps=xxx is not specified */ - par = pj_get_param (P->params, "ellps"); - if (0==par) - return 0; - - /* Otherwise produce a fake PJ to make ellps_size/ellps_shape do the hard work for us */ - - /* First move B into P's context to get error messages onto the right channel */ - B.ctx = P->ctx; - - /* Then look up the right size and shape parameters from the builtin list */ - if (strlen (par->param) < 7) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - name = par->param + 6; - ellps = pj_find_ellps (name); - if (0==ellps) - return proj_errno_set (P, PJD_ERR_UNKNOWN_ELLP_PARAM); - - /* Now, get things ready for ellps_size/ellps_shape, make them do their thing, and clean up */ - err = proj_errno_reset (P); - B = *P; - pj_erase_ellipsoid_def (&B); - B.params = pj_mkparam (ellps->major); - B.params->next = pj_mkparam (ellps->ell); - - ellps_size (&B); - ellps_shape (&B); - - pj_dealloc (B.params->next); - pj_dealloc (B.params); - if (proj_errno (&B)) - return proj_errno (&B); - - /* Finally update P and sail home */ - pj_inherit_ellipsoid_def (&B, P); - P->def_ellps = par->param; - par->used = 1; - - return proj_errno_restore (P, err); -} - - -/***************************************************************************************/ -static int ellps_size (PJ *P) { -/***************************************************************************************/ - paralist *par = 0; - int a_was_set = 0; - - /* A size parameter *must* be given, but may have been given as ellps prior */ - if (P->a != 0) - a_was_set = 1; - - /* Check which size key is specified */ - par = pj_get_param (P->params, "R"); - if (0==par) - par = pj_get_param (P->params, "a"); - if (0==par) - return a_was_set? 0: proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); - - P->def_size = par->param; - par->used = 1; - P->a = pj_atof (pj_param_value (par)); - if (P->a <= 0) - return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); - if (HUGE_VAL==P->a) - return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); - - if ('R'==par->param[0]) { - P->es = P->f = P->e = P->rf = 0; - P->b = P->a; - } - return 0; -} - - -/***************************************************************************************/ -static int ellps_shape (PJ *P) { -/***************************************************************************************/ - char *keys[] = {"rf", "f", "es", "e", "b"}; - paralist *par = 0; - char *def = 0; - size_t i, len; - - par = 0; - len = sizeof (keys) / sizeof (char *); - - /* Check which shape key is specified */ - for (i = 0; i < len; i++) { - par = pj_get_param (P->params, keys[i]); - if (par) - break; - } - - /* Not giving a shape parameter means selecting a sphere, unless shape */ - /* has been selected previously via ellps=xxx */ - if (0==par && P->es != 0) - return 0; - if (0==par && P->es==0) { - P->es = P->f = 0; - P->b = P->a; - return 0; - } - - P->def_shape = def = par->param; - par->used = 1; - P->es = P->f = P->b = P->e = P->rf = 0; - - switch (i) { - - /* reverse flattening, rf */ - case 0: - P->rf = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->rf) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (0==P->rf) - return proj_errno_set (P, PJD_ERR_REV_FLATTENING_IS_ZERO); - P->f = 1 / P->rf; - P->es = 2*P->f - P->f*P->f; - break; - - /* flattening, f */ - case 1: - P->f = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->f) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - - P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; - P->es = 2*P->f - P->f*P->f; - break; - - /* eccentricity squared, es */ - case 2: - P->es = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->es) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (1==P->es) - return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); - break; - - /* eccentricity, e */ - case 3: - P->e = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->e) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (0==P->e) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (1==P->e) - return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); - P->es = P->e * P->e; - break; - - /* semiminor axis, b */ - case 4: - P->b = pj_atof (pj_param_value (par)); - if (HUGE_VAL==P->b) - return proj_errno_set (P, PJD_ERR_INVALID_ARG); - if (0==P->b) - return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); - if (P->b==P->a) - break; - P->f = (P->a - P->b) / P->a; - P->es = 2*P->f - P->f*P->f; - break; - default: - return PJD_ERR_INVALID_ARG; - - } - - if (P->es < 0) - return proj_errno_set (P, PJD_ERR_ES_LESS_THAN_ZERO); - return 0; -} - - -/* series coefficients for calculating ellipsoid-equivalent spheres */ -static const double SIXTH = 1/6.; -static const double RA4 = 17/360.; -static const double RA6 = 67/3024.; -static const double RV4 = 5/72.; -static const double RV6 = 55/1296.; - -/***************************************************************************************/ -static int ellps_spherification (PJ *P) { -/***************************************************************************************/ - char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g"}; - size_t len, i; - paralist *par = 0; - char *def = 0; - - double t; - char *v, *endp; - - len = sizeof (keys) / sizeof (char *); - P->def_spherification = 0; - - /* Check which spherification key is specified */ - for (i = 0; i < len; i++) { - par = pj_get_param (P->params, keys[i]); - if (par) - break; - } - - /* No spherification specified? Then we're done */ - if (i==len) - return 0; - - /* Store definition */ - P->def_spherification = def = par->param; - par->used = 1; - - switch (i) { - - /* R_A - a sphere with same area as ellipsoid */ - case 0: - P->a *= 1. - P->es * (SIXTH + P->es * (RA4 + P->es * RA6)); - break; - - /* R_V - a sphere with same volume as ellipsoid */ - case 1: - P->a *= 1. - P->es * (SIXTH + P->es * (RV4 + P->es * RV6)); - break; - - /* R_a - a sphere with R = the arithmetic mean of the ellipsoid */ - case 2: - P->a = (P->a + P->b) / 2; - break; - - /* R_g - a sphere with R = the geometric mean of the ellipsoid */ - case 3: - P->a = sqrt (P->a * P->b); - break; - - /* R_h - a sphere with R = the harmonic mean of the ellipsoid */ - case 4: - if (P->a + P->b == 0) - return proj_errno_set (P, PJD_ERR_TOLERANCE_CONDITION); - P->a = (2*P->a * P->b) / (P->a + P->b); - break; - - /* R_lat_a - a sphere with R = the arithmetic mean of the ellipsoid at given latitude */ - case 5: - /* R_lat_g - a sphere with R = the geometric mean of the ellipsoid at given latitude */ - case 6: - v = pj_param_value (par); - t = proj_dmstor (v, &endp); - if (fabs (t) > M_HALFPI) - return proj_errno_set (P, PJD_ERR_REF_RAD_LARGER_THAN_90); - t = sin (t); - t = 1 - P->es * t * t; - if (i==5) /* arithmetic */ - P->a *= (1. - P->es + t) / (2 * t * sqrt(t)); - else /* geometric */ - P->a *= sqrt (1 - P->es) / t; - break; - } - - /* Clean up the ellipsoidal parameters to reflect the sphere */ - P->es = P->e = P->f = 0; - P->rf = HUGE_VAL; - P->b = P->a; - pj_calc_ellipsoid_params (P, P->a, 0); - - return 0; -} - - -/* locate parameter in list */ -static paralist *pj_get_param (paralist *list, char *key) { - size_t l = strlen(key); - while (list && !(0==strncmp(list->param, key, l) && (0==list->param[l] || list->param[l] == '=') ) ) - list = list->next; - return list; -} - - -static char *pj_param_value (paralist *list) { - char *key, *value; - if (0==list) - return 0; - - key = list->param; - value = strchr (key, '='); - - /* a flag (i.e. a key without value) has its own name (key) as value */ - return value? value + 1: key; -} - - -static const PJ_ELLPS *pj_find_ellps (char *name) { - int i; - const char *s; - const PJ_ELLPS *ellps; - - if (0==name) - return 0; - - ellps = proj_list_ellps(); - - /* Search through internal ellipsoid list for name */ - for (i = 0; (s = ellps[i].id) && strcmp(name, s) ; ++i); - if (0==s) - return 0; - return ellps + i; -} - - -/**************************************************************************************/ -void pj_erase_ellipsoid_def (PJ *P) { -/*************************************************************************************** - Erase all ellipsoidal parameters in P -***************************************************************************************/ - PJ B; - - /* Make a blank PJ to copy from */ - memset (&B, 0, sizeof (B)); - - /* And use it to overwrite all existing ellipsoid defs */ - pj_inherit_ellipsoid_def (&B, P); -} - - -/**************************************************************************************/ -void pj_inherit_ellipsoid_def (const PJ *src, PJ *dst) { -/*************************************************************************************** - Brute force copy the ellipsoidal parameters from src to dst. This code was - written before the actual ellipsoid setup parameters were kept available in - the PJ->def_xxx elements. -***************************************************************************************/ - - /* The linear parameters */ - dst->a = src->a; - dst->b = src->b; - dst->ra = src->ra; - dst->rb = src->rb; - - /* The eccentricities */ - dst->alpha = src->alpha; - dst->e = src->e; - dst->es = src->es; - dst->e2 = src->e2; - dst->e2s = src->e2s; - dst->e3 = src->e3; - dst->e3s = src->e3s; - dst->one_es = src->one_es; - dst->rone_es = src->rone_es; - - /* The flattenings */ - dst->f = src->f; - dst->f2 = src->f2; - dst->n = src->n; - dst->rf = src->rf; - dst->rf2 = src->rf2; - dst->rn = src->rn; - - /* This one's for GRS80 */ - dst->J = src->J; - - /* es and a before any +proj related adjustment */ - dst->es_orig = src->es_orig; - dst->a_orig = src->a_orig; -} - - -/***************************************************************************************/ -int pj_calc_ellipsoid_params (PJ *P, double a, double es) { -/**************************************************************************************** - Calculate a large number of ancillary ellipsoidal parameters, in addition to - the two traditional PROJ defining parameters: Semimajor axis, a, and the - eccentricity squared, es. - - Most of these parameters are fairly cheap to compute in comparison to the overall - effort involved in initializing a PJ object. They may, however, take a substantial - part of the time taken in computing an individual point transformation. - - So by providing them up front, we can amortize the (already modest) cost over all - transformations carried out over the entire lifetime of a PJ object, rather than - incur that cost for every single transformation. - - Most of the parameter calculations here are based on the "angular eccentricity", - i.e. the angle, measured from the semiminor axis, of a line going from the north - pole to one of the foci of the ellipsoid - or in other words: The arc sine of the - eccentricity. - - The formulae used are mostly taken from: - - Richard H. Rapp: Geometric Geodesy, Part I, (178 pp, 1991). - Columbus, Ohio: Dept. of Geodetic Science - and Surveying, Ohio State University. - -****************************************************************************************/ - - P->a = a; - P->es = es; - - /* Compute some ancillary ellipsoidal parameters */ - if (P->e==0) - P->e = sqrt(P->es); /* eccentricity */ - P->alpha = asin (P->e); /* angular eccentricity */ - - /* second eccentricity */ - P->e2 = tan (P->alpha); - P->e2s = P->e2 * P->e2; - - /* third eccentricity */ - P->e3 = (0!=P->alpha)? sin (P->alpha) / sqrt(2 - sin (P->alpha)*sin (P->alpha)): 0; - P->e3s = P->e3 * P->e3; - - /* flattening */ - if (0==P->f) - P->f = 1 - cos (P->alpha); /* = 1 - sqrt (1 - PIN->es); */ - P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; - - /* second flattening */ - P->f2 = (cos(P->alpha)!=0)? 1/cos (P->alpha) - 1: 0; - P->rf2 = P->f2 != 0.0 ? 1/P->f2: HUGE_VAL; - - /* third flattening */ - P->n = pow (tan (P->alpha/2), 2); - P->rn = P->n != 0.0 ? 1/P->n: HUGE_VAL; - - /* ...and a few more */ - if (0==P->b) - P->b = (1 - P->f)*P->a; - P->rb = 1. / P->b; - P->ra = 1. / P->a; - - P->one_es = 1. - P->es; - if (P->one_es == 0.) { - pj_ctx_set_errno( P->ctx, PJD_ERR_ECCENTRICITY_IS_ONE); - return PJD_ERR_ECCENTRICITY_IS_ONE; - } - - P->rone_es = 1./P->one_es; - - return 0; -} - - - -#ifndef KEEP_ORIGINAL_PJ_ELL_SET -/**************************************************************************************/ -int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { -/*************************************************************************************** - Initialize ellipsoidal parameters by emulating the original ellipsoid setup - function by Gerald Evenden, through a call to pj_ellipsoid -***************************************************************************************/ - PJ B; - int ret; - - memset (&B, 0, sizeof (B)); - B.ctx = ctx; - B.params = pl; - - ret = pj_ellipsoid (&B); - if (ret) - return ret; - - *a = B.a; - *es = B.es; - return 0; -} -#else - - -/**************************************************************************************/ -int pj_ell_set (projCtx ctx, paralist *pl, double *a, double *es) { -/*************************************************************************************** - Initialize ellipsoidal parameters: This is the original ellipsoid setup - function by Gerald Evenden - significantly more compact than pj_ellipsoid and - its many helper functions, and still quite readable. - - It is, however, also so tight that it is hard to modify and add functionality, - and equally hard to find the right place to add further commentary for improved - future maintainability. - - Hence, when the need to store in the PJ object, the parameters actually used to - define the ellipsoid came up, rather than modifying this little gem of - "economy of expression", a much more verbose reimplementation, pj_ellipsoid, - was written. -***************************************************************************************/ - int i; - double b=0.0, e; - char *name; - paralist *start = 0; - - /* clear any previous error */ - pj_ctx_set_errno(ctx,0); - - /* check for varying forms of ellipsoid input */ - *a = *es = 0.; - - /* R takes precedence */ - if (pj_param(ctx, pl, "tR").i) - *a = pj_param(ctx,pl, "dR").f; - - /* probable elliptical figure */ - else { - /* check if ellps present and temporarily append its values to pl */ - if ((name = pj_param(ctx,pl, "sellps").s) != NULL) { - char *s; - - for (start = pl; start && start->next ; start = start->next) ; - for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ; - if (!s) { - pj_ctx_set_errno( ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); - return 1; - } - start->next = pj_mkparam(pj_ellps[i].major); - start->next->next = pj_mkparam(pj_ellps[i].ell); - } - - *a = pj_param(ctx,pl, "da").f; - - if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */ - *es = pj_param(ctx,pl, "des").f; - else if (pj_param(ctx,pl, "te").i) { /* eccentricity */ - e = pj_param(ctx,pl, "de").f; - *es = e * e; - } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */ - *es = pj_param(ctx,pl, "drf").f; - if (*es == 0.0) { - pj_ctx_set_errno(ctx, PJD_ERR_REV_FLATTENING_IS_ZERO); - goto bomb; - } - *es = 1./ *es; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tf").i) { /* flattening */ - *es = pj_param(ctx,pl, "df").f; - *es = *es * (2. - *es); - } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */ - b = pj_param(ctx,pl, "db").f; - *es = 1. - (b * b) / (*a * *a); - } /* else *es == 0. and sphere of radius *a */ - if (b == 0.0) - b = *a * sqrt(1. - *es); - - - /* following options turn ellipsoid into equivalent sphere */ - if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */ - *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6)); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */ - *a = .5 * (*a + b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */ - *a = sqrt(*a * b); - *es = 0.; - } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */ - if ( (*a + b) == 0.0) { - pj_ctx_set_errno(ctx, PJD_ERR_TOLERANCE_CONDITION); - goto bomb; - } - *a = 2. * *a * b / (*a + b); - *es = 0.; - } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */ - pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */ - double tmp; - - tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f); - if (fabs(tmp) > M_HALFPI) { - pj_ctx_set_errno(ctx, PJD_ERR_REF_RAD_LARGER_THAN_90); - goto bomb; - } - tmp = 1. - *es * tmp * tmp; - *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) : - sqrt(1. - *es) / tmp; - *es = 0.; - } -bomb: - if (start) { /* clean up temporary extension of list */ - pj_dalloc(start->next->next); - pj_dalloc(start->next); - start->next = 0; - } - if (ctx->last_errno) - return 1; - } - /* some remaining checks */ - if (*es < 0.) { - pj_ctx_set_errno(ctx, PJD_ERR_ES_LESS_THAN_ZERO); - return 1; - } - if (*a <= 0.) { - pj_ctx_set_errno(ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); - return 1; - } - return 0; -} -#endif diff --git a/src/pj_ell_set.cpp b/src/pj_ell_set.cpp new file mode 100644 index 00000000..e2d2750c --- /dev/null +++ b/src/pj_ell_set.cpp @@ -0,0 +1,727 @@ +/* set ellipsoid parameters a and es */ + +#include +#include +#include + +#include "proj.h" +#include "proj_internal.h" +#include "projects.h" + + +/* Prototypes of the pj_ellipsoid helper functions */ +static int ellps_ellps (PJ *P); +static int ellps_size (PJ *P); +static int ellps_shape (PJ *P); +static int ellps_spherification (PJ *P); + +static paralist *pj_get_param (paralist *list, char *key); +static char *pj_param_value (paralist *list); +static const PJ_ELLPS *pj_find_ellps (char *name); + + +/***************************************************************************************/ +int pj_ellipsoid (PJ *P) { +/**************************************************************************************** + This is a replacement for the classic PROJ pj_ell_set function. The main difference + is that pj_ellipsoid augments the PJ object with a copy of the exact tags used to + define its related ellipsoid. + + This makes it possible to let a new PJ object inherit the geometrical properties + of an existing one. + + A complete ellipsoid definition comprises a size (primary) and a shape (secondary) + parameter. + + Size parameters supported are: + R, defining the radius of a spherical planet + a, defining the semimajor axis of an ellipsoidal planet + + Shape parameters supported are: + rf, the reverse flattening of the ellipsoid + f, the flattening of the ellipsoid + es, the eccentricity squared + e, the eccentricity + b, the semiminor axis + + The ellps=xxx parameter provides both size and shape for a number of built in + ellipsoid definitions. + + The ellipsoid definition may be augmented with a spherification flag, turning + the ellipsoid into a sphere with features defined by the ellipsoid. + + Spherification parameters supported are: + R_A, which gives a sphere with the same surface area as the ellipsoid + R_A, which gives a sphere with the same volume as the ellipsoid + + R_a, which gives a sphere with R = (a + b)/2 (arithmetic mean) + R_g, which gives a sphere with R = sqrt(a*b) (geometric mean) + R_h, which gives a sphere with R = 2*a*b/(a+b) (harmonic mean) + + R_lat_a=phi, which gives a sphere with R being the arithmetic mean of + of the corresponding ellipsoid at latitude phi. + R_lat_g=phi, which gives a sphere with R being the geometric mean of + of the corresponding ellipsoid at latitude phi. + + If R is given as size parameter, any shape and spherification parameters + given are ignored. + + If size and shape are given as ellps=xxx, later shape and size parameters + are are taken into account as modifiers for the built in ellipsoid definition. + + While this may seem strange, it is in accordance with historical PROJ + behaviour. It can e.g. be used to define coordinates on the ellipsoid + scaled to unit semimajor axis by specifying "+ellps=xxx +a=1" + +****************************************************************************************/ + int err = proj_errno_reset (P); + char *empty = {""}; + + P->def_size = P->def_shape = P->def_spherification = P->def_ellps = 0; + + /* Specifying R overrules everything */ + if (pj_get_param (P->params, "R")) { + ellps_size (P); + pj_calc_ellipsoid_params (P, P->a, 0); + if (proj_errno (P)) + return 1; + return proj_errno_restore (P, err); + } + + + /* If an ellps argument is specified, start by using that */ + if (0 != ellps_ellps (P)) + return 1; + + /* We may overwrite the size */ + if (0 != ellps_size (P)) + return 2; + + /* We may also overwrite the shape */ + if (0 != ellps_shape (P)) + return 3; + + /* When we're done with it, we compute all related ellipsoid parameters */ + pj_calc_ellipsoid_params (P, P->a, P->es); + + /* And finally, we may turn it into a sphere */ + if (0 != ellps_spherification (P)) + return 4; + + proj_log_debug (P, "pj_ellipsoid - final: a=%.3f f=1/%7.3f, errno=%d", + P->a, P->f!=0? 1/P->f: 0, proj_errno (P)); + proj_log_debug (P, "pj_ellipsoid - final: %s %s %s %s", + P->def_size? P->def_size: empty, + P->def_shape? P->def_shape: empty, + P->def_spherification? P->def_spherification: empty, + P->def_ellps? P->def_ellps: empty ); + + if (proj_errno (P)) + return 5; + + /* success */ + return proj_errno_restore (P, err); +} + + +/***************************************************************************************/ +static int ellps_ellps (PJ *P) { +/***************************************************************************************/ + PJ B; + const PJ_ELLPS *ellps; + paralist *par = 0; + char *name; + int err; + + /* Sail home if ellps=xxx is not specified */ + par = pj_get_param (P->params, "ellps"); + if (0==par) + return 0; + + /* Otherwise produce a fake PJ to make ellps_size/ellps_shape do the hard work for us */ + + /* First move B into P's context to get error messages onto the right channel */ + B.ctx = P->ctx; + + /* Then look up the right size and shape parameters from the builtin list */ + if (strlen (par->param) < 7) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + name = par->param + 6; + ellps = pj_find_ellps (name); + if (0==ellps) + return proj_errno_set (P, PJD_ERR_UNKNOWN_ELLP_PARAM); + + /* Now, get things ready for ellps_size/ellps_shape, make them do their thing, and clean up */ + err = proj_errno_reset (P); + B = *P; + pj_erase_ellipsoid_def (&B); + B.params = pj_mkparam (ellps->major); + B.params->next = pj_mkparam (ellps->ell); + + ellps_size (&B); + ellps_shape (&B); + + pj_dealloc (B.params->next); + pj_dealloc (B.params); + if (proj_errno (&B)) + return proj_errno (&B); + + /* Finally update P and sail home */ + pj_inherit_ellipsoid_def (&B, P); + P->def_ellps = par->param; + par->used = 1; + + return proj_errno_restore (P, err); +} + + +/***************************************************************************************/ +static int ellps_size (PJ *P) { +/***************************************************************************************/ + paralist *par = 0; + int a_was_set = 0; + + /* A size parameter *must* be given, but may have been given as ellps prior */ + if (P->a != 0) + a_was_set = 1; + + /* Check which size key is specified */ + par = pj_get_param (P->params, "R"); + if (0==par) + par = pj_get_param (P->params, "a"); + if (0==par) + return a_was_set? 0: proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + + P->def_size = par->param; + par->used = 1; + P->a = pj_atof (pj_param_value (par)); + if (P->a <= 0) + return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + if (HUGE_VAL==P->a) + return proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + + if ('R'==par->param[0]) { + P->es = P->f = P->e = P->rf = 0; + P->b = P->a; + } + return 0; +} + + +/***************************************************************************************/ +static int ellps_shape (PJ *P) { +/***************************************************************************************/ + char *keys[] = {"rf", "f", "es", "e", "b"}; + paralist *par = 0; + char *def = 0; + size_t i, len; + + par = 0; + len = sizeof (keys) / sizeof (char *); + + /* Check which shape key is specified */ + for (i = 0; i < len; i++) { + par = pj_get_param (P->params, keys[i]); + if (par) + break; + } + + /* Not giving a shape parameter means selecting a sphere, unless shape */ + /* has been selected previously via ellps=xxx */ + if (0==par && P->es != 0) + return 0; + if (0==par && P->es==0) { + P->es = P->f = 0; + P->b = P->a; + return 0; + } + + P->def_shape = def = par->param; + par->used = 1; + P->es = P->f = P->b = P->e = P->rf = 0; + + switch (i) { + + /* reverse flattening, rf */ + case 0: + P->rf = pj_atof (pj_param_value (par)); + if (HUGE_VAL==P->rf) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (0==P->rf) + return proj_errno_set (P, PJD_ERR_REV_FLATTENING_IS_ZERO); + P->f = 1 / P->rf; + P->es = 2*P->f - P->f*P->f; + break; + + /* flattening, f */ + case 1: + P->f = pj_atof (pj_param_value (par)); + if (HUGE_VAL==P->f) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + + P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; + P->es = 2*P->f - P->f*P->f; + break; + + /* eccentricity squared, es */ + case 2: + P->es = pj_atof (pj_param_value (par)); + if (HUGE_VAL==P->es) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (1==P->es) + return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); + break; + + /* eccentricity, e */ + case 3: + P->e = pj_atof (pj_param_value (par)); + if (HUGE_VAL==P->e) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (0==P->e) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (1==P->e) + return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); + P->es = P->e * P->e; + break; + + /* semiminor axis, b */ + case 4: + P->b = pj_atof (pj_param_value (par)); + if (HUGE_VAL==P->b) + return proj_errno_set (P, PJD_ERR_INVALID_ARG); + if (0==P->b) + return proj_errno_set (P, PJD_ERR_ECCENTRICITY_IS_ONE); + if (P->b==P->a) + break; + P->f = (P->a - P->b) / P->a; + P->es = 2*P->f - P->f*P->f; + break; + default: + return PJD_ERR_INVALID_ARG; + + } + + if (P->es < 0) + return proj_errno_set (P, PJD_ERR_ES_LESS_THAN_ZERO); + return 0; +} + + +/* series coefficients for calculating ellipsoid-equivalent spheres */ +static const double SIXTH = 1/6.; +static const double RA4 = 17/360.; +static const double RA6 = 67/3024.; +static const double RV4 = 5/72.; +static const double RV6 = 55/1296.; + +/***************************************************************************************/ +static int ellps_spherification (PJ *P) { +/***************************************************************************************/ + char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g"}; + size_t len, i; + paralist *par = 0; + char *def = 0; + + double t; + char *v, *endp; + + len = sizeof (keys) / sizeof (char *); + P->def_spherification = 0; + + /* Check which spherification key is specified */ + for (i = 0; i < len; i++) { + par = pj_get_param (P->params, keys[i]); + if (par) + break; + } + + /* No spherification specified? Then we're done */ + if (i==len) + return 0; + + /* Store definition */ + P->def_spherification = def = par->param; + par->used = 1; + + switch (i) { + + /* R_A - a sphere with same area as ellipsoid */ + case 0: + P->a *= 1. - P->es * (SIXTH + P->es * (RA4 + P->es * RA6)); + break; + + /* R_V - a sphere with same volume as ellipsoid */ + case 1: + P->a *= 1. - P->es * (SIXTH + P->es * (RV4 + P->es * RV6)); + break; + + /* R_a - a sphere with R = the arithmetic mean of the ellipsoid */ + case 2: + P->a = (P->a + P->b) / 2; + break; + + /* R_g - a sphere with R = the geometric mean of the ellipsoid */ + case 3: + P->a = sqrt (P->a * P->b); + break; + + /* R_h - a sphere with R = the harmonic mean of the ellipsoid */ + case 4: + if (P->a + P->b == 0) + return proj_errno_set (P, PJD_ERR_TOLERANCE_CONDITION); + P->a = (2*P->a * P->b) / (P->a + P->b); + break; + + /* R_lat_a - a sphere with R = the arithmetic mean of the ellipsoid at given latitude */ + case 5: + /* R_lat_g - a sphere with R = the geometric mean of the ellipsoid at given latitude */ + case 6: + v = pj_param_value (par); + t = proj_dmstor (v, &endp); + if (fabs (t) > M_HALFPI) + return proj_errno_set (P, PJD_ERR_REF_RAD_LARGER_THAN_90); + t = sin (t); + t = 1 - P->es * t * t; + if (i==5) /* arithmetic */ + P->a *= (1. - P->es + t) / (2 * t * sqrt(t)); + else /* geometric */ + P->a *= sqrt (1 - P->es) / t; + break; + } + + /* Clean up the ellipsoidal parameters to reflect the sphere */ + P->es = P->e = P->f = 0; + P->rf = HUGE_VAL; + P->b = P->a; + pj_calc_ellipsoid_params (P, P->a, 0); + + return 0; +} + + +/* locate parameter in list */ +static paralist *pj_get_param (paralist *list, char *key) { + size_t l = strlen(key); + while (list && !(0==strncmp(list->param, key, l) && (0==list->param[l] || list->param[l] == '=') ) ) + list = list->next; + return list; +} + + +static char *pj_param_value (paralist *list) { + char *key, *value; + if (0==list) + return 0; + + key = list->param; + value = strchr (key, '='); + + /* a flag (i.e. a key without value) has its own name (key) as value */ + return value? value + 1: key; +} + + +static const PJ_ELLPS *pj_find_ellps (char *name) { + int i; + const char *s; + const PJ_ELLPS *ellps; + + if (0==name) + return 0; + + ellps = proj_list_ellps(); + + /* Search through internal ellipsoid list for name */ + for (i = 0; (s = ellps[i].id) && strcmp(name, s) ; ++i); + if (0==s) + return 0; + return ellps + i; +} + + +/**************************************************************************************/ +void pj_erase_ellipsoid_def (PJ *P) { +/*************************************************************************************** + Erase all ellipsoidal parameters in P +***************************************************************************************/ + PJ B; + + /* Make a blank PJ to copy from */ + memset (&B, 0, sizeof (B)); + + /* And use it to overwrite all existing ellipsoid defs */ + pj_inherit_ellipsoid_def (&B, P); +} + + +/**************************************************************************************/ +void pj_inherit_ellipsoid_def (const PJ *src, PJ *dst) { +/*************************************************************************************** + Brute force copy the ellipsoidal parameters from src to dst. This code was + written before the actual ellipsoid setup parameters were kept available in + the PJ->def_xxx elements. +***************************************************************************************/ + + /* The linear parameters */ + dst->a = src->a; + dst->b = src->b; + dst->ra = src->ra; + dst->rb = src->rb; + + /* The eccentricities */ + dst->alpha = src->alpha; + dst->e = src->e; + dst->es = src->es; + dst->e2 = src->e2; + dst->e2s = src->e2s; + dst->e3 = src->e3; + dst->e3s = src->e3s; + dst->one_es = src->one_es; + dst->rone_es = src->rone_es; + + /* The flattenings */ + dst->f = src->f; + dst->f2 = src->f2; + dst->n = src->n; + dst->rf = src->rf; + dst->rf2 = src->rf2; + dst->rn = src->rn; + + /* This one's for GRS80 */ + dst->J = src->J; + + /* es and a before any +proj related adjustment */ + dst->es_orig = src->es_orig; + dst->a_orig = src->a_orig; +} + + +/***************************************************************************************/ +int pj_calc_ellipsoid_params (PJ *P, double a, double es) { +/**************************************************************************************** + Calculate a large number of ancillary ellipsoidal parameters, in addition to + the two traditional PROJ defining parameters: Semimajor axis, a, and the + eccentricity squared, es. + + Most of these parameters are fairly cheap to compute in comparison to the overall + effort involved in initializing a PJ object. They may, however, take a substantial + part of the time taken in computing an individual point transformation. + + So by providing them up front, we can amortize the (already modest) cost over all + transformations carried out over the entire lifetime of a PJ object, rather than + incur that cost for every single transformation. + + Most of the parameter calculations here are based on the "angular eccentricity", + i.e. the angle, measured from the semiminor axis, of a line going from the north + pole to one of the foci of the ellipsoid - or in other words: The arc sine of the + eccentricity. + + The formulae used are mostly taken from: + + Richard H. Rapp: Geometric Geodesy, Part I, (178 pp, 1991). + Columbus, Ohio: Dept. of Geodetic Science + and Surveying, Ohio State University. + +****************************************************************************************/ + + P->a = a; + P->es = es; + + /* Compute some ancillary ellipsoidal parameters */ + if (P->e==0) + P->e = sqrt(P->es); /* eccentricity */ + P->alpha = asin (P->e); /* angular eccentricity */ + + /* second eccentricity */ + P->e2 = tan (P->alpha); + P->e2s = P->e2 * P->e2; + + /* third eccentricity */ + P->e3 = (0!=P->alpha)? sin (P->alpha) / sqrt(2 - sin (P->alpha)*sin (P->alpha)): 0; + P->e3s = P->e3 * P->e3; + + /* flattening */ + if (0==P->f) + P->f = 1 - cos (P->alpha); /* = 1 - sqrt (1 - PIN->es); */ + P->rf = P->f != 0.0 ? 1.0/P->f: HUGE_VAL; + + /* second flattening */ + P->f2 = (cos(P->alpha)!=0)? 1/cos (P->alpha) - 1: 0; + P->rf2 = P->f2 != 0.0 ? 1/P->f2: HUGE_VAL; + + /* third flattening */ + P->n = pow (tan (P->alpha/2), 2); + P->rn = P->n != 0.0 ? 1/P->n: HUGE_VAL; + + /* ...and a few more */ + if (0==P->b) + P->b = (1 - P->f)*P->a; + P->rb = 1. / P->b; + P->ra = 1. / P->a; + + P->one_es = 1. - P->es; + if (P->one_es == 0.) { + pj_ctx_set_errno( P->ctx, PJD_ERR_ECCENTRICITY_IS_ONE); + return PJD_ERR_ECCENTRICITY_IS_ONE; + } + + P->rone_es = 1./P->one_es; + + return 0; +} + + + +#ifndef KEEP_ORIGINAL_PJ_ELL_SET +/**************************************************************************************/ +int pj_ell_set (PJ_CONTEXT *ctx, paralist *pl, double *a, double *es) { +/*************************************************************************************** + Initialize ellipsoidal parameters by emulating the original ellipsoid setup + function by Gerald Evenden, through a call to pj_ellipsoid +***************************************************************************************/ + PJ B; + int ret; + + memset (&B, 0, sizeof (B)); + B.ctx = ctx; + B.params = pl; + + ret = pj_ellipsoid (&B); + if (ret) + return ret; + + *a = B.a; + *es = B.es; + return 0; +} +#else + + +/**************************************************************************************/ +int pj_ell_set (projCtx ctx, paralist *pl, double *a, double *es) { +/*************************************************************************************** + Initialize ellipsoidal parameters: This is the original ellipsoid setup + function by Gerald Evenden - significantly more compact than pj_ellipsoid and + its many helper functions, and still quite readable. + + It is, however, also so tight that it is hard to modify and add functionality, + and equally hard to find the right place to add further commentary for improved + future maintainability. + + Hence, when the need to store in the PJ object, the parameters actually used to + define the ellipsoid came up, rather than modifying this little gem of + "economy of expression", a much more verbose reimplementation, pj_ellipsoid, + was written. +***************************************************************************************/ + int i; + double b=0.0, e; + char *name; + paralist *start = 0; + + /* clear any previous error */ + pj_ctx_set_errno(ctx,0); + + /* check for varying forms of ellipsoid input */ + *a = *es = 0.; + + /* R takes precedence */ + if (pj_param(ctx, pl, "tR").i) + *a = pj_param(ctx,pl, "dR").f; + + /* probable elliptical figure */ + else { + /* check if ellps present and temporarily append its values to pl */ + if ((name = pj_param(ctx,pl, "sellps").s) != NULL) { + char *s; + + for (start = pl; start && start->next ; start = start->next) ; + for (i = 0; (s = pj_ellps[i].id) && strcmp(name, s) ; ++i) ; + if (!s) { + pj_ctx_set_errno( ctx, PJD_ERR_UNKNOWN_ELLP_PARAM); + return 1; + } + start->next = pj_mkparam(pj_ellps[i].major); + start->next->next = pj_mkparam(pj_ellps[i].ell); + } + + *a = pj_param(ctx,pl, "da").f; + + if (pj_param(ctx,pl, "tes").i) /* eccentricity squared */ + *es = pj_param(ctx,pl, "des").f; + else if (pj_param(ctx,pl, "te").i) { /* eccentricity */ + e = pj_param(ctx,pl, "de").f; + *es = e * e; + } else if (pj_param(ctx,pl, "trf").i) { /* recip flattening */ + *es = pj_param(ctx,pl, "drf").f; + if (*es == 0.0) { + pj_ctx_set_errno(ctx, PJD_ERR_REV_FLATTENING_IS_ZERO); + goto bomb; + } + *es = 1./ *es; + *es = *es * (2. - *es); + } else if (pj_param(ctx,pl, "tf").i) { /* flattening */ + *es = pj_param(ctx,pl, "df").f; + *es = *es * (2. - *es); + } else if (pj_param(ctx,pl, "tb").i) { /* minor axis */ + b = pj_param(ctx,pl, "db").f; + *es = 1. - (b * b) / (*a * *a); + } /* else *es == 0. and sphere of radius *a */ + if (b == 0.0) + b = *a * sqrt(1. - *es); + + + /* following options turn ellipsoid into equivalent sphere */ + if (pj_param(ctx,pl, "bR_A").i) { /* sphere--area of ellipsoid */ + *a *= 1. - *es * (SIXTH + *es * (RA4 + *es * RA6)); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_V").i) { /* sphere--vol. of ellipsoid */ + *a *= 1. - *es * (SIXTH + *es * (RV4 + *es * RV6)); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_a").i) { /* sphere--arithmetic mean */ + *a = .5 * (*a + b); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_g").i) { /* sphere--geometric mean */ + *a = sqrt(*a * b); + *es = 0.; + } else if (pj_param(ctx,pl, "bR_h").i) { /* sphere--harmonic mean */ + if ( (*a + b) == 0.0) { + pj_ctx_set_errno(ctx, PJD_ERR_TOLERANCE_CONDITION); + goto bomb; + } + *a = 2. * *a * b / (*a + b); + *es = 0.; + } else if ((i = pj_param(ctx,pl, "tR_lat_a").i) || /* sphere--arith. */ + pj_param(ctx,pl, "tR_lat_g").i) { /* or geom. mean at latitude */ + double tmp; + + tmp = sin(pj_param(ctx,pl, i ? "rR_lat_a" : "rR_lat_g").f); + if (fabs(tmp) > M_HALFPI) { + pj_ctx_set_errno(ctx, PJD_ERR_REF_RAD_LARGER_THAN_90); + goto bomb; + } + tmp = 1. - *es * tmp * tmp; + *a *= i ? .5 * (1. - *es + tmp) / ( tmp * sqrt(tmp)) : + sqrt(1. - *es) / tmp; + *es = 0.; + } +bomb: + if (start) { /* clean up temporary extension of list */ + pj_dalloc(start->next->next); + pj_dalloc(start->next); + start->next = 0; + } + if (ctx->last_errno) + return 1; + } + /* some remaining checks */ + if (*es < 0.) { + pj_ctx_set_errno(ctx, PJD_ERR_ES_LESS_THAN_ZERO); + return 1; + } + if (*a <= 0.) { + pj_ctx_set_errno(ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); + return 1; + } + return 0; +} +#endif diff --git a/src/pj_ellps.c b/src/pj_ellps.c deleted file mode 100644 index 8b3b8f0a..00000000 --- a/src/pj_ellps.c +++ /dev/null @@ -1,62 +0,0 @@ -/* definition of standard geoids */ - -#include - -#include "proj.h" -#include "projects.h" - -static const struct PJ_ELLPS -pj_ellps[] = { -{"MERIT", "a=6378137.0", "rf=298.257", "MERIT 1983"}, -{"SGS85", "a=6378136.0", "rf=298.257", "Soviet Geodetic System 85"}, -{"GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"}, -{"IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"}, -{"airy", "a=6377563.396", "rf=299.3249646", "Airy 1830"}, -{"APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"}, -{"NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"}, -{"mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"}, -{"andrae", "a=6377104.43", "rf=300.0", "Andrae 1876 (Den., Iclnd.)"}, -{"danish", "a=6377019.2563", "rf=300.0", "Andrae 1876 (Denmark, Iceland)"}, -{"aust_SA", "a=6378160.0", "rf=298.25", "Australian Natl & S. Amer. 1969"}, -{"GRS67", "a=6378160.0", "rf=298.2471674270", "GRS 67(IUGG 1967)"}, -{"GSK2011", "a=6378136.5", "rf=298.2564151", "GSK-2011"}, -{"bessel", "a=6377397.155", "rf=299.1528128", "Bessel 1841"}, -{"bess_nam", "a=6377483.865", "rf=299.1528128", "Bessel 1841 (Namibia)"}, -{"clrk66", "a=6378206.4", "b=6356583.8", "Clarke 1866"}, -{"clrk80", "a=6378249.145", "rf=293.4663", "Clarke 1880 mod."}, -{"clrk80ign", "a=6378249.2", "rf=293.4660212936269", "Clarke 1880 (IGN)."}, -{"CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"}, -{"delmbr", "a=6376428.", "rf=311.5", "Delambre 1810 (Belgium)"}, -{"engelis", "a=6378136.05", "rf=298.2566", "Engelis 1985"}, -{"evrst30", "a=6377276.345", "rf=300.8017", "Everest 1830"}, -{"evrst48", "a=6377304.063", "rf=300.8017", "Everest 1948"}, -{"evrst56", "a=6377301.243", "rf=300.8017", "Everest 1956"}, -{"evrst69", "a=6377295.664", "rf=300.8017", "Everest 1969"}, -{"evrstSS", "a=6377298.556", "rf=300.8017", "Everest (Sabah & Sarawak)"}, -{"fschr60", "a=6378166.", "rf=298.3", "Fischer (Mercury Datum) 1960"}, -{"fschr60m", "a=6378155.", "rf=298.3", "Modified Fischer 1960"}, -{"fschr68", "a=6378150.", "rf=298.3", "Fischer 1968"}, -{"helmert", "a=6378200.", "rf=298.3", "Helmert 1906"}, -{"hough", "a=6378270.0", "rf=297.", "Hough"}, -{"intl", "a=6378388.0", "rf=297.", "International 1909 (Hayford)"}, -{"krass", "a=6378245.0", "rf=298.3", "Krassovsky, 1942"}, -{"kaula", "a=6378163.", "rf=298.24", "Kaula 1961"}, -{"lerch", "a=6378139.", "rf=298.257", "Lerch 1979"}, -{"mprts", "a=6397300.", "rf=191.", "Maupertius 1738"}, -{"new_intl", "a=6378157.5", "b=6356772.2", "New International 1967"}, -{"plessis", "a=6376523.", "b=6355863.", "Plessis 1817 (France)"}, -{"PZ90", "a=6378136.0", "rf=298.25784", "PZ-90"}, -{"SEasia", "a=6378155.0", "b=6356773.3205", "Southeast Asia"}, -{"walbeck", "a=6376896.0", "b=6355834.8467", "Walbeck"}, -{"WGS60", "a=6378165.0", "rf=298.3", "WGS 60"}, -{"WGS66", "a=6378145.0", "rf=298.25", "WGS 66"}, -{"WGS72", "a=6378135.0", "rf=298.26", "WGS 72"}, -{"WGS84", "a=6378137.0", "rf=298.257223563", "WGS 84"}, -{"sphere", "a=6370997.0", "b=6370997.0", "Normal Sphere (r=6370997)"}, -{NULL, NULL, NULL, NULL} -}; - -const PJ_ELLPS *proj_list_ellps(void) -{ - return pj_ellps; -} diff --git a/src/pj_ellps.cpp b/src/pj_ellps.cpp new file mode 100644 index 00000000..8b3b8f0a --- /dev/null +++ b/src/pj_ellps.cpp @@ -0,0 +1,62 @@ +/* definition of standard geoids */ + +#include + +#include "proj.h" +#include "projects.h" + +static const struct PJ_ELLPS +pj_ellps[] = { +{"MERIT", "a=6378137.0", "rf=298.257", "MERIT 1983"}, +{"SGS85", "a=6378136.0", "rf=298.257", "Soviet Geodetic System 85"}, +{"GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"}, +{"IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"}, +{"airy", "a=6377563.396", "rf=299.3249646", "Airy 1830"}, +{"APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"}, +{"NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"}, +{"mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"}, +{"andrae", "a=6377104.43", "rf=300.0", "Andrae 1876 (Den., Iclnd.)"}, +{"danish", "a=6377019.2563", "rf=300.0", "Andrae 1876 (Denmark, Iceland)"}, +{"aust_SA", "a=6378160.0", "rf=298.25", "Australian Natl & S. Amer. 1969"}, +{"GRS67", "a=6378160.0", "rf=298.2471674270", "GRS 67(IUGG 1967)"}, +{"GSK2011", "a=6378136.5", "rf=298.2564151", "GSK-2011"}, +{"bessel", "a=6377397.155", "rf=299.1528128", "Bessel 1841"}, +{"bess_nam", "a=6377483.865", "rf=299.1528128", "Bessel 1841 (Namibia)"}, +{"clrk66", "a=6378206.4", "b=6356583.8", "Clarke 1866"}, +{"clrk80", "a=6378249.145", "rf=293.4663", "Clarke 1880 mod."}, +{"clrk80ign", "a=6378249.2", "rf=293.4660212936269", "Clarke 1880 (IGN)."}, +{"CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"}, +{"delmbr", "a=6376428.", "rf=311.5", "Delambre 1810 (Belgium)"}, +{"engelis", "a=6378136.05", "rf=298.2566", "Engelis 1985"}, +{"evrst30", "a=6377276.345", "rf=300.8017", "Everest 1830"}, +{"evrst48", "a=6377304.063", "rf=300.8017", "Everest 1948"}, +{"evrst56", "a=6377301.243", "rf=300.8017", "Everest 1956"}, +{"evrst69", "a=6377295.664", "rf=300.8017", "Everest 1969"}, +{"evrstSS", "a=6377298.556", "rf=300.8017", "Everest (Sabah & Sarawak)"}, +{"fschr60", "a=6378166.", "rf=298.3", "Fischer (Mercury Datum) 1960"}, +{"fschr60m", "a=6378155.", "rf=298.3", "Modified Fischer 1960"}, +{"fschr68", "a=6378150.", "rf=298.3", "Fischer 1968"}, +{"helmert", "a=6378200.", "rf=298.3", "Helmert 1906"}, +{"hough", "a=6378270.0", "rf=297.", "Hough"}, +{"intl", "a=6378388.0", "rf=297.", "International 1909 (Hayford)"}, +{"krass", "a=6378245.0", "rf=298.3", "Krassovsky, 1942"}, +{"kaula", "a=6378163.", "rf=298.24", "Kaula 1961"}, +{"lerch", "a=6378139.", "rf=298.257", "Lerch 1979"}, +{"mprts", "a=6397300.", "rf=191.", "Maupertius 1738"}, +{"new_intl", "a=6378157.5", "b=6356772.2", "New International 1967"}, +{"plessis", "a=6376523.", "b=6355863.", "Plessis 1817 (France)"}, +{"PZ90", "a=6378136.0", "rf=298.25784", "PZ-90"}, +{"SEasia", "a=6378155.0", "b=6356773.3205", "Southeast Asia"}, +{"walbeck", "a=6376896.0", "b=6355834.8467", "Walbeck"}, +{"WGS60", "a=6378165.0", "rf=298.3", "WGS 60"}, +{"WGS66", "a=6378145.0", "rf=298.25", "WGS 66"}, +{"WGS72", "a=6378135.0", "rf=298.26", "WGS 72"}, +{"WGS84", "a=6378137.0", "rf=298.257223563", "WGS 84"}, +{"sphere", "a=6370997.0", "b=6370997.0", "Normal Sphere (r=6370997)"}, +{NULL, NULL, NULL, NULL} +}; + +const PJ_ELLPS *proj_list_ellps(void) +{ + return pj_ellps; +} diff --git a/src/pj_errno.c b/src/pj_errno.c deleted file mode 100644 index 6e98cd73..00000000 --- a/src/pj_errno.c +++ /dev/null @@ -1,17 +0,0 @@ -/* For full ANSI compliance of global variable */ - -#include "projects.h" - -C_NAMESPACE_VAR int pj_errno = 0; - -/************************************************************************/ -/* pj_get_errno_ref() */ -/************************************************************************/ - -int *pj_get_errno_ref() - -{ - return &pj_errno; -} - -/* end */ diff --git a/src/pj_errno.cpp b/src/pj_errno.cpp new file mode 100644 index 00000000..6e98cd73 --- /dev/null +++ b/src/pj_errno.cpp @@ -0,0 +1,17 @@ +/* For full ANSI compliance of global variable */ + +#include "projects.h" + +C_NAMESPACE_VAR int pj_errno = 0; + +/************************************************************************/ +/* pj_get_errno_ref() */ +/************************************************************************/ + +int *pj_get_errno_ref() + +{ + return &pj_errno; +} + +/* end */ diff --git a/src/pj_factors.c b/src/pj_factors.c deleted file mode 100644 index e4b871a1..00000000 --- a/src/pj_factors.c +++ /dev/null @@ -1,107 +0,0 @@ -/* projection scale factors */ -#define PJ_LIB__ -#include "proj.h" -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -#include - -#ifndef DEFAULT_H -#define DEFAULT_H 1e-5 /* radian default for numeric h */ -#endif - -#define EPS 1.0e-12 - -int pj_factors(LP lp, const PJ *P, double h, struct FACTORS *fac) { - double cosphi, t, n, r; - int err; - PJ_COORD coo = {{0, 0, 0, 0}}; - coo.lp = lp; - - /* Failing the 3 initial checks will most likely be due to */ - /* earlier errors, so we leave errno alone */ - if (0==fac) - return 1; - - if (0==P) - return 1; - - if (HUGE_VAL==lp.lam) - return 1; - - /* But from here, we're ready to make our own mistakes */ - err = proj_errno_reset (P); - - /* Indicate that all factors are numerical approximations */ - fac->code = 0; - - /* Check for latitude or longitude overange */ - if ((fabs (lp.phi)-M_HALFPI) > EPS || fabs (lp.lam) > 10.) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - return 1; - } - - /* Set a reasonable step size for the numerical derivatives */ - h = fabs (h); - if (h < EPS) - h = DEFAULT_H; - - /* If input latitudes are geocentric, convert to geographic */ - if (P->geoc) - lp = pj_geocentric_latitude (P, PJ_INV, coo).lp; - - /* If latitude + one step overshoots the pole, move it slightly inside, */ - /* so the numerical derivative still exists */ - if (fabs (lp.phi) > (M_HALFPI - h)) - lp.phi = lp.phi < 0. ? -(M_HALFPI-h) : (M_HALFPI-h); - - /* Longitudinal distance from central meridian */ - lp.lam -= P->lam0; - if (!P->over) - lp.lam = adjlon(lp.lam); - - /* Derivatives */ - if (pj_deriv (lp, h, P, &(fac->der))) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - return 1; - } - - /* Scale factors */ - cosphi = cos (lp.phi); - fac->h = hypot (fac->der.x_p, fac->der.y_p); - fac->k = hypot (fac->der.x_l, fac->der.y_l) / cosphi; - - if (P->es != 0.0) { - t = sin(lp.phi); - t = 1. - P->es * t * t; - n = sqrt(t); - fac->h *= t * n / P->one_es; - fac->k *= n; - r = t * t / P->one_es; - } else - r = 1.; - - /* Convergence */ - fac->conv = -atan2 (fac->der.x_p, fac->der.y_p); - - /* Areal scale factor */ - fac->s = (fac->der.y_p * fac->der.x_l - fac->der.x_p * fac->der.y_l) * r / cosphi; - - /* Meridian-parallel angle (theta prime) */ - fac->thetap = aasin(P->ctx,fac->s / (fac->h * fac->k)); - - /* Tissot ellipse axis */ - t = fac->k * fac->k + fac->h * fac->h; - fac->a = sqrt(t + 2. * fac->s); - t = t - 2. * fac->s; - t = t > 0? sqrt(t): 0; - fac->b = 0.5 * (fac->a - t); - fac->a = 0.5 * (fac->a + t); - - /* Angular distortion */ - fac->omega = 2. * aasin(P->ctx, (fac->a - fac->b) / (fac->a + fac->b) ); - - proj_errno_restore (P, err); - return 0; -} diff --git a/src/pj_factors.cpp b/src/pj_factors.cpp new file mode 100644 index 00000000..e4b871a1 --- /dev/null +++ b/src/pj_factors.cpp @@ -0,0 +1,107 @@ +/* projection scale factors */ +#define PJ_LIB__ +#include "proj.h" +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" + +#include + +#ifndef DEFAULT_H +#define DEFAULT_H 1e-5 /* radian default for numeric h */ +#endif + +#define EPS 1.0e-12 + +int pj_factors(LP lp, const PJ *P, double h, struct FACTORS *fac) { + double cosphi, t, n, r; + int err; + PJ_COORD coo = {{0, 0, 0, 0}}; + coo.lp = lp; + + /* Failing the 3 initial checks will most likely be due to */ + /* earlier errors, so we leave errno alone */ + if (0==fac) + return 1; + + if (0==P) + return 1; + + if (HUGE_VAL==lp.lam) + return 1; + + /* But from here, we're ready to make our own mistakes */ + err = proj_errno_reset (P); + + /* Indicate that all factors are numerical approximations */ + fac->code = 0; + + /* Check for latitude or longitude overange */ + if ((fabs (lp.phi)-M_HALFPI) > EPS || fabs (lp.lam) > 10.) { + proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + return 1; + } + + /* Set a reasonable step size for the numerical derivatives */ + h = fabs (h); + if (h < EPS) + h = DEFAULT_H; + + /* If input latitudes are geocentric, convert to geographic */ + if (P->geoc) + lp = pj_geocentric_latitude (P, PJ_INV, coo).lp; + + /* If latitude + one step overshoots the pole, move it slightly inside, */ + /* so the numerical derivative still exists */ + if (fabs (lp.phi) > (M_HALFPI - h)) + lp.phi = lp.phi < 0. ? -(M_HALFPI-h) : (M_HALFPI-h); + + /* Longitudinal distance from central meridian */ + lp.lam -= P->lam0; + if (!P->over) + lp.lam = adjlon(lp.lam); + + /* Derivatives */ + if (pj_deriv (lp, h, P, &(fac->der))) { + proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + return 1; + } + + /* Scale factors */ + cosphi = cos (lp.phi); + fac->h = hypot (fac->der.x_p, fac->der.y_p); + fac->k = hypot (fac->der.x_l, fac->der.y_l) / cosphi; + + if (P->es != 0.0) { + t = sin(lp.phi); + t = 1. - P->es * t * t; + n = sqrt(t); + fac->h *= t * n / P->one_es; + fac->k *= n; + r = t * t / P->one_es; + } else + r = 1.; + + /* Convergence */ + fac->conv = -atan2 (fac->der.x_p, fac->der.y_p); + + /* Areal scale factor */ + fac->s = (fac->der.y_p * fac->der.x_l - fac->der.x_p * fac->der.y_l) * r / cosphi; + + /* Meridian-parallel angle (theta prime) */ + fac->thetap = aasin(P->ctx,fac->s / (fac->h * fac->k)); + + /* Tissot ellipse axis */ + t = fac->k * fac->k + fac->h * fac->h; + fac->a = sqrt(t + 2. * fac->s); + t = t - 2. * fac->s; + t = t > 0? sqrt(t): 0; + fac->b = 0.5 * (fac->a - t); + fac->a = 0.5 * (fac->a + t); + + /* Angular distortion */ + fac->omega = 2. * aasin(P->ctx, (fac->a - fac->b) / (fac->a + fac->b) ); + + proj_errno_restore (P, err); + return 0; +} diff --git a/src/pj_fileapi.c b/src/pj_fileapi.c deleted file mode 100644 index eba96afd..00000000 --- a/src/pj_fileapi.c +++ /dev/null @@ -1,213 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of the pj_ctx_* file api, and the default stdio - * based implementation. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2013, 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 -#include -#include - -#include "projects.h" - -static PAFile stdio_fopen(projCtx ctx, const char *filename, - const char *access); -static size_t stdio_fread(void *buffer, size_t size, size_t nmemb, - PAFile file); -static int stdio_fseek(PAFile file, long offset, int whence); -static long stdio_ftell(PAFile file); -static void stdio_fclose(PAFile file); - -static projFileAPI default_fileapi = { - stdio_fopen, - stdio_fread, - stdio_fseek, - stdio_ftell, - stdio_fclose -}; - -typedef struct { - projCtx ctx; - FILE *fp; -} stdio_pafile; - -/************************************************************************/ -/* pj_get_default_fileapi() */ -/************************************************************************/ - -projFileAPI *pj_get_default_fileapi(void) -{ - return &default_fileapi; -} - -/************************************************************************/ -/* stdio_fopen() */ -/************************************************************************/ - -static PAFile stdio_fopen(projCtx ctx, const char *filename, - const char *access) -{ - stdio_pafile *pafile; - FILE *fp; - - fp = fopen(filename, access); - if (fp == NULL) - { - return NULL; - } - - pafile = (stdio_pafile *) malloc(sizeof(stdio_pafile)); - if (!pafile) - { - pj_ctx_set_errno(ctx, ENOMEM); - fclose(fp); - return NULL; - } - - pafile->fp = fp; - pafile->ctx = ctx; - return (PAFile) pafile; -} - -/************************************************************************/ -/* stdio_fread() */ -/************************************************************************/ - -static size_t stdio_fread(void *buffer, size_t size, size_t nmemb, - PAFile file) -{ - stdio_pafile *pafile = (stdio_pafile *) file; - return fread(buffer, size, nmemb, pafile->fp); -} - -/************************************************************************/ -/* stdio_fseek() */ -/************************************************************************/ -static int stdio_fseek(PAFile file, long offset, int whence) -{ - stdio_pafile *pafile = (stdio_pafile *) file; - return fseek(pafile->fp, offset, whence); -} - -/************************************************************************/ -/* stdio_ftell() */ -/************************************************************************/ -static long stdio_ftell(PAFile file) -{ - stdio_pafile *pafile = (stdio_pafile *) file; - return ftell(pafile->fp); -} - -/************************************************************************/ -/* stdio_fclose() */ -/************************************************************************/ -static void stdio_fclose(PAFile file) -{ - stdio_pafile *pafile = (stdio_pafile *) file; - fclose(pafile->fp); - free(pafile); -} - -/************************************************************************/ -/* pj_ctx_fopen() */ -/* */ -/* Open a file using the provided file io hooks. */ -/************************************************************************/ - -PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access) -{ - return ctx->fileapi->FOpen(ctx, filename, access); -} - -/************************************************************************/ -/* pj_ctx_fread() */ -/************************************************************************/ -size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file) -{ - return ctx->fileapi->FRead(buffer, size, nmemb, file); -} - -/************************************************************************/ -/* pj_ctx_fseek() */ -/************************************************************************/ -int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence) -{ - return ctx->fileapi->FSeek(file, offset, whence); -} - -/************************************************************************/ -/* pj_ctx_ftell() */ -/************************************************************************/ -long pj_ctx_ftell(projCtx ctx, PAFile file) -{ - return ctx->fileapi->FTell(file); -} - -/************************************************************************/ -/* pj_ctx_fclose() */ -/************************************************************************/ -void pj_ctx_fclose(projCtx ctx, PAFile file) -{ - ctx->fileapi->FClose(file); -} - -/************************************************************************/ -/* pj_ctx_fgets() */ -/* */ -/* A not very optimal implementation of fgets on top of */ -/* fread(). If we end up using this a lot more care should be */ -/* taken. */ -/************************************************************************/ - -char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file) -{ - long start = pj_ctx_ftell(ctx, file); - size_t bytes_read; - int i; - int max_size; - - line[size-1] = '\0'; - bytes_read = pj_ctx_fread(ctx, line, 1, size-1, file); - if(bytes_read == 0) - return NULL; - if(bytes_read < (size_t)size) - { - line[bytes_read] = '\0'; - } - - max_size = (int)MIN(bytes_read, (size_t)(size > 2 ? size - 2 : 0)); - for( i = 0; i < max_size; i++) - { - if (line[i] == '\n') - { - line[i+1] = '\0'; - pj_ctx_fseek(ctx, file, start + i + 1, SEEK_SET); - break; - } - } - return line; -} diff --git a/src/pj_fileapi.cpp b/src/pj_fileapi.cpp new file mode 100644 index 00000000..eba96afd --- /dev/null +++ b/src/pj_fileapi.cpp @@ -0,0 +1,213 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Implementation of the pj_ctx_* file api, and the default stdio + * based implementation. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2013, 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 +#include +#include + +#include "projects.h" + +static PAFile stdio_fopen(projCtx ctx, const char *filename, + const char *access); +static size_t stdio_fread(void *buffer, size_t size, size_t nmemb, + PAFile file); +static int stdio_fseek(PAFile file, long offset, int whence); +static long stdio_ftell(PAFile file); +static void stdio_fclose(PAFile file); + +static projFileAPI default_fileapi = { + stdio_fopen, + stdio_fread, + stdio_fseek, + stdio_ftell, + stdio_fclose +}; + +typedef struct { + projCtx ctx; + FILE *fp; +} stdio_pafile; + +/************************************************************************/ +/* pj_get_default_fileapi() */ +/************************************************************************/ + +projFileAPI *pj_get_default_fileapi(void) +{ + return &default_fileapi; +} + +/************************************************************************/ +/* stdio_fopen() */ +/************************************************************************/ + +static PAFile stdio_fopen(projCtx ctx, const char *filename, + const char *access) +{ + stdio_pafile *pafile; + FILE *fp; + + fp = fopen(filename, access); + if (fp == NULL) + { + return NULL; + } + + pafile = (stdio_pafile *) malloc(sizeof(stdio_pafile)); + if (!pafile) + { + pj_ctx_set_errno(ctx, ENOMEM); + fclose(fp); + return NULL; + } + + pafile->fp = fp; + pafile->ctx = ctx; + return (PAFile) pafile; +} + +/************************************************************************/ +/* stdio_fread() */ +/************************************************************************/ + +static size_t stdio_fread(void *buffer, size_t size, size_t nmemb, + PAFile file) +{ + stdio_pafile *pafile = (stdio_pafile *) file; + return fread(buffer, size, nmemb, pafile->fp); +} + +/************************************************************************/ +/* stdio_fseek() */ +/************************************************************************/ +static int stdio_fseek(PAFile file, long offset, int whence) +{ + stdio_pafile *pafile = (stdio_pafile *) file; + return fseek(pafile->fp, offset, whence); +} + +/************************************************************************/ +/* stdio_ftell() */ +/************************************************************************/ +static long stdio_ftell(PAFile file) +{ + stdio_pafile *pafile = (stdio_pafile *) file; + return ftell(pafile->fp); +} + +/************************************************************************/ +/* stdio_fclose() */ +/************************************************************************/ +static void stdio_fclose(PAFile file) +{ + stdio_pafile *pafile = (stdio_pafile *) file; + fclose(pafile->fp); + free(pafile); +} + +/************************************************************************/ +/* pj_ctx_fopen() */ +/* */ +/* Open a file using the provided file io hooks. */ +/************************************************************************/ + +PAFile pj_ctx_fopen(projCtx ctx, const char *filename, const char *access) +{ + return ctx->fileapi->FOpen(ctx, filename, access); +} + +/************************************************************************/ +/* pj_ctx_fread() */ +/************************************************************************/ +size_t pj_ctx_fread(projCtx ctx, void *buffer, size_t size, size_t nmemb, PAFile file) +{ + return ctx->fileapi->FRead(buffer, size, nmemb, file); +} + +/************************************************************************/ +/* pj_ctx_fseek() */ +/************************************************************************/ +int pj_ctx_fseek(projCtx ctx, PAFile file, long offset, int whence) +{ + return ctx->fileapi->FSeek(file, offset, whence); +} + +/************************************************************************/ +/* pj_ctx_ftell() */ +/************************************************************************/ +long pj_ctx_ftell(projCtx ctx, PAFile file) +{ + return ctx->fileapi->FTell(file); +} + +/************************************************************************/ +/* pj_ctx_fclose() */ +/************************************************************************/ +void pj_ctx_fclose(projCtx ctx, PAFile file) +{ + ctx->fileapi->FClose(file); +} + +/************************************************************************/ +/* pj_ctx_fgets() */ +/* */ +/* A not very optimal implementation of fgets on top of */ +/* fread(). If we end up using this a lot more care should be */ +/* taken. */ +/************************************************************************/ + +char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file) +{ + long start = pj_ctx_ftell(ctx, file); + size_t bytes_read; + int i; + int max_size; + + line[size-1] = '\0'; + bytes_read = pj_ctx_fread(ctx, line, 1, size-1, file); + if(bytes_read == 0) + return NULL; + if(bytes_read < (size_t)size) + { + line[bytes_read] = '\0'; + } + + max_size = (int)MIN(bytes_read, (size_t)(size > 2 ? size - 2 : 0)); + for( i = 0; i < max_size; i++) + { + if (line[i] == '\n') + { + line[i+1] = '\0'; + pj_ctx_fseek(ctx, file, start + i + 1, SEEK_SET); + break; + } + } + return line; +} diff --git a/src/pj_fwd.c b/src/pj_fwd.c deleted file mode 100644 index 38443f07..00000000 --- a/src/pj_fwd.c +++ /dev/null @@ -1,261 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Forward operation invocation - * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 - * Based on material from Gerald Evenden (original pj_fwd) - * and Piyush Agram (original pj_fwd3d) - * - ****************************************************************************** - * Copyright (c) 2000, Frank Warmerdam - * Copyright (c) 2018, 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. - *****************************************************************************/ - -#include -#include - -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -#define INPUT_UNITS P->left -#define OUTPUT_UNITS P->right - - -static PJ_COORD fwd_prepare (PJ *P, PJ_COORD coo) { - if (HUGE_VAL==coo.v[0] || HUGE_VAL==coo.v[1] || HUGE_VAL==coo.v[2]) - return proj_coord_error (); - - /* The helmert datum shift will choke unless it gets a sensible 4D coordinate */ - if (HUGE_VAL==coo.v[2] && P->helmert) coo.v[2] = 0.0; - if (HUGE_VAL==coo.v[3] && P->helmert) coo.v[3] = 0.0; - - /* Check validity of angular input coordinates */ - if (INPUT_UNITS==PJ_IO_UNITS_ANGULAR) { - double t; - - /* check for latitude or longitude over-range */ - t = (coo.lp.phi < 0 ? -coo.lp.phi : coo.lp.phi) - M_HALFPI; - if (t > PJ_EPS_LAT || coo.lp.lam > 10 || coo.lp.lam < -10) { - proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - return proj_coord_error (); - } - - /* Clamp latitude to -90..90 degree range */ - if (coo.lp.phi > M_HALFPI) - coo.lp.phi = M_HALFPI; - if (coo.lp.phi < -M_HALFPI) - coo.lp.phi = -M_HALFPI; - - /* If input latitude is geocentrical, convert to geographical */ - if (P->geoc) - coo = pj_geocentric_latitude (P, PJ_INV, coo); - - /* Ensure longitude is in the -pi:pi range */ - if (0==P->over) - coo.lp.lam = adjlon(coo.lp.lam); - - if (P->hgridshift) - coo = proj_trans (P->hgridshift, PJ_INV, coo); - else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { - coo = proj_trans (P->cart_wgs84, PJ_FWD, coo); /* Go cartesian in WGS84 frame */ - if( P->helmert ) - coo = proj_trans (P->helmert, PJ_INV, coo); /* Step into local frame */ - coo = proj_trans (P->cart, PJ_INV, coo); /* Go back to angular using local ellps */ - } - if (coo.lp.lam==HUGE_VAL) - return coo; - if (P->vgridshift) - coo = proj_trans (P->vgridshift, PJ_FWD, coo); /* Go orthometric from geometric */ - - /* Distance from central meridian, taking system zero meridian into account */ - coo.lp.lam = (coo.lp.lam - P->from_greenwich) - P->lam0; - - /* Ensure longitude is in the -pi:pi range */ - if (0==P->over) - coo.lp.lam = adjlon(coo.lp.lam); - - return coo; - } - - - /* We do not support gridshifts on cartesian input */ - if (INPUT_UNITS==PJ_IO_UNITS_CARTESIAN && P->helmert) - return proj_trans (P->helmert, PJ_INV, coo); - return coo; -} - - -static PJ_COORD fwd_finalize (PJ *P, PJ_COORD coo) { - - switch (OUTPUT_UNITS) { - - /* Handle false eastings/northings and non-metric linear units */ - case PJ_IO_UNITS_CARTESIAN: - - if (P->is_geocent) { - coo = proj_trans (P->cart, PJ_FWD, coo); - } - coo.xyz.x *= P->fr_meter; - coo.xyz.y *= P->fr_meter; - coo.xyz.z *= P->fr_meter; - - break; - - /* Classic proj.4 functions return plane coordinates in units of the semimajor axis */ - case PJ_IO_UNITS_CLASSIC: - coo.xy.x *= P->a; - coo.xy.y *= P->a; - - /* Falls through */ /* (<-- GCC warning silencer) */ - /* to continue processing in common with PJ_IO_UNITS_PROJECTED */ - case PJ_IO_UNITS_PROJECTED: - coo.xyz.x = P->fr_meter * (coo.xyz.x + P->x0); - coo.xyz.y = P->fr_meter * (coo.xyz.y + P->y0); - coo.xyz.z = P->vfr_meter * (coo.xyz.z + P->z0); - break; - - case PJ_IO_UNITS_WHATEVER: - break; - - case PJ_IO_UNITS_ANGULAR: - coo.lpz.z = P->vfr_meter * (coo.lpz.z + P->z0); - - if( P->is_long_wrap_set ) { - if( coo.lpz.lam != HUGE_VAL ) { - coo.lpz.lam = P->long_wrap_center + - adjlon(coo.lpz.lam - P->long_wrap_center); - } - } - - break; - } - - if (P->axisswap) - coo = proj_trans (P->axisswap, PJ_FWD, coo); - - return coo; -} - - -static PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { - if (proj_errno(P)) - return proj_coord_error(); - - proj_errno_restore(P, last_errno); - return coord; -} - - -XY pj_fwd(LP lp, PJ *P) { - int last_errno; - PJ_COORD coo = {{0,0,0,0}}; - coo.lp = lp; - - last_errno = proj_errno_reset(P); - - if (!P->skip_fwd_prepare) - coo = fwd_prepare (P, coo); - if (HUGE_VAL==coo.v[0] || HUGE_VAL==coo.v[1]) - return proj_coord_error ().xy; - - /* Do the transformation, using the lowest dimensional transformer available */ - if (P->fwd) - coo.xy = P->fwd(coo.lp, P); - else if (P->fwd3d) - coo.xyz = P->fwd3d (coo.lpz, P); - else if (P->fwd4d) - coo = P->fwd4d (coo, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error ().xy; - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().xy; - - if (!P->skip_fwd_finalize) - coo = fwd_finalize (P, coo); - - return error_or_coord(P, coo, last_errno).xy; -} - - - -XYZ pj_fwd3d(LPZ lpz, PJ *P) { - int last_errno; - PJ_COORD coo = {{0,0,0,0}}; - coo.lpz = lpz; - - last_errno = proj_errno_reset(P); - - if (!P->skip_fwd_prepare) - coo = fwd_prepare (P, coo); - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().xyz; - - /* Do the transformation, using the lowest dimensional transformer feasible */ - if (P->fwd3d) - coo.xyz = P->fwd3d(coo.lpz, P); - else if (P->fwd4d) - coo = P->fwd4d (coo, P); - else if (P->fwd) - coo.xy = P->fwd (coo.lp, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error ().xyz; - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().xyz; - - if (!P->skip_fwd_finalize) - coo = fwd_finalize (P, coo); - - return error_or_coord(P, coo, last_errno).xyz; -} - - - -PJ_COORD pj_fwd4d (PJ_COORD coo, PJ *P) { - int last_errno = proj_errno_reset(P); - - if (!P->skip_fwd_prepare) - coo = fwd_prepare (P, coo); - if (HUGE_VAL==coo.v[0]) - return proj_coord_error (); - - /* Call the highest dimensional converter available */ - if (P->fwd4d) - coo = P->fwd4d (coo, P); - else if (P->fwd3d) - coo.xyz = P->fwd3d (coo.lpz, P); - else if (P->fwd) - coo.xy = P->fwd (coo.lp, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error (); - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error (); - - if (!P->skip_fwd_finalize) - coo = fwd_finalize (P, coo); - - return error_or_coord(P, coo, last_errno); -} diff --git a/src/pj_fwd.cpp b/src/pj_fwd.cpp new file mode 100644 index 00000000..38443f07 --- /dev/null +++ b/src/pj_fwd.cpp @@ -0,0 +1,261 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Forward operation invocation + * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 + * Based on material from Gerald Evenden (original pj_fwd) + * and Piyush Agram (original pj_fwd3d) + * + ****************************************************************************** + * Copyright (c) 2000, Frank Warmerdam + * Copyright (c) 2018, 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. + *****************************************************************************/ + +#include +#include + +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" + +#define INPUT_UNITS P->left +#define OUTPUT_UNITS P->right + + +static PJ_COORD fwd_prepare (PJ *P, PJ_COORD coo) { + if (HUGE_VAL==coo.v[0] || HUGE_VAL==coo.v[1] || HUGE_VAL==coo.v[2]) + return proj_coord_error (); + + /* The helmert datum shift will choke unless it gets a sensible 4D coordinate */ + if (HUGE_VAL==coo.v[2] && P->helmert) coo.v[2] = 0.0; + if (HUGE_VAL==coo.v[3] && P->helmert) coo.v[3] = 0.0; + + /* Check validity of angular input coordinates */ + if (INPUT_UNITS==PJ_IO_UNITS_ANGULAR) { + double t; + + /* check for latitude or longitude over-range */ + t = (coo.lp.phi < 0 ? -coo.lp.phi : coo.lp.phi) - M_HALFPI; + if (t > PJ_EPS_LAT || coo.lp.lam > 10 || coo.lp.lam < -10) { + proj_errno_set (P, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + return proj_coord_error (); + } + + /* Clamp latitude to -90..90 degree range */ + if (coo.lp.phi > M_HALFPI) + coo.lp.phi = M_HALFPI; + if (coo.lp.phi < -M_HALFPI) + coo.lp.phi = -M_HALFPI; + + /* If input latitude is geocentrical, convert to geographical */ + if (P->geoc) + coo = pj_geocentric_latitude (P, PJ_INV, coo); + + /* Ensure longitude is in the -pi:pi range */ + if (0==P->over) + coo.lp.lam = adjlon(coo.lp.lam); + + if (P->hgridshift) + coo = proj_trans (P->hgridshift, PJ_INV, coo); + else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { + coo = proj_trans (P->cart_wgs84, PJ_FWD, coo); /* Go cartesian in WGS84 frame */ + if( P->helmert ) + coo = proj_trans (P->helmert, PJ_INV, coo); /* Step into local frame */ + coo = proj_trans (P->cart, PJ_INV, coo); /* Go back to angular using local ellps */ + } + if (coo.lp.lam==HUGE_VAL) + return coo; + if (P->vgridshift) + coo = proj_trans (P->vgridshift, PJ_FWD, coo); /* Go orthometric from geometric */ + + /* Distance from central meridian, taking system zero meridian into account */ + coo.lp.lam = (coo.lp.lam - P->from_greenwich) - P->lam0; + + /* Ensure longitude is in the -pi:pi range */ + if (0==P->over) + coo.lp.lam = adjlon(coo.lp.lam); + + return coo; + } + + + /* We do not support gridshifts on cartesian input */ + if (INPUT_UNITS==PJ_IO_UNITS_CARTESIAN && P->helmert) + return proj_trans (P->helmert, PJ_INV, coo); + return coo; +} + + +static PJ_COORD fwd_finalize (PJ *P, PJ_COORD coo) { + + switch (OUTPUT_UNITS) { + + /* Handle false eastings/northings and non-metric linear units */ + case PJ_IO_UNITS_CARTESIAN: + + if (P->is_geocent) { + coo = proj_trans (P->cart, PJ_FWD, coo); + } + coo.xyz.x *= P->fr_meter; + coo.xyz.y *= P->fr_meter; + coo.xyz.z *= P->fr_meter; + + break; + + /* Classic proj.4 functions return plane coordinates in units of the semimajor axis */ + case PJ_IO_UNITS_CLASSIC: + coo.xy.x *= P->a; + coo.xy.y *= P->a; + + /* Falls through */ /* (<-- GCC warning silencer) */ + /* to continue processing in common with PJ_IO_UNITS_PROJECTED */ + case PJ_IO_UNITS_PROJECTED: + coo.xyz.x = P->fr_meter * (coo.xyz.x + P->x0); + coo.xyz.y = P->fr_meter * (coo.xyz.y + P->y0); + coo.xyz.z = P->vfr_meter * (coo.xyz.z + P->z0); + break; + + case PJ_IO_UNITS_WHATEVER: + break; + + case PJ_IO_UNITS_ANGULAR: + coo.lpz.z = P->vfr_meter * (coo.lpz.z + P->z0); + + if( P->is_long_wrap_set ) { + if( coo.lpz.lam != HUGE_VAL ) { + coo.lpz.lam = P->long_wrap_center + + adjlon(coo.lpz.lam - P->long_wrap_center); + } + } + + break; + } + + if (P->axisswap) + coo = proj_trans (P->axisswap, PJ_FWD, coo); + + return coo; +} + + +static PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { + if (proj_errno(P)) + return proj_coord_error(); + + proj_errno_restore(P, last_errno); + return coord; +} + + +XY pj_fwd(LP lp, PJ *P) { + int last_errno; + PJ_COORD coo = {{0,0,0,0}}; + coo.lp = lp; + + last_errno = proj_errno_reset(P); + + if (!P->skip_fwd_prepare) + coo = fwd_prepare (P, coo); + if (HUGE_VAL==coo.v[0] || HUGE_VAL==coo.v[1]) + return proj_coord_error ().xy; + + /* Do the transformation, using the lowest dimensional transformer available */ + if (P->fwd) + coo.xy = P->fwd(coo.lp, P); + else if (P->fwd3d) + coo.xyz = P->fwd3d (coo.lpz, P); + else if (P->fwd4d) + coo = P->fwd4d (coo, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error ().xy; + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().xy; + + if (!P->skip_fwd_finalize) + coo = fwd_finalize (P, coo); + + return error_or_coord(P, coo, last_errno).xy; +} + + + +XYZ pj_fwd3d(LPZ lpz, PJ *P) { + int last_errno; + PJ_COORD coo = {{0,0,0,0}}; + coo.lpz = lpz; + + last_errno = proj_errno_reset(P); + + if (!P->skip_fwd_prepare) + coo = fwd_prepare (P, coo); + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().xyz; + + /* Do the transformation, using the lowest dimensional transformer feasible */ + if (P->fwd3d) + coo.xyz = P->fwd3d(coo.lpz, P); + else if (P->fwd4d) + coo = P->fwd4d (coo, P); + else if (P->fwd) + coo.xy = P->fwd (coo.lp, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error ().xyz; + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().xyz; + + if (!P->skip_fwd_finalize) + coo = fwd_finalize (P, coo); + + return error_or_coord(P, coo, last_errno).xyz; +} + + + +PJ_COORD pj_fwd4d (PJ_COORD coo, PJ *P) { + int last_errno = proj_errno_reset(P); + + if (!P->skip_fwd_prepare) + coo = fwd_prepare (P, coo); + if (HUGE_VAL==coo.v[0]) + return proj_coord_error (); + + /* Call the highest dimensional converter available */ + if (P->fwd4d) + coo = P->fwd4d (coo, P); + else if (P->fwd3d) + coo.xyz = P->fwd3d (coo.lpz, P); + else if (P->fwd) + coo.xy = P->fwd (coo.lp, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error (); + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error (); + + if (!P->skip_fwd_finalize) + coo = fwd_finalize (P, coo); + + return error_or_coord(P, coo, last_errno); +} diff --git a/src/pj_gauss.c b/src/pj_gauss.c deleted file mode 100644 index 4520bb39..00000000 --- a/src/pj_gauss.c +++ /dev/null @@ -1,101 +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 - -#include "projects.h" - -#define MAX_ITER 20 - -struct GAUSS { - double C; - double K; - double e; - double ratexp; -}; -#define DEL_TOL 1e-14 - -static double srat(double esinp, double ratexp) { - return(pow((1.-esinp)/(1.+esinp), ratexp)); -} - -void *pj_gauss_ini(double e, double phi0, double *chi, double *rc) { - double sphi, cphi, es; - struct GAUSS *en; - - if ((en = (struct GAUSS *)malloc(sizeof(struct GAUSS))) == NULL) - return (NULL); - es = e * e; - en->e = e; - sphi = sin(phi0); - cphi = cos(phi0); cphi *= cphi; - *rc = sqrt(1. - es) / (1. - es * sphi * sphi); - en->C = sqrt(1. + es * cphi * cphi / (1. - es)); - if (en->C == 0.0) { - free(en); - return NULL; - } - *chi = asin(sphi / en->C); - en->ratexp = 0.5 * en->C * e; - en->K = tan(.5 * *chi + M_FORTPI) / ( - pow(tan(.5 * phi0 + M_FORTPI), en->C) * - srat(en->e * sphi, en->ratexp) ); - return ((void *)en); -} - -LP pj_gauss(projCtx ctx, LP elp, const void *data) { - const struct GAUSS *en = (const struct GAUSS *)data; - LP slp; - (void) ctx; - - slp.phi = 2. * atan( en->K * - pow(tan(.5 * elp.phi + M_FORTPI), en->C) * - srat(en->e * sin(elp.phi), en->ratexp) ) - M_HALFPI; - slp.lam = en->C * (elp.lam); - return(slp); -} - -LP pj_inv_gauss(projCtx ctx, LP slp, const void *data) { - const struct GAUSS *en = (const struct GAUSS *)data; - LP elp; - double num; - int i; - - elp.lam = slp.lam / en->C; - num = pow(tan(.5 * slp.phi + M_FORTPI)/en->K, 1./en->C); - for (i = MAX_ITER; i; --i) { - elp.phi = 2. * atan(num * srat(en->e * sin(slp.phi), -.5 * en->e)) - - M_HALFPI; - if (fabs(elp.phi - slp.phi) < DEL_TOL) break; - slp.phi = elp.phi; - } - /* convergence failed */ - if (!i) - pj_ctx_set_errno(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); - return (elp); -} diff --git a/src/pj_gauss.cpp b/src/pj_gauss.cpp new file mode 100644 index 00000000..4520bb39 --- /dev/null +++ b/src/pj_gauss.cpp @@ -0,0 +1,101 @@ +/* +** 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 + +#include "projects.h" + +#define MAX_ITER 20 + +struct GAUSS { + double C; + double K; + double e; + double ratexp; +}; +#define DEL_TOL 1e-14 + +static double srat(double esinp, double ratexp) { + return(pow((1.-esinp)/(1.+esinp), ratexp)); +} + +void *pj_gauss_ini(double e, double phi0, double *chi, double *rc) { + double sphi, cphi, es; + struct GAUSS *en; + + if ((en = (struct GAUSS *)malloc(sizeof(struct GAUSS))) == NULL) + return (NULL); + es = e * e; + en->e = e; + sphi = sin(phi0); + cphi = cos(phi0); cphi *= cphi; + *rc = sqrt(1. - es) / (1. - es * sphi * sphi); + en->C = sqrt(1. + es * cphi * cphi / (1. - es)); + if (en->C == 0.0) { + free(en); + return NULL; + } + *chi = asin(sphi / en->C); + en->ratexp = 0.5 * en->C * e; + en->K = tan(.5 * *chi + M_FORTPI) / ( + pow(tan(.5 * phi0 + M_FORTPI), en->C) * + srat(en->e * sphi, en->ratexp) ); + return ((void *)en); +} + +LP pj_gauss(projCtx ctx, LP elp, const void *data) { + const struct GAUSS *en = (const struct GAUSS *)data; + LP slp; + (void) ctx; + + slp.phi = 2. * atan( en->K * + pow(tan(.5 * elp.phi + M_FORTPI), en->C) * + srat(en->e * sin(elp.phi), en->ratexp) ) - M_HALFPI; + slp.lam = en->C * (elp.lam); + return(slp); +} + +LP pj_inv_gauss(projCtx ctx, LP slp, const void *data) { + const struct GAUSS *en = (const struct GAUSS *)data; + LP elp; + double num; + int i; + + elp.lam = slp.lam / en->C; + num = pow(tan(.5 * slp.phi + M_FORTPI)/en->K, 1./en->C); + for (i = MAX_ITER; i; --i) { + elp.phi = 2. * atan(num * srat(en->e * sin(slp.phi), -.5 * en->e)) + - M_HALFPI; + if (fabs(elp.phi - slp.phi) < DEL_TOL) break; + slp.phi = elp.phi; + } + /* convergence failed */ + if (!i) + pj_ctx_set_errno(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); + return (elp); +} diff --git a/src/pj_gc_reader.c b/src/pj_gc_reader.c deleted file mode 100644 index 493fc075..00000000 --- a/src/pj_gc_reader.c +++ /dev/null @@ -1,247 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Code to read a grid catalog from a .cvs file. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2012, 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 PJ_LIB__ - -#include -#include -#include -#include - -#include "projects.h" - -static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry); - -/************************************************************************/ -/* pj_gc_readcatalog() */ -/* */ -/* Read a grid catalog from a .csv file. */ -/************************************************************************/ - -PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) -{ - PAFile fid; - PJ_GridCatalog *catalog; - int entry_max; - char line[302]; - - fid = pj_open_lib( ctx, catalog_name, "r" ); - if (fid == NULL) - return NULL; - - /* discard title line */ - pj_ctx_fgets(ctx, line, sizeof(line)-1, fid); - - catalog = (PJ_GridCatalog *) calloc(1,sizeof(PJ_GridCatalog)); - if( !catalog ) - { - pj_ctx_set_errno(ctx, ENOMEM); - pj_ctx_fclose(ctx, fid); - return NULL; - } - - catalog->catalog_name = pj_strdup(catalog_name); - if (!catalog->catalog_name) { - pj_ctx_set_errno(ctx, ENOMEM); - free(catalog); - pj_ctx_fclose(ctx, fid); - return NULL; - } - - entry_max = 10; - catalog->entries = (PJ_GridCatalogEntry *) - malloc(entry_max * sizeof(PJ_GridCatalogEntry)); - if (!catalog->entries) { - pj_ctx_set_errno(ctx, ENOMEM); - free(catalog->catalog_name); - free(catalog); - pj_ctx_fclose(ctx, fid); - return NULL; - } - - while( gc_readentry( ctx, fid, - catalog->entries+catalog->entry_count) == 0) - { - catalog->entry_count++; - - if( catalog->entry_count == entry_max ) - { - PJ_GridCatalogEntry* new_entries; - entry_max = entry_max * 2; - new_entries = (PJ_GridCatalogEntry *) - realloc(catalog->entries, - entry_max * sizeof(PJ_GridCatalogEntry)); - if (new_entries == NULL ) - { - int i; - for( i = 0; i < catalog->entry_count; i++ ) - free( catalog->entries[i].definition ); - free( catalog->entries ); - free( catalog->catalog_name ); - free( catalog ); - pj_ctx_fclose(ctx, fid); - return NULL; - } - catalog->entries = new_entries; - } - } - - pj_ctx_fclose(ctx, fid); - - return catalog; -} - -/************************************************************************/ -/* gc_read_csv_line() */ -/* */ -/* Simple csv line splitter with fixed maximum line size and */ -/* token count. */ -/************************************************************************/ - -static int gc_read_csv_line( projCtx ctx, PAFile fid, - char **tokens, int max_tokens ) -{ - char line[302]; - - while( pj_ctx_fgets(ctx, line, sizeof(line)-1, fid) != NULL ) - { - char *next = line; - int token_count = 0; - - while( isspace(*next) ) - next++; - - /* skip blank and comment lines */ - if( next[0] == '#' || next[0] == '\0' ) - continue; - - while( token_count < max_tokens && *next != '\0' ) - { - const char *start = next; - char* token; - - while( *next != '\0' && *next != ',' ) - next++; - - if( *next == ',' ) - { - *next = '\0'; - next++; - } - - token = pj_strdup(start); - if (!token) { - while (token_count > 0) - free(tokens[--token_count]); - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - tokens[token_count++] = token; - } - - return token_count; - } - - return 0; -} - -/************************************************************************/ -/* pj_gc_parsedate() */ -/* */ -/* Parse a date into a floating point year value. Acceptable */ -/* values are "yyyy.fraction" and "yyyy-mm-dd". Anything else */ -/* returns 0.0. */ -/************************************************************************/ - -double pj_gc_parsedate( projCtx ctx, const char *date_string ) -{ - (void) ctx; - - if( strlen(date_string) == 10 - && date_string[4] == '-' && date_string[7] == '-' ) - { - int year = atoi(date_string); - int month = atoi(date_string+5); - int day = atoi(date_string+8); - - /* simplified calculation so we don't need to know all about months */ - return year + ((month-1) * 31 + (day-1)) / 372.0; - } - else - { - return pj_atof(date_string); - } -} - - -/************************************************************************/ -/* gc_readentry() */ -/* */ -/* Read one catalog entry from the file */ -/* */ -/* Format: */ -/* gridname,ll_long,ll_lat,ur_long,ur_lat,priority,date */ -/************************************************************************/ - -static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry) -{ -#define MAX_TOKENS 30 - char *tokens[MAX_TOKENS]; - int token_count, i; - int error = 0; - - memset( entry, 0, sizeof(PJ_GridCatalogEntry) ); - - token_count = gc_read_csv_line( ctx, fid, tokens, MAX_TOKENS ); - if( token_count < 5 ) - { - error = 1; /* TODO: need real error codes */ - if( token_count != 0 ) - pj_log( ctx, PJ_LOG_ERROR, "Short line in grid catalog." ); - } - else - { - entry->definition = tokens[0]; - tokens[0] = NULL; /* We take ownership of tokens[0] */ - entry->region.ll_long = dmstor_ctx( ctx, tokens[1], NULL ); - entry->region.ll_lat = dmstor_ctx( ctx, tokens[2], NULL ); - entry->region.ur_long = dmstor_ctx( ctx, tokens[3], NULL ); - entry->region.ur_lat = dmstor_ctx( ctx, tokens[4], NULL ); - if( token_count > 5 ) - entry->priority = atoi( tokens[5] ); /* defaults to zero */ - if( token_count > 6 ) - entry->date = pj_gc_parsedate( ctx, tokens[6] ); - } - - for( i = 0; i < token_count; i++ ) - free( tokens[i] ); - - return error; -} - - - diff --git a/src/pj_gc_reader.cpp b/src/pj_gc_reader.cpp new file mode 100644 index 00000000..493fc075 --- /dev/null +++ b/src/pj_gc_reader.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Code to read a grid catalog from a .cvs file. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2012, 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 PJ_LIB__ + +#include +#include +#include +#include + +#include "projects.h" + +static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry); + +/************************************************************************/ +/* pj_gc_readcatalog() */ +/* */ +/* Read a grid catalog from a .csv file. */ +/************************************************************************/ + +PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) +{ + PAFile fid; + PJ_GridCatalog *catalog; + int entry_max; + char line[302]; + + fid = pj_open_lib( ctx, catalog_name, "r" ); + if (fid == NULL) + return NULL; + + /* discard title line */ + pj_ctx_fgets(ctx, line, sizeof(line)-1, fid); + + catalog = (PJ_GridCatalog *) calloc(1,sizeof(PJ_GridCatalog)); + if( !catalog ) + { + pj_ctx_set_errno(ctx, ENOMEM); + pj_ctx_fclose(ctx, fid); + return NULL; + } + + catalog->catalog_name = pj_strdup(catalog_name); + if (!catalog->catalog_name) { + pj_ctx_set_errno(ctx, ENOMEM); + free(catalog); + pj_ctx_fclose(ctx, fid); + return NULL; + } + + entry_max = 10; + catalog->entries = (PJ_GridCatalogEntry *) + malloc(entry_max * sizeof(PJ_GridCatalogEntry)); + if (!catalog->entries) { + pj_ctx_set_errno(ctx, ENOMEM); + free(catalog->catalog_name); + free(catalog); + pj_ctx_fclose(ctx, fid); + return NULL; + } + + while( gc_readentry( ctx, fid, + catalog->entries+catalog->entry_count) == 0) + { + catalog->entry_count++; + + if( catalog->entry_count == entry_max ) + { + PJ_GridCatalogEntry* new_entries; + entry_max = entry_max * 2; + new_entries = (PJ_GridCatalogEntry *) + realloc(catalog->entries, + entry_max * sizeof(PJ_GridCatalogEntry)); + if (new_entries == NULL ) + { + int i; + for( i = 0; i < catalog->entry_count; i++ ) + free( catalog->entries[i].definition ); + free( catalog->entries ); + free( catalog->catalog_name ); + free( catalog ); + pj_ctx_fclose(ctx, fid); + return NULL; + } + catalog->entries = new_entries; + } + } + + pj_ctx_fclose(ctx, fid); + + return catalog; +} + +/************************************************************************/ +/* gc_read_csv_line() */ +/* */ +/* Simple csv line splitter with fixed maximum line size and */ +/* token count. */ +/************************************************************************/ + +static int gc_read_csv_line( projCtx ctx, PAFile fid, + char **tokens, int max_tokens ) +{ + char line[302]; + + while( pj_ctx_fgets(ctx, line, sizeof(line)-1, fid) != NULL ) + { + char *next = line; + int token_count = 0; + + while( isspace(*next) ) + next++; + + /* skip blank and comment lines */ + if( next[0] == '#' || next[0] == '\0' ) + continue; + + while( token_count < max_tokens && *next != '\0' ) + { + const char *start = next; + char* token; + + while( *next != '\0' && *next != ',' ) + next++; + + if( *next == ',' ) + { + *next = '\0'; + next++; + } + + token = pj_strdup(start); + if (!token) { + while (token_count > 0) + free(tokens[--token_count]); + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + tokens[token_count++] = token; + } + + return token_count; + } + + return 0; +} + +/************************************************************************/ +/* pj_gc_parsedate() */ +/* */ +/* Parse a date into a floating point year value. Acceptable */ +/* values are "yyyy.fraction" and "yyyy-mm-dd". Anything else */ +/* returns 0.0. */ +/************************************************************************/ + +double pj_gc_parsedate( projCtx ctx, const char *date_string ) +{ + (void) ctx; + + if( strlen(date_string) == 10 + && date_string[4] == '-' && date_string[7] == '-' ) + { + int year = atoi(date_string); + int month = atoi(date_string+5); + int day = atoi(date_string+8); + + /* simplified calculation so we don't need to know all about months */ + return year + ((month-1) * 31 + (day-1)) / 372.0; + } + else + { + return pj_atof(date_string); + } +} + + +/************************************************************************/ +/* gc_readentry() */ +/* */ +/* Read one catalog entry from the file */ +/* */ +/* Format: */ +/* gridname,ll_long,ll_lat,ur_long,ur_lat,priority,date */ +/************************************************************************/ + +static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry) +{ +#define MAX_TOKENS 30 + char *tokens[MAX_TOKENS]; + int token_count, i; + int error = 0; + + memset( entry, 0, sizeof(PJ_GridCatalogEntry) ); + + token_count = gc_read_csv_line( ctx, fid, tokens, MAX_TOKENS ); + if( token_count < 5 ) + { + error = 1; /* TODO: need real error codes */ + if( token_count != 0 ) + pj_log( ctx, PJ_LOG_ERROR, "Short line in grid catalog." ); + } + else + { + entry->definition = tokens[0]; + tokens[0] = NULL; /* We take ownership of tokens[0] */ + entry->region.ll_long = dmstor_ctx( ctx, tokens[1], NULL ); + entry->region.ll_lat = dmstor_ctx( ctx, tokens[2], NULL ); + entry->region.ur_long = dmstor_ctx( ctx, tokens[3], NULL ); + entry->region.ur_lat = dmstor_ctx( ctx, tokens[4], NULL ); + if( token_count > 5 ) + entry->priority = atoi( tokens[5] ); /* defaults to zero */ + if( token_count > 6 ) + entry->date = pj_gc_parsedate( ctx, tokens[6] ); + } + + for( i = 0; i < token_count; i++ ) + free( tokens[i] ); + + return error; +} + + + diff --git a/src/pj_geocent.c b/src/pj_geocent.c deleted file mode 100644 index 0e9d725e..00000000 --- a/src/pj_geocent.c +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Stub projection for geocentric. The transformation isn't - * really done here since this code is 2D. The real transformation - * is handled by pj_transform.c. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2002, 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 PJ_LIB__ -#include "projects.h" - -PROJ_HEAD(geocent, "Geocentric") "\n\t"; - -static XY 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 inverse(XY xy, PJ *P) { - LP lp = {0.0,0.0}; - (void) P; - lp.phi = xy.y; - lp.lam = xy.x; - return lp; -} - -PJ *CONVERSION (geocent, 0) { - P->is_geocent = 1; - P->x0 = 0.0; - P->y0 = 0.0; - P->inv = inverse; - P->fwd = forward; - P->left = PJ_IO_UNITS_ANGULAR; - P->right = PJ_IO_UNITS_CARTESIAN; - - return P; -} - diff --git a/src/pj_geocent.cpp b/src/pj_geocent.cpp new file mode 100644 index 00000000..0e9d725e --- /dev/null +++ b/src/pj_geocent.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Stub projection for geocentric. The transformation isn't + * really done here since this code is 2D. The real transformation + * is handled by pj_transform.c. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2002, 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 PJ_LIB__ +#include "projects.h" + +PROJ_HEAD(geocent, "Geocentric") "\n\t"; + +static XY 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 inverse(XY xy, PJ *P) { + LP lp = {0.0,0.0}; + (void) P; + lp.phi = xy.y; + lp.lam = xy.x; + return lp; +} + +PJ *CONVERSION (geocent, 0) { + P->is_geocent = 1; + P->x0 = 0.0; + P->y0 = 0.0; + P->inv = inverse; + P->fwd = forward; + P->left = PJ_IO_UNITS_ANGULAR; + P->right = PJ_IO_UNITS_CARTESIAN; + + return P; +} + diff --git a/src/pj_gridcatalog.c b/src/pj_gridcatalog.c deleted file mode 100644 index ea9c4aa1..00000000 --- a/src/pj_gridcatalog.c +++ /dev/null @@ -1,295 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Code in support of grid catalogs - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2012, 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 PJ_LIB__ - -#include -#include -#include -#include - -#include "projects.h" - -static PJ_GridCatalog *grid_catalog_list = NULL; - -/************************************************************************/ -/* pj_gc_unloadall() */ -/* */ -/* Deallocate all the grid catalogs (but not the referenced */ -/* grids). */ -/************************************************************************/ - -void pj_gc_unloadall( projCtx ctx ) -{ - (void) ctx; - - while( grid_catalog_list != NULL ) - { - int i; - PJ_GridCatalog *catalog = grid_catalog_list; - grid_catalog_list = grid_catalog_list->next; - - for( i = 0; i < catalog->entry_count; i++ ) - { - /* we don't own gridinfo - do not free here */ - free( catalog->entries[i].definition ); - } - free( catalog->entries ); - free( catalog->catalog_name ); - free( catalog ); - } -} - -/************************************************************************/ -/* pj_gc_findcatalog() */ -/************************************************************************/ - -PJ_GridCatalog *pj_gc_findcatalog( projCtx ctx, const char *name ) - -{ - PJ_GridCatalog *catalog; - - pj_acquire_lock(); - - for( catalog=grid_catalog_list; catalog != NULL; catalog = catalog->next ) - { - if( strcmp(catalog->catalog_name, name) == 0 ) - { - pj_release_lock(); - return catalog; - } - } - - pj_release_lock(); - - catalog = pj_gc_readcatalog( ctx, name ); - if( catalog == NULL ) - return NULL; - - pj_acquire_lock(); - catalog->next = grid_catalog_list; - grid_catalog_list = catalog; - pj_release_lock(); - - return catalog; -} - -/************************************************************************/ -/* pj_gc_apply_gridshift() */ -/************************************************************************/ - -int pj_gc_apply_gridshift( PJ *defn, int inverse, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - int i; - (void) z; - - if( defn->catalog == NULL ) - { - defn->catalog = pj_gc_findcatalog( defn->ctx, defn->catalog_name ); - if( defn->catalog == NULL ) - return defn->ctx->last_errno; - } - - defn->ctx->last_errno = 0; - - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - LP input, output_after, output_before; - double mix_ratio; - PJ_GRIDINFO *gi; - - input.phi = y[io]; - input.lam = x[io]; - - /* make sure we have appropriate "after" shift file available */ - if( defn->last_after_grid == NULL - || input.lam < defn->last_after_region.ll_long - || input.lam > defn->last_after_region.ur_long - || input.phi < defn->last_after_region.ll_lat - || input.phi > defn->last_after_region.ll_lat ) { - defn->last_after_grid = - pj_gc_findgrid( defn->ctx, defn->catalog, - 1, input, defn->datum_date, - &(defn->last_after_region), - &(defn->last_after_date)); - if( defn->last_after_grid == NULL ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - } - gi = defn->last_after_grid; - assert( gi->child == NULL ); - - /* load the grid shift info if we don't have it. */ - if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - - output_after = nad_cvt( input, inverse, gi->ct ); - if( output_after.lam == HUGE_VAL ) - { - if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) - { - pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR, - "pj_apply_gridshift(): failed to find a grid shift table for\n" - " location (%.7fdW,%.7fdN)", - x[io] * RAD_TO_DEG, - y[io] * RAD_TO_DEG ); - } - continue; - } - - if( defn->datum_date == 0.0 ) - { - y[io] = output_after.phi; - x[io] = output_after.lam; - continue; - } - - /* make sure we have appropriate "before" shift file available */ - if( defn->last_before_grid == NULL - || input.lam < defn->last_before_region.ll_long - || input.lam > defn->last_before_region.ur_long - || input.phi < defn->last_before_region.ll_lat - || input.phi > defn->last_before_region.ll_lat ) { - defn->last_before_grid = - pj_gc_findgrid( defn->ctx, defn->catalog, - 0, input, defn->datum_date, - &(defn->last_before_region), - &(defn->last_before_date)); - if( defn->last_before_grid == NULL ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - } - - gi = defn->last_before_grid; - assert( gi->child == NULL ); - - /* load the grid shift info if we don't have it. */ - if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) - { - pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return PJD_ERR_FAILED_TO_LOAD_GRID; - } - - output_before = nad_cvt( input, inverse, gi->ct ); - if( output_before.lam == HUGE_VAL ) - { - if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) - { - pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR, - "pj_apply_gridshift(): failed to find a grid shift table for\n" - " location (%.7fdW,%.7fdN)", - x[io] * RAD_TO_DEG, - y[io] * RAD_TO_DEG ); - } - continue; - } - - mix_ratio = (defn->datum_date - defn->last_before_date) - / (defn->last_after_date - defn->last_before_date); - - y[io] = mix_ratio * output_after.phi - + (1.0-mix_ratio) * output_before.phi; - x[io] = mix_ratio * output_after.lam - + (1.0-mix_ratio) * output_before.lam; - } - - return 0; -} - -/************************************************************************/ -/* pj_c_findgrid() */ -/************************************************************************/ - -PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after, - LP location, double date, - PJ_Region *optional_region, - double *grid_date ) -{ - int iEntry; - PJ_GridCatalogEntry *entry = NULL; - - for( iEntry = 0; iEntry < catalog->entry_count; iEntry++ ) - { - entry = catalog->entries + iEntry; - - if( (after && entry->date < date) - || (!after && entry->date > date) ) - continue; - - if( location.lam < entry->region.ll_long - || location.lam > entry->region.ur_long - || location.phi < entry->region.ll_lat - || location.phi > entry->region.ur_lat ) - continue; - - if( entry->available == -1 ) - continue; - - break; - } - - if( entry == NULL ) - { - if( grid_date ) - *grid_date = 0.0; - if( optional_region != NULL ) - memset( optional_region, 0, sizeof(PJ_Region)); - return NULL; - } - - if( grid_date ) - *grid_date = entry->date; - - if( optional_region ) - { - - } - - if( entry->gridinfo == NULL ) - { - PJ_GRIDINFO **gridlist = NULL; - int grid_count = 0; - gridlist = pj_gridlist_from_nadgrids( ctx, entry->definition, - &grid_count); - if( grid_count == 1 ) - entry->gridinfo = gridlist[0]; - } - - return entry->gridinfo; -} - diff --git a/src/pj_gridcatalog.cpp b/src/pj_gridcatalog.cpp new file mode 100644 index 00000000..ea9c4aa1 --- /dev/null +++ b/src/pj_gridcatalog.cpp @@ -0,0 +1,295 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Code in support of grid catalogs + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2012, 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 PJ_LIB__ + +#include +#include +#include +#include + +#include "projects.h" + +static PJ_GridCatalog *grid_catalog_list = NULL; + +/************************************************************************/ +/* pj_gc_unloadall() */ +/* */ +/* Deallocate all the grid catalogs (but not the referenced */ +/* grids). */ +/************************************************************************/ + +void pj_gc_unloadall( projCtx ctx ) +{ + (void) ctx; + + while( grid_catalog_list != NULL ) + { + int i; + PJ_GridCatalog *catalog = grid_catalog_list; + grid_catalog_list = grid_catalog_list->next; + + for( i = 0; i < catalog->entry_count; i++ ) + { + /* we don't own gridinfo - do not free here */ + free( catalog->entries[i].definition ); + } + free( catalog->entries ); + free( catalog->catalog_name ); + free( catalog ); + } +} + +/************************************************************************/ +/* pj_gc_findcatalog() */ +/************************************************************************/ + +PJ_GridCatalog *pj_gc_findcatalog( projCtx ctx, const char *name ) + +{ + PJ_GridCatalog *catalog; + + pj_acquire_lock(); + + for( catalog=grid_catalog_list; catalog != NULL; catalog = catalog->next ) + { + if( strcmp(catalog->catalog_name, name) == 0 ) + { + pj_release_lock(); + return catalog; + } + } + + pj_release_lock(); + + catalog = pj_gc_readcatalog( ctx, name ); + if( catalog == NULL ) + return NULL; + + pj_acquire_lock(); + catalog->next = grid_catalog_list; + grid_catalog_list = catalog; + pj_release_lock(); + + return catalog; +} + +/************************************************************************/ +/* pj_gc_apply_gridshift() */ +/************************************************************************/ + +int pj_gc_apply_gridshift( PJ *defn, int inverse, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + int i; + (void) z; + + if( defn->catalog == NULL ) + { + defn->catalog = pj_gc_findcatalog( defn->ctx, defn->catalog_name ); + if( defn->catalog == NULL ) + return defn->ctx->last_errno; + } + + defn->ctx->last_errno = 0; + + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + LP input, output_after, output_before; + double mix_ratio; + PJ_GRIDINFO *gi; + + input.phi = y[io]; + input.lam = x[io]; + + /* make sure we have appropriate "after" shift file available */ + if( defn->last_after_grid == NULL + || input.lam < defn->last_after_region.ll_long + || input.lam > defn->last_after_region.ur_long + || input.phi < defn->last_after_region.ll_lat + || input.phi > defn->last_after_region.ll_lat ) { + defn->last_after_grid = + pj_gc_findgrid( defn->ctx, defn->catalog, + 1, input, defn->datum_date, + &(defn->last_after_region), + &(defn->last_after_date)); + if( defn->last_after_grid == NULL ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + } + gi = defn->last_after_grid; + assert( gi->child == NULL ); + + /* load the grid shift info if we don't have it. */ + if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + + output_after = nad_cvt( input, inverse, gi->ct ); + if( output_after.lam == HUGE_VAL ) + { + if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) + { + pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR, + "pj_apply_gridshift(): failed to find a grid shift table for\n" + " location (%.7fdW,%.7fdN)", + x[io] * RAD_TO_DEG, + y[io] * RAD_TO_DEG ); + } + continue; + } + + if( defn->datum_date == 0.0 ) + { + y[io] = output_after.phi; + x[io] = output_after.lam; + continue; + } + + /* make sure we have appropriate "before" shift file available */ + if( defn->last_before_grid == NULL + || input.lam < defn->last_before_region.ll_long + || input.lam > defn->last_before_region.ur_long + || input.phi < defn->last_before_region.ll_lat + || input.phi > defn->last_before_region.ll_lat ) { + defn->last_before_grid = + pj_gc_findgrid( defn->ctx, defn->catalog, + 0, input, defn->datum_date, + &(defn->last_before_region), + &(defn->last_before_date)); + if( defn->last_before_grid == NULL ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + } + + gi = defn->last_before_grid; + assert( gi->child == NULL ); + + /* load the grid shift info if we don't have it. */ + if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) + { + pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return PJD_ERR_FAILED_TO_LOAD_GRID; + } + + output_before = nad_cvt( input, inverse, gi->ct ); + if( output_before.lam == HUGE_VAL ) + { + if( defn->ctx->debug_level >= PJ_LOG_DEBUG_MAJOR ) + { + pj_log( defn->ctx, PJ_LOG_DEBUG_MAJOR, + "pj_apply_gridshift(): failed to find a grid shift table for\n" + " location (%.7fdW,%.7fdN)", + x[io] * RAD_TO_DEG, + y[io] * RAD_TO_DEG ); + } + continue; + } + + mix_ratio = (defn->datum_date - defn->last_before_date) + / (defn->last_after_date - defn->last_before_date); + + y[io] = mix_ratio * output_after.phi + + (1.0-mix_ratio) * output_before.phi; + x[io] = mix_ratio * output_after.lam + + (1.0-mix_ratio) * output_before.lam; + } + + return 0; +} + +/************************************************************************/ +/* pj_c_findgrid() */ +/************************************************************************/ + +PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after, + LP location, double date, + PJ_Region *optional_region, + double *grid_date ) +{ + int iEntry; + PJ_GridCatalogEntry *entry = NULL; + + for( iEntry = 0; iEntry < catalog->entry_count; iEntry++ ) + { + entry = catalog->entries + iEntry; + + if( (after && entry->date < date) + || (!after && entry->date > date) ) + continue; + + if( location.lam < entry->region.ll_long + || location.lam > entry->region.ur_long + || location.phi < entry->region.ll_lat + || location.phi > entry->region.ur_lat ) + continue; + + if( entry->available == -1 ) + continue; + + break; + } + + if( entry == NULL ) + { + if( grid_date ) + *grid_date = 0.0; + if( optional_region != NULL ) + memset( optional_region, 0, sizeof(PJ_Region)); + return NULL; + } + + if( grid_date ) + *grid_date = entry->date; + + if( optional_region ) + { + + } + + if( entry->gridinfo == NULL ) + { + PJ_GRIDINFO **gridlist = NULL; + int grid_count = 0; + gridlist = pj_gridlist_from_nadgrids( ctx, entry->definition, + &grid_count); + if( grid_count == 1 ) + entry->gridinfo = gridlist[0]; + } + + return entry->gridinfo; +} + diff --git a/src/pj_gridinfo.c b/src/pj_gridinfo.c deleted file mode 100644 index de0e8d31..00000000 --- a/src/pj_gridinfo.c +++ /dev/null @@ -1,991 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Functions for handling individual PJ_GRIDINFO's. Includes - * loaders for all formats but CTABLE (in nad_init.c). - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -/************************************************************************/ -/* swap_words() */ -/* */ -/* Convert the byte order of the given word(s) in place. */ -/************************************************************************/ - -static const int byte_order_test = 1; -#define IS_LSB (1 == ((const unsigned char *) (&byte_order_test))[0]) - -static void swap_words( unsigned char *data, int word_size, int word_count ) - -{ - int word; - - 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; - } -} - -/************************************************************************/ -/* to_double() */ -/* */ -/* Returns a double from the pointed data. */ -/************************************************************************/ - -static double to_double( unsigned char* data ) -{ - double d; - memcpy(&d, data, sizeof(d)); - return d; -} - -/************************************************************************/ -/* pj_gridinfo_free() */ -/************************************************************************/ - -void pj_gridinfo_free( projCtx ctx, PJ_GRIDINFO *gi ) - -{ - if( gi == NULL ) - return; - - if( gi->child != NULL ) - { - PJ_GRIDINFO *child, *next; - - for( child = gi->child; child != NULL; child=next) - { - next=child->next; - pj_gridinfo_free( ctx, child ); - } - } - - if( gi->ct != NULL ) - nad_free( gi->ct ); - - free( gi->gridname ); - if( gi->filename != NULL ) - free( gi->filename ); - - pj_dalloc( gi ); -} - -/************************************************************************/ -/* pj_gridinfo_load() */ -/* */ -/* This function is intended to implement delayed loading of */ -/* the data contents of a grid file. The header and related */ -/* stuff are loaded by pj_gridinfo_init(). */ -/************************************************************************/ - -int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) - -{ - struct CTABLE ct_tmp; - - if( gi == NULL || gi->ct == NULL ) - return 0; - - pj_acquire_lock(); - if( gi->ct->cvs != NULL ) - { - pj_release_lock(); - return 1; - } - - memcpy(&ct_tmp, gi->ct, sizeof(struct CTABLE)); - -/* -------------------------------------------------------------------- */ -/* Original platform specific CTable format. */ -/* -------------------------------------------------------------------- */ - if( strcmp(gi->format,"ctable") == 0 ) - { - PAFile fid; - int result; - - fid = pj_open_lib( ctx, gi->filename, "rb" ); - - if( fid == NULL ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - result = nad_ctable_load( ctx, &ct_tmp, fid ); - - pj_ctx_fclose( ctx, fid ); - - gi->ct->cvs = ct_tmp.cvs; - pj_release_lock(); - - return result; - } - -/* -------------------------------------------------------------------- */ -/* CTable2 format. */ -/* -------------------------------------------------------------------- */ - else if( strcmp(gi->format,"ctable2") == 0 ) - { - PAFile fid; - int result; - - fid = pj_open_lib( ctx, gi->filename, "rb" ); - - if( fid == NULL ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - result = nad_ctable2_load( ctx, &ct_tmp, fid ); - - pj_ctx_fclose( ctx, fid ); - - gi->ct->cvs = ct_tmp.cvs; - - pj_release_lock(); - return result; - } - -/* -------------------------------------------------------------------- */ -/* NTv1 format. */ -/* We process one line at a time. Note that the array storage */ -/* direction (e-w) is different in the NTv1 file and what */ -/* the CTABLE is supposed to have. The phi/lam are also */ -/* reversed, and we have to be aware of byte swapping. */ -/* -------------------------------------------------------------------- */ - else if( strcmp(gi->format,"ntv1") == 0 ) - { - double *row_buf; - int row; - PAFile fid; - - fid = pj_open_lib( ctx, gi->filename, "rb" ); - - if( fid == NULL ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); - - row_buf = (double *) pj_malloc(gi->ct->lim.lam * sizeof(double) * 2); - ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); - if( row_buf == NULL || ct_tmp.cvs == NULL ) - { - pj_dalloc( row_buf ); - pj_dalloc( ct_tmp.cvs ); - pj_ctx_set_errno( ctx, ENOMEM ); - pj_release_lock(); - return 0; - } - - for( row = 0; row < gi->ct->lim.phi; row++ ) - { - int i; - FLP *cvs; - double *diff_seconds; - - if( pj_ctx_fread( ctx, row_buf, - sizeof(double), gi->ct->lim.lam * 2, fid ) - != (size_t)( 2 * gi->ct->lim.lam ) ) - { - pj_dalloc( row_buf ); - pj_dalloc( ct_tmp.cvs ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - if( IS_LSB ) - swap_words( (unsigned char *) row_buf, 8, gi->ct->lim.lam*2 ); - - /* convert seconds to radians */ - diff_seconds = row_buf; - - for( i = 0; i < gi->ct->lim.lam; i++ ) - { - cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam - + (gi->ct->lim.lam - i - 1); - - cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); - cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); - } - } - - pj_dalloc( row_buf ); - - pj_ctx_fclose( ctx, fid ); - - gi->ct->cvs = ct_tmp.cvs; - pj_release_lock(); - - return 1; - } - -/* -------------------------------------------------------------------- */ -/* NTv2 format. */ -/* We process one line at a time. Note that the array storage */ -/* direction (e-w) is different in the NTv2 file and what */ -/* the CTABLE is supposed to have. The phi/lam are also */ -/* reversed, and we have to be aware of byte swapping. */ -/* -------------------------------------------------------------------- */ - else if( strcmp(gi->format,"ntv2") == 0 ) - { - float *row_buf; - int row; - PAFile fid; - - pj_log( ctx, PJ_LOG_DEBUG_MINOR, - "NTv2 - loading grid %s", gi->ct->id ); - - fid = pj_open_lib( ctx, gi->filename, "rb" ); - - if( fid == NULL ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); - - row_buf = (float *) pj_malloc(gi->ct->lim.lam * sizeof(float) * 4); - ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); - if( row_buf == NULL || ct_tmp.cvs == NULL ) - { - pj_dalloc( row_buf ); - pj_dalloc( ct_tmp.cvs ); - pj_ctx_set_errno( ctx, ENOMEM ); - pj_release_lock(); - return 0; - } - - for( row = 0; row < gi->ct->lim.phi; row++ ) - { - int i; - FLP *cvs; - float *diff_seconds; - - if( pj_ctx_fread( ctx, row_buf, sizeof(float), - gi->ct->lim.lam*4, fid ) - != (size_t)( 4 * gi->ct->lim.lam ) ) - { - pj_dalloc( row_buf ); - pj_dalloc( ct_tmp.cvs ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - if( gi->must_swap ) - swap_words( (unsigned char *) row_buf, 4, - gi->ct->lim.lam*4 ); - - /* convert seconds to radians */ - diff_seconds = row_buf; - - for( i = 0; i < gi->ct->lim.lam; i++ ) - { - cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam - + (gi->ct->lim.lam - i - 1); - - cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); - cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); - diff_seconds += 2; /* skip accuracy values */ - } - } - - pj_dalloc( row_buf ); - - pj_ctx_fclose( ctx, fid ); - - gi->ct->cvs = ct_tmp.cvs; - - pj_release_lock(); - return 1; - } - -/* -------------------------------------------------------------------- */ -/* GTX format. */ -/* -------------------------------------------------------------------- */ - else if( strcmp(gi->format,"gtx") == 0 ) - { - int words = gi->ct->lim.lam * gi->ct->lim.phi; - PAFile fid; - - fid = pj_open_lib( ctx, gi->filename, "rb" ); - - if( fid == NULL ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); - - ct_tmp.cvs = (FLP *) pj_malloc(words*sizeof(float)); - if( ct_tmp.cvs == NULL ) - { - pj_ctx_set_errno( ctx, ENOMEM ); - pj_release_lock(); - return 0; - } - - if( pj_ctx_fread( ctx, ct_tmp.cvs, sizeof(float), words, fid ) - != (size_t)words ) - { - pj_dalloc( ct_tmp.cvs ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return 0; - } - - if( IS_LSB ) - swap_words( (unsigned char *) ct_tmp.cvs, 4, words ); - - pj_ctx_fclose( ctx, fid ); - gi->ct->cvs = ct_tmp.cvs; - pj_release_lock(); - return 1; - } - - else - { - pj_release_lock(); - return 0; - } -} - -/************************************************************************/ -/* gridinfo_parent() */ -/* */ -/* Seek a parent grid file by name from a grid list */ -/************************************************************************/ - -static PJ_GRIDINFO* gridinfo_parent( PJ_GRIDINFO *gilist, - const char *name, int length ) -{ - while( gilist ) - { - if( strncmp(gilist->ct->id,name,length) == 0 ) return gilist; - if( gilist->child ) - { - PJ_GRIDINFO *parent=gridinfo_parent( gilist->child, name, length ); - if( parent ) return parent; - } - gilist=gilist->next; - } - return gilist; -} - -/************************************************************************/ -/* pj_gridinfo_init_ntv2() */ -/* */ -/* Load a ntv2 (.gsb) file. */ -/************************************************************************/ - -static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) -{ - unsigned char header[11*16]; - int num_subfiles, subfile; - int must_swap; - - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(pj_int32) == 4 ); - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(double) == 8 ); - -/* -------------------------------------------------------------------- */ -/* Read the overview header. */ -/* -------------------------------------------------------------------- */ - if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - - if( header[8] == 11 ) - must_swap = !IS_LSB; - else - must_swap = IS_LSB; - -/* -------------------------------------------------------------------- */ -/* Byte swap interesting fields if needed. */ -/* -------------------------------------------------------------------- */ - if( must_swap ) - { - swap_words( header+8, 4, 1 ); - swap_words( header+8+16, 4, 1 ); - swap_words( header+8+32, 4, 1 ); - swap_words( header+8+7*16, 8, 1 ); - swap_words( header+8+8*16, 8, 1 ); - swap_words( header+8+9*16, 8, 1 ); - swap_words( header+8+10*16, 8, 1 ); - } - -/* -------------------------------------------------------------------- */ -/* Get the subfile count out ... all we really use for now. */ -/* -------------------------------------------------------------------- */ - memcpy( &num_subfiles, header+8+32, 4 ); - -/* ==================================================================== */ -/* Step through the subfiles, creating a PJ_GRIDINFO for each. */ -/* ==================================================================== */ - for( subfile = 0; subfile < num_subfiles; subfile++ ) - { - struct CTABLE *ct; - LP ur; - int gs_count; - PJ_GRIDINFO *gi; - -/* -------------------------------------------------------------------- */ -/* Read header. */ -/* -------------------------------------------------------------------- */ - if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - - if( strncmp((const char *) header,"SUB_NAME",8) != 0 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - -/* -------------------------------------------------------------------- */ -/* Byte swap interesting fields if needed. */ -/* -------------------------------------------------------------------- */ - if( must_swap ) - { - swap_words( header+8+16*4, 8, 1 ); - swap_words( header+8+16*5, 8, 1 ); - swap_words( header+8+16*6, 8, 1 ); - swap_words( header+8+16*7, 8, 1 ); - swap_words( header+8+16*8, 8, 1 ); - swap_words( header+8+16*9, 8, 1 ); - swap_words( header+8+16*10, 4, 1 ); - } - -/* -------------------------------------------------------------------- */ -/* Initialize a corresponding "ct" structure. */ -/* -------------------------------------------------------------------- */ - ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if (!ct) { - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - strncpy( ct->id, (const char *) header + 8, 8 ); - ct->id[8] = '\0'; - - ct->ll.lam = - to_double(header+7*16+8); /* W_LONG */ - ct->ll.phi = to_double(header+4*16+8); /* S_LAT */ - - ur.lam = - to_double(header+6*16+8); /* E_LONG */ - ur.phi = to_double(header+5*16+8); /* N_LAT */ - - ct->del.lam = to_double(header+9*16+8); - ct->del.phi = to_double(header+8*16+8); - - ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1; - ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1; - - pj_log( ctx, PJ_LOG_DEBUG_MINOR, - "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", - ct->id, - ct->lim.lam, ct->lim.phi, - ct->ll.lam/3600.0, ct->ll.phi/3600.0, - ur.lam/3600.0, ur.phi/3600.0 ); - - ct->ll.lam *= DEG_TO_RAD/3600.0; - ct->ll.phi *= DEG_TO_RAD/3600.0; - ct->del.lam *= DEG_TO_RAD/3600.0; - ct->del.phi *= DEG_TO_RAD/3600.0; - - memcpy( &gs_count, header + 8 + 16*10, 4 ); - if( gs_count != ct->lim.lam * ct->lim.phi ) - { - pj_log( ctx, PJ_LOG_ERROR, - "GS_COUNT(%d) does not match expected cells (%dx%d=%d)", - gs_count, ct->lim.lam, ct->lim.phi, - ct->lim.lam * ct->lim.phi ); - pj_dalloc(ct); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - - ct->cvs = NULL; - -/* -------------------------------------------------------------------- */ -/* Create a new gridinfo for this if we aren't processing the */ -/* 1st subfile, and initialize our grid info. */ -/* -------------------------------------------------------------------- */ - if( subfile == 0 ) - gi = gilist; - else - { - gi = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO)); - if (!gi) { - pj_dalloc(ct); - pj_gridinfo_free(ctx, gilist); - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - - gi->gridname = pj_strdup( gilist->gridname ); - gi->filename = pj_strdup( gilist->filename ); - if (!gi->gridname || !gi->filename) { - pj_gridinfo_free(ctx, gi); - pj_dalloc(ct); - pj_gridinfo_free(ctx, gilist); - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - gi->next = NULL; - } - - gi->must_swap = must_swap; - gi->ct = ct; - gi->format = "ntv2"; - gi->grid_offset = pj_ctx_ftell( ctx, fid ); - -/* -------------------------------------------------------------------- */ -/* Attach to the correct list or sublist. */ -/* -------------------------------------------------------------------- */ - if( strncmp((const char *)header+24,"NONE",4) == 0 ) - { - if( gi != gilist ) - { - PJ_GRIDINFO *lnk; - - for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} - lnk->next = gi; - } - } - - else - { - PJ_GRIDINFO *lnk; - PJ_GRIDINFO *gp = gridinfo_parent(gilist, - (const char*)header+24,8); - - if( gp == NULL ) - { - pj_log( ctx, PJ_LOG_ERROR, - "pj_gridinfo_init_ntv2(): " - "failed to find parent %8.8s for %s.", - (const char *) header+24, gi->ct->id ); - - for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} - lnk->next = gi; - } - else - { - if( gp->child == NULL ) - { - gp->child = gi; - } - else - { - for( lnk = gp->child; lnk->next != NULL; lnk = lnk->next ) {} - lnk->next = gi; - } - } - } - -/* -------------------------------------------------------------------- */ -/* Seek past the data. */ -/* -------------------------------------------------------------------- */ - pj_ctx_fseek( ctx, fid, gs_count * 16, SEEK_CUR ); - } - - return 1; -} - -/************************************************************************/ -/* pj_gridinfo_init_ntv1() */ -/* */ -/* Load an NTv1 style Canadian grid shift file. */ -/************************************************************************/ - -static int pj_gridinfo_init_ntv1( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) - -{ - unsigned char header[192]; /* 12 records of 16 bytes */ - struct CTABLE *ct; - LP ur; - - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(pj_int32) == 4 ); - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(double) == 8 ); - -/* -------------------------------------------------------------------- */ -/* Read the header. */ -/* -------------------------------------------------------------------- */ - if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - -/* -------------------------------------------------------------------- */ -/* Regularize fields of interest. */ -/* -------------------------------------------------------------------- */ - if( IS_LSB ) - { - swap_words( header+8, 4, 1 ); - swap_words( header+24, 8, 1 ); - swap_words( header+40, 8, 1 ); - swap_words( header+56, 8, 1 ); - swap_words( header+72, 8, 1 ); - swap_words( header+88, 8, 1 ); - swap_words( header+104, 8, 1 ); - } - - if( *((int *) (header+8)) != 12 ) - { - pj_log( ctx, PJ_LOG_ERROR, - "NTv1 grid shift file has wrong record count, corrupt?" ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - -/* -------------------------------------------------------------------- */ -/* Fill in CTABLE structure. */ -/* -------------------------------------------------------------------- */ - ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if (!ct) { - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - strcpy( ct->id, "NTv1 Grid Shift File" ); - - ct->ll.lam = - to_double(header+72); - ct->ll.phi = to_double(header+24); - ur.lam = - to_double(header+56); - ur.phi = to_double(header+40); - ct->del.lam = to_double(header+104); - ct->del.phi = to_double(header+88); - ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1; - ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1; - - pj_log( ctx, PJ_LOG_DEBUG_MINOR, - "NTv1 %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", - ct->lim.lam, ct->lim.phi, - ct->ll.lam, ct->ll.phi, ur.lam, ur.phi ); - - ct->ll.lam *= DEG_TO_RAD; - ct->ll.phi *= DEG_TO_RAD; - ct->del.lam *= DEG_TO_RAD; - ct->del.phi *= DEG_TO_RAD; - ct->cvs = NULL; - - gi->ct = ct; - gi->grid_offset = (long) sizeof(header); - gi->format = "ntv1"; - - return 1; -} - -/************************************************************************/ -/* pj_gridinfo_init_gtx() */ -/* */ -/* Load a NOAA .gtx vertical datum shift file. */ -/************************************************************************/ - -static int pj_gridinfo_init_gtx( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) - -{ - unsigned char header[40]; - struct CTABLE *ct; - double xorigin,yorigin,xstep,ystep; - int rows, columns; - - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(pj_int32) == 4 ); - /* cppcheck-suppress sizeofCalculation */ - STATIC_ASSERT( sizeof(double) == 8 ); - -/* -------------------------------------------------------------------- */ -/* Read the header. */ -/* -------------------------------------------------------------------- */ - if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) - { - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - -/* -------------------------------------------------------------------- */ -/* Regularize fields of interest and extract. */ -/* -------------------------------------------------------------------- */ - if( IS_LSB ) - { - swap_words( header+0, 8, 4 ); - swap_words( header+32, 4, 2 ); - } - - memcpy( &yorigin, header+0, 8 ); - memcpy( &xorigin, header+8, 8 ); - memcpy( &ystep, header+16, 8 ); - memcpy( &xstep, header+24, 8 ); - - memcpy( &rows, header+32, 4 ); - memcpy( &columns, header+36, 4 ); - - if( xorigin < -360 || xorigin > 360 - || yorigin < -90 || yorigin > 90 ) - { - pj_log( ctx, PJ_LOG_ERROR, - "gtx file header has invalid extents, corrupt?"); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return 0; - } - -/* -------------------------------------------------------------------- */ -/* Fill in CTABLE structure. */ -/* -------------------------------------------------------------------- */ - ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if (!ct) { - pj_ctx_set_errno(ctx, ENOMEM); - return 0; - } - strcpy( ct->id, "GTX Vertical Grid Shift File" ); - - ct->ll.lam = xorigin; - ct->ll.phi = yorigin; - ct->del.lam = xstep; - ct->del.phi = ystep; - ct->lim.lam = columns; - ct->lim.phi = rows; - - /* some GTX files come in 0-360 and we shift them back into the - expected -180 to 180 range if possible. This does not solve - problems with grids spanning the dateline. */ - if( ct->ll.lam >= 180.0 ) - ct->ll.lam -= 360.0; - - if( ct->ll.lam >= 0.0 && ct->ll.lam + ct->del.lam * ct->lim.lam > 180.0 ) - { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "This GTX spans the dateline! This will cause problems." ); - } - - pj_log( ctx, PJ_LOG_DEBUG_MINOR, - "GTX %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", - ct->lim.lam, ct->lim.phi, - ct->ll.lam, ct->ll.phi, - ct->ll.lam + (columns-1)*xstep, ct->ll.phi + (rows-1)*ystep); - - ct->ll.lam *= DEG_TO_RAD; - ct->ll.phi *= DEG_TO_RAD; - ct->del.lam *= DEG_TO_RAD; - ct->del.phi *= DEG_TO_RAD; - ct->cvs = NULL; - - gi->ct = ct; - gi->grid_offset = 40; - gi->format = "gtx"; - - return 1; -} - -/************************************************************************/ -/* pj_gridinfo_init() */ -/* */ -/* Open and parse header details from a datum gridshift file */ -/* returning a list of PJ_GRIDINFOs for the grids in that */ -/* file. This superceeds use of nad_init() for modern */ -/* applications. */ -/************************************************************************/ - -PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) - -{ - char fname[MAX_PATH_FILENAME+1]; - PJ_GRIDINFO *gilist; - PAFile fp; - char header[160]; - size_t header_size = 0; - - errno = pj_errno = 0; - ctx->last_errno = 0; - -/* -------------------------------------------------------------------- */ -/* Initialize a GRIDINFO with stub info we would use if it */ -/* cannot be loaded. */ -/* -------------------------------------------------------------------- */ - gilist = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO)); - if (!gilist) { - pj_ctx_set_errno(ctx, ENOMEM); - return NULL; - } - - gilist->gridname = pj_strdup( gridname ); - if (!gilist->gridname) { - pj_dalloc(gilist); - pj_ctx_set_errno(ctx, ENOMEM); - return NULL; - } - gilist->filename = NULL; - gilist->format = "missing"; - gilist->grid_offset = 0; - gilist->ct = NULL; - gilist->next = NULL; - -/* -------------------------------------------------------------------- */ -/* Open the file using the usual search rules. */ -/* -------------------------------------------------------------------- */ - strcpy(fname, gridname); - if (!(fp = pj_open_lib(ctx, fname, "rb"))) { - ctx->last_errno = 0; /* don't treat as a persistent error */ - return gilist; - } - - gilist->filename = pj_strdup(fname); - if (!gilist->filename) { - pj_dalloc(gilist->gridname); - pj_dalloc(gilist); - pj_ctx_set_errno(ctx, ENOMEM); - return NULL; - } - -/* -------------------------------------------------------------------- */ -/* Load a header, to determine the file type. */ -/* -------------------------------------------------------------------- */ - if( (header_size = pj_ctx_fread( ctx, header, 1, - sizeof(header), fp ) ) != sizeof(header) ) - { - /* some files may be smaller that sizeof(header), eg 160, so */ - ctx->last_errno = 0; /* don't treat as a persistent error */ - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "pj_gridinfo_init: short header read of %d bytes", - (int)header_size ); - } - - pj_ctx_fseek( ctx, fp, SEEK_SET, 0 ); - -/* -------------------------------------------------------------------- */ -/* Determine file type. */ -/* -------------------------------------------------------------------- */ - if( header_size >= 144 + 16 - && strncmp(header + 0, "HEADER", 6) == 0 - && strncmp(header + 96, "W GRID", 6) == 0 - && strncmp(header + 144, "TO NAD83 ", 16) == 0 ) - { - pj_gridinfo_init_ntv1( ctx, fp, gilist ); - } - - else if( header_size >= 48 + 7 - && strncmp(header + 0, "NUM_OREC", 8) == 0 - && strncmp(header + 48, "GS_TYPE", 7) == 0 ) - { - pj_gridinfo_init_ntv2( ctx, fp, gilist ); - } - - else if( strlen(gridname) > 4 - && (strcmp(gridname+strlen(gridname)-3,"gtx") == 0 - || strcmp(gridname+strlen(gridname)-3,"GTX") == 0) ) - { - pj_gridinfo_init_gtx( ctx, fp, gilist ); - } - - else if( header_size >= 9 && strncmp(header + 0,"CTABLE V2",9) == 0 ) - { - struct CTABLE *ct = nad_ctable2_init( ctx, fp ); - - gilist->format = "ctable2"; - gilist->ct = ct; - - if (ct == NULL) - { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "CTABLE V2 ct is NULL."); - } - else - { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "Ctable2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", - ct->id, - ct->lim.lam, ct->lim.phi, - ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG, - (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG, - (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG ); - } - } - - else - { - struct CTABLE *ct = nad_ctable_init( ctx, fp ); - if (ct == NULL) - { - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "CTABLE ct is NULL."); - } else - { - gilist->format = "ctable"; - gilist->ct = ct; - - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "Ctable %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", - ct->id, - ct->lim.lam, ct->lim.phi, - ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG, - (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG, - (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG ); - } - } - - pj_ctx_fclose(ctx, fp); - - return gilist; -} diff --git a/src/pj_gridinfo.cpp b/src/pj_gridinfo.cpp new file mode 100644 index 00000000..de0e8d31 --- /dev/null +++ b/src/pj_gridinfo.cpp @@ -0,0 +1,991 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Functions for handling individual PJ_GRIDINFO's. Includes + * loaders for all formats but CTABLE (in nad_init.c). + * 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. + *****************************************************************************/ + +#define PJ_LIB__ + +#include +#include +#include +#include +#include +#include + +#include "proj_internal.h" +#include "projects.h" + +/************************************************************************/ +/* swap_words() */ +/* */ +/* Convert the byte order of the given word(s) in place. */ +/************************************************************************/ + +static const int byte_order_test = 1; +#define IS_LSB (1 == ((const unsigned char *) (&byte_order_test))[0]) + +static void swap_words( unsigned char *data, int word_size, int word_count ) + +{ + int word; + + 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; + } +} + +/************************************************************************/ +/* to_double() */ +/* */ +/* Returns a double from the pointed data. */ +/************************************************************************/ + +static double to_double( unsigned char* data ) +{ + double d; + memcpy(&d, data, sizeof(d)); + return d; +} + +/************************************************************************/ +/* pj_gridinfo_free() */ +/************************************************************************/ + +void pj_gridinfo_free( projCtx ctx, PJ_GRIDINFO *gi ) + +{ + if( gi == NULL ) + return; + + if( gi->child != NULL ) + { + PJ_GRIDINFO *child, *next; + + for( child = gi->child; child != NULL; child=next) + { + next=child->next; + pj_gridinfo_free( ctx, child ); + } + } + + if( gi->ct != NULL ) + nad_free( gi->ct ); + + free( gi->gridname ); + if( gi->filename != NULL ) + free( gi->filename ); + + pj_dalloc( gi ); +} + +/************************************************************************/ +/* pj_gridinfo_load() */ +/* */ +/* This function is intended to implement delayed loading of */ +/* the data contents of a grid file. The header and related */ +/* stuff are loaded by pj_gridinfo_init(). */ +/************************************************************************/ + +int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) + +{ + struct CTABLE ct_tmp; + + if( gi == NULL || gi->ct == NULL ) + return 0; + + pj_acquire_lock(); + if( gi->ct->cvs != NULL ) + { + pj_release_lock(); + return 1; + } + + memcpy(&ct_tmp, gi->ct, sizeof(struct CTABLE)); + +/* -------------------------------------------------------------------- */ +/* Original platform specific CTable format. */ +/* -------------------------------------------------------------------- */ + if( strcmp(gi->format,"ctable") == 0 ) + { + PAFile fid; + int result; + + fid = pj_open_lib( ctx, gi->filename, "rb" ); + + if( fid == NULL ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + result = nad_ctable_load( ctx, &ct_tmp, fid ); + + pj_ctx_fclose( ctx, fid ); + + gi->ct->cvs = ct_tmp.cvs; + pj_release_lock(); + + return result; + } + +/* -------------------------------------------------------------------- */ +/* CTable2 format. */ +/* -------------------------------------------------------------------- */ + else if( strcmp(gi->format,"ctable2") == 0 ) + { + PAFile fid; + int result; + + fid = pj_open_lib( ctx, gi->filename, "rb" ); + + if( fid == NULL ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + result = nad_ctable2_load( ctx, &ct_tmp, fid ); + + pj_ctx_fclose( ctx, fid ); + + gi->ct->cvs = ct_tmp.cvs; + + pj_release_lock(); + return result; + } + +/* -------------------------------------------------------------------- */ +/* NTv1 format. */ +/* We process one line at a time. Note that the array storage */ +/* direction (e-w) is different in the NTv1 file and what */ +/* the CTABLE is supposed to have. The phi/lam are also */ +/* reversed, and we have to be aware of byte swapping. */ +/* -------------------------------------------------------------------- */ + else if( strcmp(gi->format,"ntv1") == 0 ) + { + double *row_buf; + int row; + PAFile fid; + + fid = pj_open_lib( ctx, gi->filename, "rb" ); + + if( fid == NULL ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); + + row_buf = (double *) pj_malloc(gi->ct->lim.lam * sizeof(double) * 2); + ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); + if( row_buf == NULL || ct_tmp.cvs == NULL ) + { + pj_dalloc( row_buf ); + pj_dalloc( ct_tmp.cvs ); + pj_ctx_set_errno( ctx, ENOMEM ); + pj_release_lock(); + return 0; + } + + for( row = 0; row < gi->ct->lim.phi; row++ ) + { + int i; + FLP *cvs; + double *diff_seconds; + + if( pj_ctx_fread( ctx, row_buf, + sizeof(double), gi->ct->lim.lam * 2, fid ) + != (size_t)( 2 * gi->ct->lim.lam ) ) + { + pj_dalloc( row_buf ); + pj_dalloc( ct_tmp.cvs ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + if( IS_LSB ) + swap_words( (unsigned char *) row_buf, 8, gi->ct->lim.lam*2 ); + + /* convert seconds to radians */ + diff_seconds = row_buf; + + for( i = 0; i < gi->ct->lim.lam; i++ ) + { + cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam + + (gi->ct->lim.lam - i - 1); + + cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); + cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); + } + } + + pj_dalloc( row_buf ); + + pj_ctx_fclose( ctx, fid ); + + gi->ct->cvs = ct_tmp.cvs; + pj_release_lock(); + + return 1; + } + +/* -------------------------------------------------------------------- */ +/* NTv2 format. */ +/* We process one line at a time. Note that the array storage */ +/* direction (e-w) is different in the NTv2 file and what */ +/* the CTABLE is supposed to have. The phi/lam are also */ +/* reversed, and we have to be aware of byte swapping. */ +/* -------------------------------------------------------------------- */ + else if( strcmp(gi->format,"ntv2") == 0 ) + { + float *row_buf; + int row; + PAFile fid; + + pj_log( ctx, PJ_LOG_DEBUG_MINOR, + "NTv2 - loading grid %s", gi->ct->id ); + + fid = pj_open_lib( ctx, gi->filename, "rb" ); + + if( fid == NULL ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); + + row_buf = (float *) pj_malloc(gi->ct->lim.lam * sizeof(float) * 4); + ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); + if( row_buf == NULL || ct_tmp.cvs == NULL ) + { + pj_dalloc( row_buf ); + pj_dalloc( ct_tmp.cvs ); + pj_ctx_set_errno( ctx, ENOMEM ); + pj_release_lock(); + return 0; + } + + for( row = 0; row < gi->ct->lim.phi; row++ ) + { + int i; + FLP *cvs; + float *diff_seconds; + + if( pj_ctx_fread( ctx, row_buf, sizeof(float), + gi->ct->lim.lam*4, fid ) + != (size_t)( 4 * gi->ct->lim.lam ) ) + { + pj_dalloc( row_buf ); + pj_dalloc( ct_tmp.cvs ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + if( gi->must_swap ) + swap_words( (unsigned char *) row_buf, 4, + gi->ct->lim.lam*4 ); + + /* convert seconds to radians */ + diff_seconds = row_buf; + + for( i = 0; i < gi->ct->lim.lam; i++ ) + { + cvs = ct_tmp.cvs + (row) * gi->ct->lim.lam + + (gi->ct->lim.lam - i - 1); + + cvs->phi = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); + cvs->lam = (float) (*(diff_seconds++) * ((M_PI/180.0) / 3600.0)); + diff_seconds += 2; /* skip accuracy values */ + } + } + + pj_dalloc( row_buf ); + + pj_ctx_fclose( ctx, fid ); + + gi->ct->cvs = ct_tmp.cvs; + + pj_release_lock(); + return 1; + } + +/* -------------------------------------------------------------------- */ +/* GTX format. */ +/* -------------------------------------------------------------------- */ + else if( strcmp(gi->format,"gtx") == 0 ) + { + int words = gi->ct->lim.lam * gi->ct->lim.phi; + PAFile fid; + + fid = pj_open_lib( ctx, gi->filename, "rb" ); + + if( fid == NULL ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); + + ct_tmp.cvs = (FLP *) pj_malloc(words*sizeof(float)); + if( ct_tmp.cvs == NULL ) + { + pj_ctx_set_errno( ctx, ENOMEM ); + pj_release_lock(); + return 0; + } + + if( pj_ctx_fread( ctx, ct_tmp.cvs, sizeof(float), words, fid ) + != (size_t)words ) + { + pj_dalloc( ct_tmp.cvs ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return 0; + } + + if( IS_LSB ) + swap_words( (unsigned char *) ct_tmp.cvs, 4, words ); + + pj_ctx_fclose( ctx, fid ); + gi->ct->cvs = ct_tmp.cvs; + pj_release_lock(); + return 1; + } + + else + { + pj_release_lock(); + return 0; + } +} + +/************************************************************************/ +/* gridinfo_parent() */ +/* */ +/* Seek a parent grid file by name from a grid list */ +/************************************************************************/ + +static PJ_GRIDINFO* gridinfo_parent( PJ_GRIDINFO *gilist, + const char *name, int length ) +{ + while( gilist ) + { + if( strncmp(gilist->ct->id,name,length) == 0 ) return gilist; + if( gilist->child ) + { + PJ_GRIDINFO *parent=gridinfo_parent( gilist->child, name, length ); + if( parent ) return parent; + } + gilist=gilist->next; + } + return gilist; +} + +/************************************************************************/ +/* pj_gridinfo_init_ntv2() */ +/* */ +/* Load a ntv2 (.gsb) file. */ +/************************************************************************/ + +static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) +{ + unsigned char header[11*16]; + int num_subfiles, subfile; + int must_swap; + + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(pj_int32) == 4 ); + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(double) == 8 ); + +/* -------------------------------------------------------------------- */ +/* Read the overview header. */ +/* -------------------------------------------------------------------- */ + if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + + if( header[8] == 11 ) + must_swap = !IS_LSB; + else + must_swap = IS_LSB; + +/* -------------------------------------------------------------------- */ +/* Byte swap interesting fields if needed. */ +/* -------------------------------------------------------------------- */ + if( must_swap ) + { + swap_words( header+8, 4, 1 ); + swap_words( header+8+16, 4, 1 ); + swap_words( header+8+32, 4, 1 ); + swap_words( header+8+7*16, 8, 1 ); + swap_words( header+8+8*16, 8, 1 ); + swap_words( header+8+9*16, 8, 1 ); + swap_words( header+8+10*16, 8, 1 ); + } + +/* -------------------------------------------------------------------- */ +/* Get the subfile count out ... all we really use for now. */ +/* -------------------------------------------------------------------- */ + memcpy( &num_subfiles, header+8+32, 4 ); + +/* ==================================================================== */ +/* Step through the subfiles, creating a PJ_GRIDINFO for each. */ +/* ==================================================================== */ + for( subfile = 0; subfile < num_subfiles; subfile++ ) + { + struct CTABLE *ct; + LP ur; + int gs_count; + PJ_GRIDINFO *gi; + +/* -------------------------------------------------------------------- */ +/* Read header. */ +/* -------------------------------------------------------------------- */ + if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + + if( strncmp((const char *) header,"SUB_NAME",8) != 0 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + +/* -------------------------------------------------------------------- */ +/* Byte swap interesting fields if needed. */ +/* -------------------------------------------------------------------- */ + if( must_swap ) + { + swap_words( header+8+16*4, 8, 1 ); + swap_words( header+8+16*5, 8, 1 ); + swap_words( header+8+16*6, 8, 1 ); + swap_words( header+8+16*7, 8, 1 ); + swap_words( header+8+16*8, 8, 1 ); + swap_words( header+8+16*9, 8, 1 ); + swap_words( header+8+16*10, 4, 1 ); + } + +/* -------------------------------------------------------------------- */ +/* Initialize a corresponding "ct" structure. */ +/* -------------------------------------------------------------------- */ + ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); + if (!ct) { + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + strncpy( ct->id, (const char *) header + 8, 8 ); + ct->id[8] = '\0'; + + ct->ll.lam = - to_double(header+7*16+8); /* W_LONG */ + ct->ll.phi = to_double(header+4*16+8); /* S_LAT */ + + ur.lam = - to_double(header+6*16+8); /* E_LONG */ + ur.phi = to_double(header+5*16+8); /* N_LAT */ + + ct->del.lam = to_double(header+9*16+8); + ct->del.phi = to_double(header+8*16+8); + + ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1; + ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1; + + pj_log( ctx, PJ_LOG_DEBUG_MINOR, + "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", + ct->id, + ct->lim.lam, ct->lim.phi, + ct->ll.lam/3600.0, ct->ll.phi/3600.0, + ur.lam/3600.0, ur.phi/3600.0 ); + + ct->ll.lam *= DEG_TO_RAD/3600.0; + ct->ll.phi *= DEG_TO_RAD/3600.0; + ct->del.lam *= DEG_TO_RAD/3600.0; + ct->del.phi *= DEG_TO_RAD/3600.0; + + memcpy( &gs_count, header + 8 + 16*10, 4 ); + if( gs_count != ct->lim.lam * ct->lim.phi ) + { + pj_log( ctx, PJ_LOG_ERROR, + "GS_COUNT(%d) does not match expected cells (%dx%d=%d)", + gs_count, ct->lim.lam, ct->lim.phi, + ct->lim.lam * ct->lim.phi ); + pj_dalloc(ct); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + + ct->cvs = NULL; + +/* -------------------------------------------------------------------- */ +/* Create a new gridinfo for this if we aren't processing the */ +/* 1st subfile, and initialize our grid info. */ +/* -------------------------------------------------------------------- */ + if( subfile == 0 ) + gi = gilist; + else + { + gi = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO)); + if (!gi) { + pj_dalloc(ct); + pj_gridinfo_free(ctx, gilist); + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + + gi->gridname = pj_strdup( gilist->gridname ); + gi->filename = pj_strdup( gilist->filename ); + if (!gi->gridname || !gi->filename) { + pj_gridinfo_free(ctx, gi); + pj_dalloc(ct); + pj_gridinfo_free(ctx, gilist); + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + gi->next = NULL; + } + + gi->must_swap = must_swap; + gi->ct = ct; + gi->format = "ntv2"; + gi->grid_offset = pj_ctx_ftell( ctx, fid ); + +/* -------------------------------------------------------------------- */ +/* Attach to the correct list or sublist. */ +/* -------------------------------------------------------------------- */ + if( strncmp((const char *)header+24,"NONE",4) == 0 ) + { + if( gi != gilist ) + { + PJ_GRIDINFO *lnk; + + for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} + lnk->next = gi; + } + } + + else + { + PJ_GRIDINFO *lnk; + PJ_GRIDINFO *gp = gridinfo_parent(gilist, + (const char*)header+24,8); + + if( gp == NULL ) + { + pj_log( ctx, PJ_LOG_ERROR, + "pj_gridinfo_init_ntv2(): " + "failed to find parent %8.8s for %s.", + (const char *) header+24, gi->ct->id ); + + for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} + lnk->next = gi; + } + else + { + if( gp->child == NULL ) + { + gp->child = gi; + } + else + { + for( lnk = gp->child; lnk->next != NULL; lnk = lnk->next ) {} + lnk->next = gi; + } + } + } + +/* -------------------------------------------------------------------- */ +/* Seek past the data. */ +/* -------------------------------------------------------------------- */ + pj_ctx_fseek( ctx, fid, gs_count * 16, SEEK_CUR ); + } + + return 1; +} + +/************************************************************************/ +/* pj_gridinfo_init_ntv1() */ +/* */ +/* Load an NTv1 style Canadian grid shift file. */ +/************************************************************************/ + +static int pj_gridinfo_init_ntv1( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) + +{ + unsigned char header[192]; /* 12 records of 16 bytes */ + struct CTABLE *ct; + LP ur; + + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(pj_int32) == 4 ); + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(double) == 8 ); + +/* -------------------------------------------------------------------- */ +/* Read the header. */ +/* -------------------------------------------------------------------- */ + if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + +/* -------------------------------------------------------------------- */ +/* Regularize fields of interest. */ +/* -------------------------------------------------------------------- */ + if( IS_LSB ) + { + swap_words( header+8, 4, 1 ); + swap_words( header+24, 8, 1 ); + swap_words( header+40, 8, 1 ); + swap_words( header+56, 8, 1 ); + swap_words( header+72, 8, 1 ); + swap_words( header+88, 8, 1 ); + swap_words( header+104, 8, 1 ); + } + + if( *((int *) (header+8)) != 12 ) + { + pj_log( ctx, PJ_LOG_ERROR, + "NTv1 grid shift file has wrong record count, corrupt?" ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + +/* -------------------------------------------------------------------- */ +/* Fill in CTABLE structure. */ +/* -------------------------------------------------------------------- */ + ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); + if (!ct) { + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + strcpy( ct->id, "NTv1 Grid Shift File" ); + + ct->ll.lam = - to_double(header+72); + ct->ll.phi = to_double(header+24); + ur.lam = - to_double(header+56); + ur.phi = to_double(header+40); + ct->del.lam = to_double(header+104); + ct->del.phi = to_double(header+88); + ct->lim.lam = (pj_int32) (fabs(ur.lam-ct->ll.lam)/ct->del.lam + 0.5) + 1; + ct->lim.phi = (pj_int32) (fabs(ur.phi-ct->ll.phi)/ct->del.phi + 0.5) + 1; + + pj_log( ctx, PJ_LOG_DEBUG_MINOR, + "NTv1 %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", + ct->lim.lam, ct->lim.phi, + ct->ll.lam, ct->ll.phi, ur.lam, ur.phi ); + + ct->ll.lam *= DEG_TO_RAD; + ct->ll.phi *= DEG_TO_RAD; + ct->del.lam *= DEG_TO_RAD; + ct->del.phi *= DEG_TO_RAD; + ct->cvs = NULL; + + gi->ct = ct; + gi->grid_offset = (long) sizeof(header); + gi->format = "ntv1"; + + return 1; +} + +/************************************************************************/ +/* pj_gridinfo_init_gtx() */ +/* */ +/* Load a NOAA .gtx vertical datum shift file. */ +/************************************************************************/ + +static int pj_gridinfo_init_gtx( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) + +{ + unsigned char header[40]; + struct CTABLE *ct; + double xorigin,yorigin,xstep,ystep; + int rows, columns; + + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(pj_int32) == 4 ); + /* cppcheck-suppress sizeofCalculation */ + STATIC_ASSERT( sizeof(double) == 8 ); + +/* -------------------------------------------------------------------- */ +/* Read the header. */ +/* -------------------------------------------------------------------- */ + if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) + { + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + +/* -------------------------------------------------------------------- */ +/* Regularize fields of interest and extract. */ +/* -------------------------------------------------------------------- */ + if( IS_LSB ) + { + swap_words( header+0, 8, 4 ); + swap_words( header+32, 4, 2 ); + } + + memcpy( &yorigin, header+0, 8 ); + memcpy( &xorigin, header+8, 8 ); + memcpy( &ystep, header+16, 8 ); + memcpy( &xstep, header+24, 8 ); + + memcpy( &rows, header+32, 4 ); + memcpy( &columns, header+36, 4 ); + + if( xorigin < -360 || xorigin > 360 + || yorigin < -90 || yorigin > 90 ) + { + pj_log( ctx, PJ_LOG_ERROR, + "gtx file header has invalid extents, corrupt?"); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + return 0; + } + +/* -------------------------------------------------------------------- */ +/* Fill in CTABLE structure. */ +/* -------------------------------------------------------------------- */ + ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); + if (!ct) { + pj_ctx_set_errno(ctx, ENOMEM); + return 0; + } + strcpy( ct->id, "GTX Vertical Grid Shift File" ); + + ct->ll.lam = xorigin; + ct->ll.phi = yorigin; + ct->del.lam = xstep; + ct->del.phi = ystep; + ct->lim.lam = columns; + ct->lim.phi = rows; + + /* some GTX files come in 0-360 and we shift them back into the + expected -180 to 180 range if possible. This does not solve + problems with grids spanning the dateline. */ + if( ct->ll.lam >= 180.0 ) + ct->ll.lam -= 360.0; + + if( ct->ll.lam >= 0.0 && ct->ll.lam + ct->del.lam * ct->lim.lam > 180.0 ) + { + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "This GTX spans the dateline! This will cause problems." ); + } + + pj_log( ctx, PJ_LOG_DEBUG_MINOR, + "GTX %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", + ct->lim.lam, ct->lim.phi, + ct->ll.lam, ct->ll.phi, + ct->ll.lam + (columns-1)*xstep, ct->ll.phi + (rows-1)*ystep); + + ct->ll.lam *= DEG_TO_RAD; + ct->ll.phi *= DEG_TO_RAD; + ct->del.lam *= DEG_TO_RAD; + ct->del.phi *= DEG_TO_RAD; + ct->cvs = NULL; + + gi->ct = ct; + gi->grid_offset = 40; + gi->format = "gtx"; + + return 1; +} + +/************************************************************************/ +/* pj_gridinfo_init() */ +/* */ +/* Open and parse header details from a datum gridshift file */ +/* returning a list of PJ_GRIDINFOs for the grids in that */ +/* file. This superceeds use of nad_init() for modern */ +/* applications. */ +/************************************************************************/ + +PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) + +{ + char fname[MAX_PATH_FILENAME+1]; + PJ_GRIDINFO *gilist; + PAFile fp; + char header[160]; + size_t header_size = 0; + + errno = pj_errno = 0; + ctx->last_errno = 0; + +/* -------------------------------------------------------------------- */ +/* Initialize a GRIDINFO with stub info we would use if it */ +/* cannot be loaded. */ +/* -------------------------------------------------------------------- */ + gilist = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO)); + if (!gilist) { + pj_ctx_set_errno(ctx, ENOMEM); + return NULL; + } + + gilist->gridname = pj_strdup( gridname ); + if (!gilist->gridname) { + pj_dalloc(gilist); + pj_ctx_set_errno(ctx, ENOMEM); + return NULL; + } + gilist->filename = NULL; + gilist->format = "missing"; + gilist->grid_offset = 0; + gilist->ct = NULL; + gilist->next = NULL; + +/* -------------------------------------------------------------------- */ +/* Open the file using the usual search rules. */ +/* -------------------------------------------------------------------- */ + strcpy(fname, gridname); + if (!(fp = pj_open_lib(ctx, fname, "rb"))) { + ctx->last_errno = 0; /* don't treat as a persistent error */ + return gilist; + } + + gilist->filename = pj_strdup(fname); + if (!gilist->filename) { + pj_dalloc(gilist->gridname); + pj_dalloc(gilist); + pj_ctx_set_errno(ctx, ENOMEM); + return NULL; + } + +/* -------------------------------------------------------------------- */ +/* Load a header, to determine the file type. */ +/* -------------------------------------------------------------------- */ + if( (header_size = pj_ctx_fread( ctx, header, 1, + sizeof(header), fp ) ) != sizeof(header) ) + { + /* some files may be smaller that sizeof(header), eg 160, so */ + ctx->last_errno = 0; /* don't treat as a persistent error */ + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "pj_gridinfo_init: short header read of %d bytes", + (int)header_size ); + } + + pj_ctx_fseek( ctx, fp, SEEK_SET, 0 ); + +/* -------------------------------------------------------------------- */ +/* Determine file type. */ +/* -------------------------------------------------------------------- */ + if( header_size >= 144 + 16 + && strncmp(header + 0, "HEADER", 6) == 0 + && strncmp(header + 96, "W GRID", 6) == 0 + && strncmp(header + 144, "TO NAD83 ", 16) == 0 ) + { + pj_gridinfo_init_ntv1( ctx, fp, gilist ); + } + + else if( header_size >= 48 + 7 + && strncmp(header + 0, "NUM_OREC", 8) == 0 + && strncmp(header + 48, "GS_TYPE", 7) == 0 ) + { + pj_gridinfo_init_ntv2( ctx, fp, gilist ); + } + + else if( strlen(gridname) > 4 + && (strcmp(gridname+strlen(gridname)-3,"gtx") == 0 + || strcmp(gridname+strlen(gridname)-3,"GTX") == 0) ) + { + pj_gridinfo_init_gtx( ctx, fp, gilist ); + } + + else if( header_size >= 9 && strncmp(header + 0,"CTABLE V2",9) == 0 ) + { + struct CTABLE *ct = nad_ctable2_init( ctx, fp ); + + gilist->format = "ctable2"; + gilist->ct = ct; + + if (ct == NULL) + { + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "CTABLE V2 ct is NULL."); + } + else + { + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "Ctable2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", + ct->id, + ct->lim.lam, ct->lim.phi, + ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG, + (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG, + (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG ); + } + } + + else + { + struct CTABLE *ct = nad_ctable_init( ctx, fp ); + if (ct == NULL) + { + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "CTABLE ct is NULL."); + } else + { + gilist->format = "ctable"; + gilist->ct = ct; + + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "Ctable %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", + ct->id, + ct->lim.lam, ct->lim.phi, + ct->ll.lam * RAD_TO_DEG, ct->ll.phi * RAD_TO_DEG, + (ct->ll.lam + (ct->lim.lam-1)*ct->del.lam) * RAD_TO_DEG, + (ct->ll.phi + (ct->lim.phi-1)*ct->del.phi) * RAD_TO_DEG ); + } + } + + pj_ctx_fclose(ctx, fp); + + return gilist; +} diff --git a/src/pj_gridlist.c b/src/pj_gridlist.c deleted file mode 100644 index 332de8dd..00000000 --- a/src/pj_gridlist.c +++ /dev/null @@ -1,219 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Code to manage the list of currently loaded (cached) PJ_GRIDINFOs - * See pj_gridinfo.c for details of loading individual grids. - * 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. - *****************************************************************************/ - -#define PJ_LIB__ - -#include -#include -#include - -#include "projects.h" - -static PJ_GRIDINFO *grid_list = NULL; -#define PJ_MAX_PATH_LENGTH 1024 - -/************************************************************************/ -/* pj_deallocate_grids() */ -/* */ -/* Deallocate all loaded grids. */ -/************************************************************************/ - -void pj_deallocate_grids() - -{ - while( grid_list != NULL ) - { - PJ_GRIDINFO *item = grid_list; - grid_list = grid_list->next; - item->next = NULL; - - pj_gridinfo_free( pj_get_default_ctx(), item ); - } -} - -/************************************************************************/ -/* pj_gridlist_merge_grid() */ -/* */ -/* Find/load the named gridfile and merge it into the */ -/* last_nadgrids_list. */ -/************************************************************************/ - -static int pj_gridlist_merge_gridfile( projCtx ctx, - const char *gridname, - PJ_GRIDINFO ***p_gridlist, - int *p_gridcount, - int *p_gridmax ) - -{ - int got_match=0; - PJ_GRIDINFO *this_grid, *tail = NULL; - -/* -------------------------------------------------------------------- */ -/* Try to find in the existing list of loaded grids. Add all */ -/* matching grids as with NTv2 we can get many grids from one */ -/* file (one shared gridname). */ -/* -------------------------------------------------------------------- */ - for( this_grid = grid_list; this_grid != NULL; this_grid = this_grid->next) - { - if( strcmp(this_grid->gridname,gridname) == 0 ) - { - got_match = 1; - - /* don't add to the list if it is invalid. */ - if( this_grid->ct == NULL ) - return 0; - - /* do we need to grow the list? */ - if( *p_gridcount >= *p_gridmax - 2 ) - { - PJ_GRIDINFO **new_list; - int new_max = *p_gridmax + 20; - - new_list = (PJ_GRIDINFO **) pj_calloc(new_max, sizeof(void *)); - if (!new_list) { - pj_ctx_set_errno( ctx, ENOMEM ); - return 0; - } - if( *p_gridlist != NULL ) - { - memcpy( new_list, *p_gridlist, - sizeof(void *) * (*p_gridmax) ); - pj_dalloc( *p_gridlist ); - } - - *p_gridlist = new_list; - *p_gridmax = new_max; - } - - /* add to the list */ - (*p_gridlist)[(*p_gridcount)++] = this_grid; - (*p_gridlist)[*p_gridcount] = NULL; - } - - tail = this_grid; - } - - if( got_match ) - return 1; - -/* -------------------------------------------------------------------- */ -/* Try to load the named grid. */ -/* -------------------------------------------------------------------- */ - this_grid = pj_gridinfo_init( ctx, gridname ); - - if( this_grid == NULL ) - { - return 0; - } - - if( tail != NULL ) - tail->next = this_grid; - else - grid_list = this_grid; - -/* -------------------------------------------------------------------- */ -/* Recurse to add the grid now that it is loaded. */ -/* -------------------------------------------------------------------- */ - return pj_gridlist_merge_gridfile( ctx, gridname, p_gridlist, - p_gridcount, p_gridmax ); -} - -/************************************************************************/ -/* pj_gridlist_from_nadgrids() */ -/* */ -/* This functions loads the list of grids corresponding to a */ -/* particular nadgrids string into a list, and returns it. The */ -/* list is kept around till a request is made with a different */ -/* string in order to cut down on the string parsing cost, and */ -/* the cost of building the list of tables each time. */ -/************************************************************************/ - -PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids, - int *grid_count) - -{ - const char *s; - PJ_GRIDINFO **gridlist = NULL; - int grid_max = 0; - - pj_errno = 0; - *grid_count = 0; - - pj_acquire_lock(); - -/* -------------------------------------------------------------------- */ -/* Loop processing names out of nadgrids one at a time. */ -/* -------------------------------------------------------------------- */ - for( s = nadgrids; *s != '\0'; ) - { - size_t end_char; - int required = 1; - char name[PJ_MAX_PATH_LENGTH]; - - if( *s == '@' ) - { - required = 0; - s++; - } - - for( end_char = 0; - s[end_char] != '\0' && s[end_char] != ','; - end_char++ ) {} - - if( end_char >= sizeof(name) ) - { - pj_dalloc( gridlist ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return NULL; - } - - strncpy( name, s, end_char ); - name[end_char] = '\0'; - - s += end_char; - if( *s == ',' ) - s++; - - if( !pj_gridlist_merge_gridfile( ctx, name, &gridlist, grid_count, - &grid_max) - && required ) - { - pj_dalloc( gridlist ); - pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - pj_release_lock(); - return NULL; - } - else - pj_errno = 0; - } - - pj_release_lock(); - - return gridlist; -} diff --git a/src/pj_gridlist.cpp b/src/pj_gridlist.cpp new file mode 100644 index 00000000..332de8dd --- /dev/null +++ b/src/pj_gridlist.cpp @@ -0,0 +1,219 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Code to manage the list of currently loaded (cached) PJ_GRIDINFOs + * See pj_gridinfo.c for details of loading individual grids. + * 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. + *****************************************************************************/ + +#define PJ_LIB__ + +#include +#include +#include + +#include "projects.h" + +static PJ_GRIDINFO *grid_list = NULL; +#define PJ_MAX_PATH_LENGTH 1024 + +/************************************************************************/ +/* pj_deallocate_grids() */ +/* */ +/* Deallocate all loaded grids. */ +/************************************************************************/ + +void pj_deallocate_grids() + +{ + while( grid_list != NULL ) + { + PJ_GRIDINFO *item = grid_list; + grid_list = grid_list->next; + item->next = NULL; + + pj_gridinfo_free( pj_get_default_ctx(), item ); + } +} + +/************************************************************************/ +/* pj_gridlist_merge_grid() */ +/* */ +/* Find/load the named gridfile and merge it into the */ +/* last_nadgrids_list. */ +/************************************************************************/ + +static int pj_gridlist_merge_gridfile( projCtx ctx, + const char *gridname, + PJ_GRIDINFO ***p_gridlist, + int *p_gridcount, + int *p_gridmax ) + +{ + int got_match=0; + PJ_GRIDINFO *this_grid, *tail = NULL; + +/* -------------------------------------------------------------------- */ +/* Try to find in the existing list of loaded grids. Add all */ +/* matching grids as with NTv2 we can get many grids from one */ +/* file (one shared gridname). */ +/* -------------------------------------------------------------------- */ + for( this_grid = grid_list; this_grid != NULL; this_grid = this_grid->next) + { + if( strcmp(this_grid->gridname,gridname) == 0 ) + { + got_match = 1; + + /* don't add to the list if it is invalid. */ + if( this_grid->ct == NULL ) + return 0; + + /* do we need to grow the list? */ + if( *p_gridcount >= *p_gridmax - 2 ) + { + PJ_GRIDINFO **new_list; + int new_max = *p_gridmax + 20; + + new_list = (PJ_GRIDINFO **) pj_calloc(new_max, sizeof(void *)); + if (!new_list) { + pj_ctx_set_errno( ctx, ENOMEM ); + return 0; + } + if( *p_gridlist != NULL ) + { + memcpy( new_list, *p_gridlist, + sizeof(void *) * (*p_gridmax) ); + pj_dalloc( *p_gridlist ); + } + + *p_gridlist = new_list; + *p_gridmax = new_max; + } + + /* add to the list */ + (*p_gridlist)[(*p_gridcount)++] = this_grid; + (*p_gridlist)[*p_gridcount] = NULL; + } + + tail = this_grid; + } + + if( got_match ) + return 1; + +/* -------------------------------------------------------------------- */ +/* Try to load the named grid. */ +/* -------------------------------------------------------------------- */ + this_grid = pj_gridinfo_init( ctx, gridname ); + + if( this_grid == NULL ) + { + return 0; + } + + if( tail != NULL ) + tail->next = this_grid; + else + grid_list = this_grid; + +/* -------------------------------------------------------------------- */ +/* Recurse to add the grid now that it is loaded. */ +/* -------------------------------------------------------------------- */ + return pj_gridlist_merge_gridfile( ctx, gridname, p_gridlist, + p_gridcount, p_gridmax ); +} + +/************************************************************************/ +/* pj_gridlist_from_nadgrids() */ +/* */ +/* This functions loads the list of grids corresponding to a */ +/* particular nadgrids string into a list, and returns it. The */ +/* list is kept around till a request is made with a different */ +/* string in order to cut down on the string parsing cost, and */ +/* the cost of building the list of tables each time. */ +/************************************************************************/ + +PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids, + int *grid_count) + +{ + const char *s; + PJ_GRIDINFO **gridlist = NULL; + int grid_max = 0; + + pj_errno = 0; + *grid_count = 0; + + pj_acquire_lock(); + +/* -------------------------------------------------------------------- */ +/* Loop processing names out of nadgrids one at a time. */ +/* -------------------------------------------------------------------- */ + for( s = nadgrids; *s != '\0'; ) + { + size_t end_char; + int required = 1; + char name[PJ_MAX_PATH_LENGTH]; + + if( *s == '@' ) + { + required = 0; + s++; + } + + for( end_char = 0; + s[end_char] != '\0' && s[end_char] != ','; + end_char++ ) {} + + if( end_char >= sizeof(name) ) + { + pj_dalloc( gridlist ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return NULL; + } + + strncpy( name, s, end_char ); + name[end_char] = '\0'; + + s += end_char; + if( *s == ',' ) + s++; + + if( !pj_gridlist_merge_gridfile( ctx, name, &gridlist, grid_count, + &grid_max) + && required ) + { + pj_dalloc( gridlist ); + pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); + pj_release_lock(); + return NULL; + } + else + pj_errno = 0; + } + + pj_release_lock(); + + return gridlist; +} diff --git a/src/pj_init.c b/src/pj_init.c deleted file mode 100644 index 36a3276f..00000000 --- a/src/pj_init.c +++ /dev/null @@ -1,870 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Initialize projection object from string definition. Includes - * pj_init(), pj_init_plus() and pj_free() function. - * Author: Gerald Evenden, Frank Warmerdam - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * Copyright (c) 2002, 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 PJ_LIB__ - -#include -#include -#include -#include -#include -#include - -#include "geodesic.h" -#include "proj.h" -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - - -/**************************************************************************************/ -static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { -/*************************************************************************************** - Convert a string (presumably originating from get_init_string) to a paralist. -***************************************************************************************/ - char *c = definition; - paralist *first = 0, *next = 0; - - while (*c) { - /* Find start of next substring */ - while (isspace (*c)) - c++; - - /* Keep a handle to the start of the list, so we have something to return */ - if (0==first) - first = next = pj_mkparam_ws (c); - else - next = next->next = pj_mkparam_ws (c); - if (0==next) - return pj_dealloc_params (ctx, first, ENOMEM); - - /* And skip to the end of the substring */ - while ((!isspace(*c)) && 0!=*c) - c++; - } - - if( next == 0 ) - return 0; - - /* Terminate list and return */ - next->next = 0; - return first; -} - - - -/**************************************************************************************/ -static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { -/*************************************************************************************** - Read a section of an init file. Return its contents as a plain character string. - It is the duty of the caller to free the memory allocated for the string. -***************************************************************************************/ -#define MAX_LINE_LENGTH 1000 - size_t current_buffer_size = 5 * (MAX_LINE_LENGTH + 1); - char *fname, *section; - const char *key; - char *buffer = 0; - char *line = 0; - PAFile fid; - size_t n; - - - line = pj_malloc (MAX_LINE_LENGTH + 1); - if (0==line) - return 0; - - fname = pj_malloc (MAX_PATH_FILENAME+ID_TAG_MAX+3); - if (0==fname) { - pj_dealloc (line); - return 0; - } - - /* Support "init=file:section", "+init=file:section", and "file:section" format */ - key = strstr (name, "init="); - if (0==key) - key = name; - else - key += 5; - if (MAX_PATH_FILENAME + ID_TAG_MAX + 2 < strlen (key)) { - pj_dealloc (fname); - pj_dealloc (line); - return 0; - } - memmove (fname, key, strlen (key) + 1); - - /* Locate the name of the section we search for */ - section = strrchr(fname, ':'); - if (0==section) { - proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING); - pj_dealloc (fname); - pj_dealloc (line); - return 0; - } - *section = 0; - section++; - n = strlen (section); - pj_log (ctx, PJ_LOG_TRACE, - "get_init_string: searching for section [%s] in init file [%s]", - section, fname); - - fid = pj_open_lib (ctx, fname, "rt"); - if (0==fid) { - pj_dealloc (fname); - pj_dealloc (line); - proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); - return 0; - } - - /* Search for section in init file */ - for (;;) { - - /* End of file? */ - if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) { - pj_dealloc (buffer); - pj_dealloc (fname); - pj_dealloc (line); - pj_ctx_fclose (ctx, fid); - proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); - return 0; - } - - /* At start of right section? */ - pj_chomp (line); - if ('<'!=line[0]) - continue; - if (strlen (line) < n + 2) - continue; - if (line[n + 1] != '>') - continue; - if (0==strncmp (line + 1, section, n)) - break; - } - - /* We're at the first line of the right section - copy line to buffer */ - buffer = pj_malloc (current_buffer_size); - if (0==buffer) { - pj_dealloc (fname); - pj_dealloc (line); - pj_ctx_fclose (ctx, fid); - return 0; - } - - /* Skip the "
" indicator, and copy the rest of the line over */ - strcpy (buffer, line + strlen (section) + 2); - - /* Copy the remaining lines of the section to buffer */ - for (;;) { - char *end_i_cator; - size_t next_length, buffer_length; - - /* Did the section end somewhere in the most recently read line? */ - end_i_cator = strchr (buffer, '<'); - if (end_i_cator) { - *end_i_cator = 0; - break; - } - - /* End of file? - done! */ - if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) - break; - - /* Otherwise, handle the line. It MAY be the start of the next section, */ - /* but that will be handled at the start of next trip through the loop */ - buffer_length = strlen (buffer); - pj_chomp (line); /* Remove '#' style comments */ - next_length = strlen (line) + buffer_length + 2; - if (next_length > current_buffer_size) { - char *b = pj_malloc (2 * current_buffer_size); - if (0==b) { - pj_dealloc (buffer); - buffer = 0; - break; - } - strcpy (b, buffer); - current_buffer_size *= 2; - pj_dealloc (buffer); - buffer = b; - } - buffer[buffer_length] = ' '; - strcpy (buffer + buffer_length + 1, line); - } - - pj_ctx_fclose (ctx, fid); - pj_dealloc (fname); - pj_dealloc (line); - if (0==buffer) - return 0; - pj_shrink (buffer); - pj_log (ctx, PJ_LOG_TRACE, "key=%s, value: [%s]", key, buffer); - return buffer; -} - - - -/************************************************************************/ -static paralist *get_init(PJ_CONTEXT *ctx, const char *key, int allow_init_epsg) { -/************************************************************************* -Expand key from buffer or (if not in buffer) from init file -*************************************************************************/ - const char *xkey; - char *definition = 0; - paralist *init_items = 0; - - /* support "init=file:section", "+init=file:section", and "file:section" format */ - xkey = strstr (key, "init="); - if (0==xkey) - xkey = key; - else - xkey += 5; - pj_log (ctx, PJ_LOG_TRACE, "get_init: searching cache for key: [%s]", xkey); - - /* Is file/key pair already in cache? */ - init_items = pj_search_initcache (xkey); - if (init_items) - return init_items; - - if( (strncmp(xkey, "epsg:", 5) == 0 || strncmp(xkey, "IGNF:", 5) == 0) ) { - char unused[256]; - char initname[5]; - int exists; - - memcpy(initname, xkey, 4); - initname[4] = 0; - - if( strncmp(xkey, "epsg:", 5) == 0 ) { - exists = ctx->epsg_file_exists; - if( exists < 0 ) { - exists = pj_find_file(ctx, initname, unused, sizeof(unused)); - ctx->epsg_file_exists = exists; - } - } else { - exists = pj_find_file(ctx, initname, unused, sizeof(unused)); - } - - if( !exists ) { - const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; - char szInitStr[7 + 64]; - PJ_OBJ* src; - const char* proj_string; - - pj_ctx_set_errno( ctx, 0 ); - - if( !allow_init_epsg ) { - pj_log (ctx, PJ_LOG_TRACE, "%s expansion disallowed", xkey); - return 0; - } - if( strlen(xkey) > 64 ) { - return 0; - } - strcpy(szInitStr, "+init="); - strcat(szInitStr, xkey); - - src = proj_obj_create_from_user_input(ctx, szInitStr, optionsProj4Mode); - if( !src ) { - return 0; - } - - proj_string = proj_obj_as_proj_string(ctx, src, PJ_PROJ_4, NULL); - if( !proj_string ) { - proj_obj_destroy(src); - return 0; - } - definition = (char*)calloc(1, strlen(proj_string)+1); - if( definition ) { - strcpy(definition, proj_string); - } - - proj_obj_destroy(src); - } - } - - if( !definition ) { - /* If not, we must read it from file */ - pj_log (ctx, PJ_LOG_TRACE, - "get_init: searching on in init files for [%s]", xkey); - definition = get_init_string (ctx, xkey); - } - - if (0==definition) - return 0; - init_items = string_to_paralist (ctx, definition); - if (init_items) - pj_log (ctx, PJ_LOG_TRACE, "get_init: got [%s], paralist[0,1]: [%s,%s]", - definition, - init_items->param, - init_items->next ? init_items->next->param : "(empty)"); - pj_dealloc (definition); - if (0==init_items) - return 0; - - /* We found it in file - now insert into the cache, before returning */ - pj_insert_initcache (xkey, init_items); - return init_items; -} - - - -static paralist *append_defaults_to_paralist (PJ_CONTEXT *ctx, paralist *start, const char *key, int allow_init_epsg) { - paralist *defaults, *last = 0; - char keystring[ID_TAG_MAX + 20]; - paralist *next, *proj; - int err; - - if (0==start) - return 0; - - if (strlen(key) > ID_TAG_MAX) - return 0; - - /* Set defaults, unless inhibited (either explicitly through a "no_defs" token */ - /* or implicitly, because we are initializing a pipeline) */ - if (pj_param_exists (start, "no_defs")) - return start; - proj = pj_param_exists (start, "proj"); - if (0==proj) - return start; - if (strlen (proj->param) < 6) - return start; - if (0==strcmp ("pipeline", proj->param + 5)) - return start; - - err = pj_ctx_get_errno (ctx); - pj_ctx_set_errno (ctx, 0); - - /* Locate end of start-list */ - for (last = start; last->next; last = last->next); - - strcpy (keystring, "proj_def.dat:"); - strcat (keystring, key); - defaults = get_init (ctx, keystring, allow_init_epsg); - - /* Defaults are optional - so we don't care if we cannot open the file */ - pj_ctx_set_errno (ctx, err); - - if (!defaults) - return last; - - /* Loop over all default items */ - for (next = defaults; next; next = next->next) { - - /* Don't override existing parameter value of same name */ - if (pj_param_exists (start, next->param)) - continue; - - /* Don't default ellipse if datum, ellps or any ellipsoid information is set */ - if (0==strncmp(next->param,"ellps=", 6)) { - if (pj_param_exists (start, "datum")) continue; - if (pj_param_exists (start, "ellps")) continue; - if (pj_param_exists (start, "a")) continue; - if (pj_param_exists (start, "b")) continue; - if (pj_param_exists (start, "rf")) continue; - if (pj_param_exists (start, "f")) continue; - if (pj_param_exists (start, "e")) continue; - if (pj_param_exists (start, "es")) continue; - } - - /* If we're here, it's OK to append the current default item */ - last = last->next = pj_mkparam(next->param); - } - last->next = 0; - - pj_dealloc_params (ctx, defaults, 0); - return last; -} - -/*****************************************************************************/ -static paralist *pj_expand_init_internal(PJ_CONTEXT *ctx, paralist *init, int allow_init_epsg) { -/****************************************************************************** -Append expansion of to the paralist . The expansion is appended, -rather than inserted at 's place, since may contain -overrides to the expansion. These must take precedence, and hence come first -in the expanded list. - -Consider e.g. the key 'foo:bar' which (hypothetically) expands to 'proj=utm -zone=32 ellps=GRS80', i.e. a UTM projection on the GRS80 ellipsoid. - -The expression 'init=foo:bar ellps=intl' will then expand to: - - 'init=foo:bar ellps=intl proj=utm zone=32 ellps=GRS80', - -where 'ellps=intl' precedes 'ellps=GRS80', and hence takes precedence, -turning the expansion into an UTM projection on the Hayford ellipsoid. - -Note that 'init=foo:bar' stays in the list. It is ignored after expansion. - -******************************************************************************/ - paralist *last; - paralist *expn; - - /* Nowhere to start? */ - if (0==init) - return 0; - - expn = get_init(ctx, init->param, allow_init_epsg); - - /* Nothing in expansion? */ - if (0==expn) - return 0; - - /* Locate the end of the list */ - for (last = init; last && last->next; last = last->next); - - /* Then append and return */ - last->next = expn; - return init; -} - -paralist *pj_expand_init(PJ_CONTEXT *ctx, paralist *init) { - return pj_expand_init_internal(ctx, init, TRUE); -} - - -/************************************************************************/ -/* pj_init_plus() */ -/* */ -/* Same as pj_init() except it takes one argument string with */ -/* individual arguments preceded by '+', such as "+proj=utm */ -/* +zone=11 +ellps=WGS84". */ -/************************************************************************/ - -PJ * -pj_init_plus( const char *definition ) - -{ - return pj_init_plus_ctx( pj_get_default_ctx(), definition ); -} - -PJ * -pj_init_plus_ctx( projCtx ctx, const char *definition ) -{ -#define MAX_ARG 200 - char *argv[MAX_ARG]; - char *defn_copy; - int argc = 0, i, blank_count = 0; - PJ *result = NULL; - - /* make a copy that we can manipulate */ - defn_copy = (char *) pj_malloc( strlen(definition)+1 ); - if (!defn_copy) - return NULL; - strcpy( defn_copy, definition ); - - /* split into arguments based on '+' and trim white space */ - - for( i = 0; defn_copy[i] != '\0'; i++ ) - { - switch( defn_copy[i] ) - { - case '+': - if( i == 0 || defn_copy[i-1] == '\0' || blank_count > 0 ) - { - /* trim trailing spaces from the previous param */ - if( blank_count > 0 ) - { - defn_copy[i - blank_count] = '\0'; - blank_count = 0; - } - - if( argc+1 == MAX_ARG ) - { - pj_dalloc( defn_copy ); - pj_ctx_set_errno( ctx, PJD_ERR_UNPARSEABLE_CS_DEF ); - return 0; - } - - argv[argc++] = defn_copy + i + 1; - } - break; - - case ' ': - case '\t': - case '\n': - /* trim leading spaces from the current param */ - if( i == 0 || defn_copy[i-1] == '\0' || argc == 0 || argv[argc-1] == defn_copy + i ) - defn_copy[i] = '\0'; - else - blank_count++; - break; - - default: - /* reset blank_count */ - blank_count = 0; - } - } - /* trim trailing spaces from the last param */ - defn_copy[i - blank_count] = '\0'; - - /* perform actual initialization */ - result = pj_init_ctx( ctx, argc, argv ); - - pj_dalloc( defn_copy ); - return result; -} - - - -/************************************************************************/ -/* pj_init() */ -/* */ -/* Main entry point for initialing a PJ projections */ -/* definition. Note that the projection specific function is */ -/* called to do the initial allocation so it can be created */ -/* large enough to hold projection specific parameters. */ -/************************************************************************/ - -PJ * -pj_init(int argc, char **argv) { - return pj_init_ctx( pj_get_default_ctx(), argc, argv ); -} - - -static PJ_CONSTRUCTOR locate_constructor (const char *name) { - int i; - const char *s; - const PJ_OPERATIONS *operations; - operations = proj_list_operations(); - for (i = 0; (s = operations[i].id) && strcmp(name, s) ; ++i) ; - if (0==s) - return 0; - return (PJ_CONSTRUCTOR) operations[i].proj; -} - - -PJ * -pj_init_ctx(projCtx ctx, int argc, char **argv) { - /* Legacy interface: allow init=epsg:XXXX syntax by default */ - int allow_init_epsg = proj_context_get_use_proj4_init_rules(ctx, TRUE); - return pj_init_ctx_with_allow_init_epsg(ctx, argc, argv, allow_init_epsg); -} - - -PJ * -pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_init_epsg) { - const char *s; - char *name; - PJ_CONSTRUCTOR proj; - paralist *curr, *init, *start; - int i; - int err; - PJ *PIN = 0; - int n_pipelines = 0; - int n_inits = 0; - const PJ_UNITS *units; - const PJ_PRIME_MERIDIANS *prime_meridians; - - if (0==ctx) - ctx = pj_get_default_ctx (); - - ctx->last_errno = 0; - - if (argc <= 0) { - pj_ctx_set_errno (ctx, PJD_ERR_NO_ARGS); - return 0; - } - - /* count occurrences of pipelines and inits */ - for (i = 0; i < argc; ++i) { - if (!strcmp (argv[i], "+proj=pipeline") || !strcmp(argv[i], "proj=pipeline") ) - n_pipelines++; - if (!strncmp (argv[i], "+init=", 6) || !strncmp(argv[i], "init=", 5)) - n_inits++; - } - - /* can't have nested pipelines directly */ - if (n_pipelines > 1) { - pj_ctx_set_errno (ctx, PJD_ERR_MALFORMED_PIPELINE); - return 0; - } - - /* don't allow more than one +init in non-pipeline operations */ - if (n_pipelines == 0 && n_inits > 1) { - pj_ctx_set_errno (ctx, PJD_ERR_TOO_MANY_INITS); - return 0; - } - - - /* put arguments into internal linked list */ - start = curr = pj_mkparam(argv[0]); - if (!curr) - return pj_dealloc_params (ctx, start, ENOMEM); - - for (i = 1; i < argc; ++i) { - curr->next = pj_mkparam(argv[i]); - if (!curr->next) - return pj_dealloc_params (ctx, start, ENOMEM); - curr = curr->next; - } - - - /* Only expand '+init's in non-pipeline operations. '+init's in pipelines are */ - /* expanded in the individual pipeline steps during pipeline initialization. */ - /* Potentially this leads to many nested pipelines, which shouldn't be a */ - /* problem when '+init's are expanded as late as possible. */ - init = pj_param_exists (start, "init"); - if (init && n_pipelines == 0) { - init = pj_expand_init_internal (ctx, init, allow_init_epsg); - if (!init) - return pj_dealloc_params (ctx, start, PJD_ERR_NO_ARGS); - } - if (ctx->last_errno) - return pj_dealloc_params (ctx, start, ctx->last_errno); - - /* Find projection selection */ - curr = pj_param_exists (start, "proj"); - if (0==curr) - return pj_dealloc_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); - name = curr->param; - if (strlen (name) < 6) - return pj_dealloc_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); - name += 5; - - proj = locate_constructor (name); - if (0==proj) - return pj_dealloc_params (ctx, start, PJD_ERR_UNKNOWN_PROJECTION_ID); - - - /* Append general and projection specific defaults to the definition list */ - append_defaults_to_paralist (ctx, start, "general", allow_init_epsg); - append_defaults_to_paralist (ctx, start, name, allow_init_epsg); - - - /* Allocate projection structure */ - PIN = proj(0); - if (0==PIN) - return pj_dealloc_params (ctx, start, ENOMEM); - - - PIN->ctx = ctx; - PIN->params = start; - PIN->is_latlong = 0; - PIN->is_geocent = 0; - PIN->is_long_wrap_set = 0; - PIN->long_wrap_center = 0.0; - strcpy( PIN->axis, "enu" ); - - PIN->gridlist = NULL; - PIN->gridlist_count = 0; - - PIN->vgridlist_geoid = NULL; - PIN->vgridlist_geoid_count = 0; - - /* Set datum parameters. Similarly to +init parameters we want to expand */ - /* +datum parameters as late as possible when dealing with pipelines. */ - /* otherwise only the first occurrence of +datum will be expanded and that */ - if (n_pipelines == 0) { - if (pj_datum_set(ctx, start, PIN)) - return pj_default_destructor (PIN, proj_errno(PIN)); - } - - err = pj_ellipsoid (PIN); - - if (err) { - /* Didn't get an ellps, but doesn't need one: Get a free WGS84 */ - if (PIN->need_ellps) { - pj_log (ctx, PJ_LOG_DEBUG_MINOR, "pj_init_ctx: Must specify ellipsoid or sphere"); - return pj_default_destructor (PIN, proj_errno(PIN)); - } - else { - if (PJD_ERR_MAJOR_AXIS_NOT_GIVEN==proj_errno (PIN)) - proj_errno_reset (PIN); - PIN->f = 1.0/298.257223563; - PIN->a_orig = PIN->a = 6378137.0; - PIN->es_orig = PIN->es = PIN->f*(2-PIN->f); - } - } - PIN->a_orig = PIN->a; - PIN->es_orig = PIN->es; - if (pj_calc_ellipsoid_params (PIN, PIN->a, PIN->es)) - return pj_default_destructor (PIN, PJD_ERR_ECCENTRICITY_IS_ONE); - - /* Now that we have ellipse information check for WGS84 datum */ - if( PIN->datum_type == PJD_3PARAM - && PIN->datum_params[0] == 0.0 - && PIN->datum_params[1] == 0.0 - && PIN->datum_params[2] == 0.0 - && PIN->a == 6378137.0 - && ABS(PIN->es - 0.006694379990) < 0.000000000050 )/*WGS84/GRS80*/ - { - PIN->datum_type = PJD_WGS84; - } - - /* Set PIN->geoc coordinate system */ - PIN->geoc = (PIN->es != 0.0 && pj_param(ctx, start, "bgeoc").i); - - /* Over-ranging flag */ - PIN->over = pj_param(ctx, start, "bover").i; - - /* Vertical datum geoid grids */ - PIN->has_geoid_vgrids = pj_param(ctx, start, "tgeoidgrids").i; - if( PIN->has_geoid_vgrids ) /* we need to mark it as used. */ - pj_param(ctx, start, "sgeoidgrids"); - - /* Longitude center for wrapping */ - PIN->is_long_wrap_set = pj_param(ctx, start, "tlon_wrap").i; - if (PIN->is_long_wrap_set) { - PIN->long_wrap_center = pj_param(ctx, start, "rlon_wrap").f; - /* Don't accept excessive values otherwise we might perform badly */ - /* when correcting longitudes around it */ - /* The test is written this way to error on long_wrap_center "=" NaN */ - if( !(fabs(PIN->long_wrap_center) < 10 * M_TWOPI) ) - return pj_default_destructor (PIN, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); - } - - /* Axis orientation */ - if( (pj_param(ctx, start,"saxis").s) != NULL ) - { - const char *axis_legal = "ewnsud"; - const char *axis_arg = pj_param(ctx, start,"saxis").s; - if( strlen(axis_arg) != 3 ) - return pj_default_destructor (PIN, PJD_ERR_AXIS); - - if( strchr( axis_legal, axis_arg[0] ) == NULL - || strchr( axis_legal, axis_arg[1] ) == NULL - || strchr( axis_legal, axis_arg[2] ) == NULL) - return pj_default_destructor (PIN, PJD_ERR_AXIS); - - /* TODO: it would be nice to validate we don't have on axis repeated */ - strcpy( PIN->axis, axis_arg ); - } - - /* Central meridian */ - PIN->lam0=pj_param(ctx, start, "rlon_0").f; - - /* Central latitude */ - PIN->phi0 = pj_param(ctx, start, "rlat_0").f; - - /* False easting and northing */ - PIN->x0 = pj_param(ctx, start, "dx_0").f; - PIN->y0 = pj_param(ctx, start, "dy_0").f; - PIN->z0 = pj_param(ctx, start, "dz_0").f; - PIN->t0 = pj_param(ctx, start, "dt_0").f; - - /* General scaling factor */ - if (pj_param(ctx, start, "tk_0").i) - PIN->k0 = pj_param(ctx, start, "dk_0").f; - else if (pj_param(ctx, start, "tk").i) - PIN->k0 = pj_param(ctx, start, "dk").f; - else - PIN->k0 = 1.; - if (PIN->k0 <= 0.) - return pj_default_destructor (PIN, PJD_ERR_K_LESS_THAN_ZERO); - - /* Set units */ - units = proj_list_units(); - s = 0; - if ((name = pj_param(ctx, start, "sunits").s) != NULL) { - for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; - if (!s) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); - s = units[i].to_meter; - } - if (s || (s = pj_param(ctx, start, "sto_meter").s)) { - double factor; - int ratio = 0; - - /* ratio number? */ - if (strlen (s) > 1 && s[0] == '1' && s[1]=='/') { - ratio = 1; - s += 2; - } - - factor = pj_strtod(s, 0); - if ((factor <= 0.0) || (1/factor==0)) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); - - PIN->to_meter = ratio? 1 / factor: factor; - PIN->fr_meter = 1 / PIN->to_meter; - - } else - PIN->to_meter = PIN->fr_meter = 1.; - - /* Set vertical units */ - s = 0; - if ((name = pj_param(ctx, start, "svunits").s) != NULL) { - for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; - if (!s) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); - s = units[i].to_meter; - } - if (s || (s = pj_param(ctx, start, "svto_meter").s)) { - PIN->vto_meter = pj_strtod(s, 0); - if (*s == '/') /* ratio number */ - PIN->vto_meter /= pj_strtod(++s, 0); - if (PIN->vto_meter <= 0.0) - return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); - PIN->vfr_meter = 1. / PIN->vto_meter; - } else { - PIN->vto_meter = PIN->to_meter; - PIN->vfr_meter = PIN->fr_meter; - } - - /* Prime meridian */ - prime_meridians = proj_list_prime_meridians(); - s = 0; - if ((name = pj_param(ctx, start, "spm").s) != NULL) { - const char *value = NULL; - char *next_str = NULL; - - for (i = 0; prime_meridians[i].id != NULL; ++i ) - { - if( strcmp(name,prime_meridians[i].id) == 0 ) - { - value = prime_meridians[i].defn; - break; - } - } - - if( value == NULL - && (dmstor_ctx(ctx,name,&next_str) != 0.0 || *name == '0') - && *next_str == '\0' ) - value = name; - - if (!value) - return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_PRIME_MERIDIAN); - PIN->from_greenwich = dmstor_ctx(ctx,value,NULL); - } - else - PIN->from_greenwich = 0.0; - - /* Private object for the geodesic functions */ - PIN->geod = pj_calloc (1, sizeof (struct geod_geodesic)); - if (0==PIN->geod) - return pj_default_destructor (PIN, ENOMEM); - geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es))); - - /* Projection specific initialization */ - err = proj_errno_reset (PIN); - PIN = proj(PIN); - if (proj_errno (PIN)) { - pj_free(PIN); - return 0; - } - proj_errno_restore (PIN, err); - return PIN; -} diff --git a/src/pj_init.cpp b/src/pj_init.cpp new file mode 100644 index 00000000..b09d70ed --- /dev/null +++ b/src/pj_init.cpp @@ -0,0 +1,888 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Initialize projection object from string definition. Includes + * pj_init(), pj_init_plus() and pj_free() function. + * Author: Gerald Evenden, Frank Warmerdam + * + ****************************************************************************** + * Copyright (c) 1995, Gerald Evenden + * Copyright (c) 2002, 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 PJ_LIB__ + +#include +#include +#include +#include +#include +#include + +#include "geodesic.h" +#include "proj.h" +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" + + +/**************************************************************************************/ +static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { +/*************************************************************************************** + Convert a string (presumably originating from get_init_string) to a paralist. +***************************************************************************************/ + char *c = definition; + paralist *first = 0, *next = 0; + + while (*c) { + /* Find start of next substring */ + while (isspace (*c)) + c++; + + /* Keep a handle to the start of the list, so we have something to return */ + if (0==first) + first = next = pj_mkparam_ws (c); + else + next = next->next = pj_mkparam_ws (c); + if (0==next) { + pj_dealloc_params (ctx, first, ENOMEM); + return nullptr; + } + + /* And skip to the end of the substring */ + while ((!isspace(*c)) && 0!=*c) + c++; + } + + if( next == 0 ) + return 0; + + /* Terminate list and return */ + next->next = 0; + return first; +} + + + +/**************************************************************************************/ +static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { +/*************************************************************************************** + Read a section of an init file. Return its contents as a plain character string. + It is the duty of the caller to free the memory allocated for the string. +***************************************************************************************/ +#define MAX_LINE_LENGTH 1000 + size_t current_buffer_size = 5 * (MAX_LINE_LENGTH + 1); + char *fname, *section; + const char *key; + char *buffer = 0; + char *line = 0; + PAFile fid; + size_t n; + + + line = static_cast(pj_malloc (MAX_LINE_LENGTH + 1)); + if (0==line) + return 0; + + fname = static_cast(pj_malloc (MAX_PATH_FILENAME+ID_TAG_MAX+3)); + if (0==fname) { + pj_dealloc (line); + return 0; + } + + /* Support "init=file:section", "+init=file:section", and "file:section" format */ + key = strstr (name, "init="); + if (0==key) + key = name; + else + key += 5; + if (MAX_PATH_FILENAME + ID_TAG_MAX + 2 < strlen (key)) { + pj_dealloc (fname); + pj_dealloc (line); + return 0; + } + memmove (fname, key, strlen (key) + 1); + + /* Locate the name of the section we search for */ + section = strrchr(fname, ':'); + if (0==section) { + proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING); + pj_dealloc (fname); + pj_dealloc (line); + return 0; + } + *section = 0; + section++; + n = strlen (section); + pj_log (ctx, PJ_LOG_TRACE, + "get_init_string: searching for section [%s] in init file [%s]", + section, fname); + + fid = pj_open_lib (ctx, fname, "rt"); + if (0==fid) { + pj_dealloc (fname); + pj_dealloc (line); + proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); + return 0; + } + + /* Search for section in init file */ + for (;;) { + + /* End of file? */ + if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) { + pj_dealloc (buffer); + pj_dealloc (fname); + pj_dealloc (line); + pj_ctx_fclose (ctx, fid); + proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); + return 0; + } + + /* At start of right section? */ + pj_chomp (line); + if ('<'!=line[0]) + continue; + if (strlen (line) < n + 2) + continue; + if (line[n + 1] != '>') + continue; + if (0==strncmp (line + 1, section, n)) + break; + } + + /* We're at the first line of the right section - copy line to buffer */ + buffer = static_cast(pj_malloc (current_buffer_size)); + if (0==buffer) { + pj_dealloc (fname); + pj_dealloc (line); + pj_ctx_fclose (ctx, fid); + return 0; + } + + /* Skip the "
" indicator, and copy the rest of the line over */ + strcpy (buffer, line + strlen (section) + 2); + + /* Copy the remaining lines of the section to buffer */ + for (;;) { + char *end_i_cator; + size_t next_length, buffer_length; + + /* Did the section end somewhere in the most recently read line? */ + end_i_cator = strchr (buffer, '<'); + if (end_i_cator) { + *end_i_cator = 0; + break; + } + + /* End of file? - done! */ + if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) + break; + + /* Otherwise, handle the line. It MAY be the start of the next section, */ + /* but that will be handled at the start of next trip through the loop */ + buffer_length = strlen (buffer); + pj_chomp (line); /* Remove '#' style comments */ + next_length = strlen (line) + buffer_length + 2; + if (next_length > current_buffer_size) { + char *b = static_cast(pj_malloc (2 * current_buffer_size)); + if (0==b) { + pj_dealloc (buffer); + buffer = 0; + break; + } + strcpy (b, buffer); + current_buffer_size *= 2; + pj_dealloc (buffer); + buffer = b; + } + buffer[buffer_length] = ' '; + strcpy (buffer + buffer_length + 1, line); + } + + pj_ctx_fclose (ctx, fid); + pj_dealloc (fname); + pj_dealloc (line); + if (0==buffer) + return 0; + pj_shrink (buffer); + pj_log (ctx, PJ_LOG_TRACE, "key=%s, value: [%s]", key, buffer); + return buffer; +} + + + +/************************************************************************/ +static paralist *get_init(PJ_CONTEXT *ctx, const char *key, int allow_init_epsg) { +/************************************************************************* +Expand key from buffer or (if not in buffer) from init file +*************************************************************************/ + const char *xkey; + char *definition = 0; + paralist *init_items = 0; + + /* support "init=file:section", "+init=file:section", and "file:section" format */ + xkey = strstr (key, "init="); + if (0==xkey) + xkey = key; + else + xkey += 5; + pj_log (ctx, PJ_LOG_TRACE, "get_init: searching cache for key: [%s]", xkey); + + /* Is file/key pair already in cache? */ + init_items = pj_search_initcache (xkey); + if (init_items) + return init_items; + + if( (strncmp(xkey, "epsg:", 5) == 0 || strncmp(xkey, "IGNF:", 5) == 0) ) { + char unused[256]; + char initname[5]; + int exists; + + memcpy(initname, xkey, 4); + initname[4] = 0; + + if( strncmp(xkey, "epsg:", 5) == 0 ) { + exists = ctx->epsg_file_exists; + if( exists < 0 ) { + exists = pj_find_file(ctx, initname, unused, sizeof(unused)); + ctx->epsg_file_exists = exists; + } + } else { + exists = pj_find_file(ctx, initname, unused, sizeof(unused)); + } + + if( !exists ) { + const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; + char szInitStr[7 + 64]; + PJ_OBJ* src; + const char* proj_string; + + pj_ctx_set_errno( ctx, 0 ); + + if( !allow_init_epsg ) { + pj_log (ctx, PJ_LOG_TRACE, "%s expansion disallowed", xkey); + return 0; + } + if( strlen(xkey) > 64 ) { + return 0; + } + strcpy(szInitStr, "+init="); + strcat(szInitStr, xkey); + + src = proj_obj_create_from_user_input(ctx, szInitStr, optionsProj4Mode); + if( !src ) { + return 0; + } + + proj_string = proj_obj_as_proj_string(ctx, src, PJ_PROJ_4, NULL); + if( !proj_string ) { + proj_obj_destroy(src); + return 0; + } + definition = (char*)calloc(1, strlen(proj_string)+1); + if( definition ) { + strcpy(definition, proj_string); + } + + proj_obj_destroy(src); + } + } + + if( !definition ) { + /* If not, we must read it from file */ + pj_log (ctx, PJ_LOG_TRACE, + "get_init: searching on in init files for [%s]", xkey); + definition = get_init_string (ctx, xkey); + } + + if (0==definition) + return 0; + init_items = string_to_paralist (ctx, definition); + if (init_items) + pj_log (ctx, PJ_LOG_TRACE, "get_init: got [%s], paralist[0,1]: [%s,%s]", + definition, + init_items->param, + init_items->next ? init_items->next->param : "(empty)"); + pj_dealloc (definition); + if (0==init_items) + return 0; + + /* We found it in file - now insert into the cache, before returning */ + pj_insert_initcache (xkey, init_items); + return init_items; +} + + + +static paralist *append_defaults_to_paralist (PJ_CONTEXT *ctx, paralist *start, const char *key, int allow_init_epsg) { + paralist *defaults, *last = 0; + char keystring[ID_TAG_MAX + 20]; + paralist *next, *proj; + int err; + + if (0==start) + return 0; + + if (strlen(key) > ID_TAG_MAX) + return 0; + + /* Set defaults, unless inhibited (either explicitly through a "no_defs" token */ + /* or implicitly, because we are initializing a pipeline) */ + if (pj_param_exists (start, "no_defs")) + return start; + proj = pj_param_exists (start, "proj"); + if (0==proj) + return start; + if (strlen (proj->param) < 6) + return start; + if (0==strcmp ("pipeline", proj->param + 5)) + return start; + + err = pj_ctx_get_errno (ctx); + pj_ctx_set_errno (ctx, 0); + + /* Locate end of start-list */ + for (last = start; last->next; last = last->next); + + strcpy (keystring, "proj_def.dat:"); + strcat (keystring, key); + defaults = get_init (ctx, keystring, allow_init_epsg); + + /* Defaults are optional - so we don't care if we cannot open the file */ + pj_ctx_set_errno (ctx, err); + + if (!defaults) + return last; + + /* Loop over all default items */ + for (next = defaults; next; next = next->next) { + + /* Don't override existing parameter value of same name */ + if (pj_param_exists (start, next->param)) + continue; + + /* Don't default ellipse if datum, ellps or any ellipsoid information is set */ + if (0==strncmp(next->param,"ellps=", 6)) { + if (pj_param_exists (start, "datum")) continue; + if (pj_param_exists (start, "ellps")) continue; + if (pj_param_exists (start, "a")) continue; + if (pj_param_exists (start, "b")) continue; + if (pj_param_exists (start, "rf")) continue; + if (pj_param_exists (start, "f")) continue; + if (pj_param_exists (start, "e")) continue; + if (pj_param_exists (start, "es")) continue; + } + + /* If we're here, it's OK to append the current default item */ + last = last->next = pj_mkparam(next->param); + } + last->next = 0; + + pj_dealloc_params (ctx, defaults, 0); + return last; +} + +/*****************************************************************************/ +static paralist *pj_expand_init_internal(PJ_CONTEXT *ctx, paralist *init, int allow_init_epsg) { +/****************************************************************************** +Append expansion of to the paralist . The expansion is appended, +rather than inserted at 's place, since may contain +overrides to the expansion. These must take precedence, and hence come first +in the expanded list. + +Consider e.g. the key 'foo:bar' which (hypothetically) expands to 'proj=utm +zone=32 ellps=GRS80', i.e. a UTM projection on the GRS80 ellipsoid. + +The expression 'init=foo:bar ellps=intl' will then expand to: + + 'init=foo:bar ellps=intl proj=utm zone=32 ellps=GRS80', + +where 'ellps=intl' precedes 'ellps=GRS80', and hence takes precedence, +turning the expansion into an UTM projection on the Hayford ellipsoid. + +Note that 'init=foo:bar' stays in the list. It is ignored after expansion. + +******************************************************************************/ + paralist *last; + paralist *expn; + + /* Nowhere to start? */ + if (0==init) + return 0; + + expn = get_init(ctx, init->param, allow_init_epsg); + + /* Nothing in expansion? */ + if (0==expn) + return 0; + + /* Locate the end of the list */ + for (last = init; last && last->next; last = last->next); + + /* Then append and return */ + last->next = expn; + return init; +} + +paralist *pj_expand_init(PJ_CONTEXT *ctx, paralist *init) { + return pj_expand_init_internal(ctx, init, TRUE); +} + + +/************************************************************************/ +/* pj_init_plus() */ +/* */ +/* Same as pj_init() except it takes one argument string with */ +/* individual arguments preceded by '+', such as "+proj=utm */ +/* +zone=11 +ellps=WGS84". */ +/************************************************************************/ + +PJ * +pj_init_plus( const char *definition ) + +{ + return pj_init_plus_ctx( pj_get_default_ctx(), definition ); +} + +PJ * +pj_init_plus_ctx( projCtx ctx, const char *definition ) +{ +#define MAX_ARG 200 + char *argv[MAX_ARG]; + char *defn_copy; + int argc = 0, i, blank_count = 0; + PJ *result = NULL; + + /* make a copy that we can manipulate */ + defn_copy = (char *) pj_malloc( strlen(definition)+1 ); + if (!defn_copy) + return NULL; + strcpy( defn_copy, definition ); + + /* split into arguments based on '+' and trim white space */ + + for( i = 0; defn_copy[i] != '\0'; i++ ) + { + switch( defn_copy[i] ) + { + case '+': + if( i == 0 || defn_copy[i-1] == '\0' || blank_count > 0 ) + { + /* trim trailing spaces from the previous param */ + if( blank_count > 0 ) + { + defn_copy[i - blank_count] = '\0'; + blank_count = 0; + } + + if( argc+1 == MAX_ARG ) + { + pj_dalloc( defn_copy ); + pj_ctx_set_errno( ctx, PJD_ERR_UNPARSEABLE_CS_DEF ); + return 0; + } + + argv[argc++] = defn_copy + i + 1; + } + break; + + case ' ': + case '\t': + case '\n': + /* trim leading spaces from the current param */ + if( i == 0 || defn_copy[i-1] == '\0' || argc == 0 || argv[argc-1] == defn_copy + i ) + defn_copy[i] = '\0'; + else + blank_count++; + break; + + default: + /* reset blank_count */ + blank_count = 0; + } + } + /* trim trailing spaces from the last param */ + defn_copy[i - blank_count] = '\0'; + + /* perform actual initialization */ + result = pj_init_ctx( ctx, argc, argv ); + + pj_dalloc( defn_copy ); + return result; +} + + + +/************************************************************************/ +/* pj_init() */ +/* */ +/* Main entry point for initialing a PJ projections */ +/* definition. Note that the projection specific function is */ +/* called to do the initial allocation so it can be created */ +/* large enough to hold projection specific parameters. */ +/************************************************************************/ + +PJ * +pj_init(int argc, char **argv) { + return pj_init_ctx( pj_get_default_ctx(), argc, argv ); +} + + +static PJ_CONSTRUCTOR locate_constructor (const char *name) { + int i; + const char *s; + const PJ_OPERATIONS *operations; + operations = proj_list_operations(); + for (i = 0; (s = operations[i].id) && strcmp(name, s) ; ++i) ; + if (0==s) + return 0; + return (PJ_CONSTRUCTOR) operations[i].proj; +} + + +PJ * +pj_init_ctx(projCtx ctx, int argc, char **argv) { + /* Legacy interface: allow init=epsg:XXXX syntax by default */ + int allow_init_epsg = proj_context_get_use_proj4_init_rules(ctx, TRUE); + return pj_init_ctx_with_allow_init_epsg(ctx, argc, argv, allow_init_epsg); +} + + +PJ * +pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_init_epsg) { + const char *s; + char *name; + PJ_CONSTRUCTOR proj; + paralist *curr, *init, *start; + int i; + int err; + PJ *PIN = 0; + int n_pipelines = 0; + int n_inits = 0; + const PJ_UNITS *units; + const PJ_PRIME_MERIDIANS *prime_meridians; + + if (0==ctx) + ctx = pj_get_default_ctx (); + + ctx->last_errno = 0; + + if (argc <= 0) { + pj_ctx_set_errno (ctx, PJD_ERR_NO_ARGS); + return 0; + } + + /* count occurrences of pipelines and inits */ + for (i = 0; i < argc; ++i) { + if (!strcmp (argv[i], "+proj=pipeline") || !strcmp(argv[i], "proj=pipeline") ) + n_pipelines++; + if (!strncmp (argv[i], "+init=", 6) || !strncmp(argv[i], "init=", 5)) + n_inits++; + } + + /* can't have nested pipelines directly */ + if (n_pipelines > 1) { + pj_ctx_set_errno (ctx, PJD_ERR_MALFORMED_PIPELINE); + return 0; + } + + /* don't allow more than one +init in non-pipeline operations */ + if (n_pipelines == 0 && n_inits > 1) { + pj_ctx_set_errno (ctx, PJD_ERR_TOO_MANY_INITS); + return 0; + } + + + /* put arguments into internal linked list */ + start = curr = pj_mkparam(argv[0]); + if (!curr) { + pj_dealloc_params (ctx, start, ENOMEM); + return nullptr; + } + + for (i = 1; i < argc; ++i) { + curr->next = pj_mkparam(argv[i]); + if (!curr->next) { + pj_dealloc_params (ctx, start, ENOMEM); + return nullptr; + } + curr = curr->next; + } + + + /* Only expand '+init's in non-pipeline operations. '+init's in pipelines are */ + /* expanded in the individual pipeline steps during pipeline initialization. */ + /* Potentially this leads to many nested pipelines, which shouldn't be a */ + /* problem when '+init's are expanded as late as possible. */ + init = pj_param_exists (start, "init"); + if (init && n_pipelines == 0) { + init = pj_expand_init_internal (ctx, init, allow_init_epsg); + if (!init) { + pj_dealloc_params (ctx, start, PJD_ERR_NO_ARGS); + return nullptr; + } + } + if (ctx->last_errno) { + pj_dealloc_params (ctx, start, ctx->last_errno); + return nullptr; + } + + /* Find projection selection */ + curr = pj_param_exists (start, "proj"); + if (0==curr) { + pj_dealloc_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); + return nullptr; + } + name = curr->param; + if (strlen (name) < 6) { + pj_dealloc_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); + return nullptr; + } + name += 5; + + proj = locate_constructor (name); + if (0==proj) { + pj_dealloc_params (ctx, start, PJD_ERR_UNKNOWN_PROJECTION_ID); + return nullptr; + } + + + /* Append general and projection specific defaults to the definition list */ + append_defaults_to_paralist (ctx, start, "general", allow_init_epsg); + append_defaults_to_paralist (ctx, start, name, allow_init_epsg); + + + /* Allocate projection structure */ + PIN = proj(0); + if (0==PIN) { + pj_dealloc_params (ctx, start, ENOMEM); + return nullptr; + } + + + PIN->ctx = ctx; + PIN->params = start; + PIN->is_latlong = 0; + PIN->is_geocent = 0; + PIN->is_long_wrap_set = 0; + PIN->long_wrap_center = 0.0; + strcpy( PIN->axis, "enu" ); + + PIN->gridlist = NULL; + PIN->gridlist_count = 0; + + PIN->vgridlist_geoid = NULL; + PIN->vgridlist_geoid_count = 0; + + /* Set datum parameters. Similarly to +init parameters we want to expand */ + /* +datum parameters as late as possible when dealing with pipelines. */ + /* otherwise only the first occurrence of +datum will be expanded and that */ + if (n_pipelines == 0) { + if (pj_datum_set(ctx, start, PIN)) + return pj_default_destructor (PIN, proj_errno(PIN)); + } + + err = pj_ellipsoid (PIN); + + if (err) { + /* Didn't get an ellps, but doesn't need one: Get a free WGS84 */ + if (PIN->need_ellps) { + pj_log (ctx, PJ_LOG_DEBUG_MINOR, "pj_init_ctx: Must specify ellipsoid or sphere"); + return pj_default_destructor (PIN, proj_errno(PIN)); + } + else { + if (PJD_ERR_MAJOR_AXIS_NOT_GIVEN==proj_errno (PIN)) + proj_errno_reset (PIN); + PIN->f = 1.0/298.257223563; + PIN->a_orig = PIN->a = 6378137.0; + PIN->es_orig = PIN->es = PIN->f*(2-PIN->f); + } + } + PIN->a_orig = PIN->a; + PIN->es_orig = PIN->es; + if (pj_calc_ellipsoid_params (PIN, PIN->a, PIN->es)) + return pj_default_destructor (PIN, PJD_ERR_ECCENTRICITY_IS_ONE); + + /* Now that we have ellipse information check for WGS84 datum */ + if( PIN->datum_type == PJD_3PARAM + && PIN->datum_params[0] == 0.0 + && PIN->datum_params[1] == 0.0 + && PIN->datum_params[2] == 0.0 + && PIN->a == 6378137.0 + && ABS(PIN->es - 0.006694379990) < 0.000000000050 )/*WGS84/GRS80*/ + { + PIN->datum_type = PJD_WGS84; + } + + /* Set PIN->geoc coordinate system */ + PIN->geoc = (PIN->es != 0.0 && pj_param(ctx, start, "bgeoc").i); + + /* Over-ranging flag */ + PIN->over = pj_param(ctx, start, "bover").i; + + /* Vertical datum geoid grids */ + PIN->has_geoid_vgrids = pj_param(ctx, start, "tgeoidgrids").i; + if( PIN->has_geoid_vgrids ) /* we need to mark it as used. */ + pj_param(ctx, start, "sgeoidgrids"); + + /* Longitude center for wrapping */ + PIN->is_long_wrap_set = pj_param(ctx, start, "tlon_wrap").i; + if (PIN->is_long_wrap_set) { + PIN->long_wrap_center = pj_param(ctx, start, "rlon_wrap").f; + /* Don't accept excessive values otherwise we might perform badly */ + /* when correcting longitudes around it */ + /* The test is written this way to error on long_wrap_center "=" NaN */ + if( !(fabs(PIN->long_wrap_center) < 10 * M_TWOPI) ) + return pj_default_destructor (PIN, PJD_ERR_LAT_OR_LON_EXCEED_LIMIT); + } + + /* Axis orientation */ + if( (pj_param(ctx, start,"saxis").s) != NULL ) + { + const char *axis_legal = "ewnsud"; + const char *axis_arg = pj_param(ctx, start,"saxis").s; + if( strlen(axis_arg) != 3 ) + return pj_default_destructor (PIN, PJD_ERR_AXIS); + + if( strchr( axis_legal, axis_arg[0] ) == NULL + || strchr( axis_legal, axis_arg[1] ) == NULL + || strchr( axis_legal, axis_arg[2] ) == NULL) + return pj_default_destructor (PIN, PJD_ERR_AXIS); + + /* TODO: it would be nice to validate we don't have on axis repeated */ + strcpy( PIN->axis, axis_arg ); + } + + /* Central meridian */ + PIN->lam0=pj_param(ctx, start, "rlon_0").f; + + /* Central latitude */ + PIN->phi0 = pj_param(ctx, start, "rlat_0").f; + + /* False easting and northing */ + PIN->x0 = pj_param(ctx, start, "dx_0").f; + PIN->y0 = pj_param(ctx, start, "dy_0").f; + PIN->z0 = pj_param(ctx, start, "dz_0").f; + PIN->t0 = pj_param(ctx, start, "dt_0").f; + + /* General scaling factor */ + if (pj_param(ctx, start, "tk_0").i) + PIN->k0 = pj_param(ctx, start, "dk_0").f; + else if (pj_param(ctx, start, "tk").i) + PIN->k0 = pj_param(ctx, start, "dk").f; + else + PIN->k0 = 1.; + if (PIN->k0 <= 0.) + return pj_default_destructor (PIN, PJD_ERR_K_LESS_THAN_ZERO); + + /* Set units */ + units = proj_list_units(); + s = 0; + if ((name = pj_param(ctx, start, "sunits").s) != NULL) { + for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; + if (!s) + return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); + s = units[i].to_meter; + } + if (s || (s = pj_param(ctx, start, "sto_meter").s)) { + double factor; + int ratio = 0; + + /* ratio number? */ + if (strlen (s) > 1 && s[0] == '1' && s[1]=='/') { + ratio = 1; + s += 2; + } + + factor = pj_strtod(s, 0); + if ((factor <= 0.0) || (1/factor==0)) + return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + + PIN->to_meter = ratio? 1 / factor: factor; + PIN->fr_meter = 1 / PIN->to_meter; + + } else + PIN->to_meter = PIN->fr_meter = 1.; + + /* Set vertical units */ + s = 0; + if ((name = pj_param(ctx, start, "svunits").s) != NULL) { + for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; + if (!s) + return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); + s = units[i].to_meter; + } + if (s || (s = pj_param(ctx, start, "svto_meter").s)) { + PIN->vto_meter = pj_strtod(s, 0); + if (*s == '/') /* ratio number */ + PIN->vto_meter /= pj_strtod(++s, 0); + if (PIN->vto_meter <= 0.0) + return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); + PIN->vfr_meter = 1. / PIN->vto_meter; + } else { + PIN->vto_meter = PIN->to_meter; + PIN->vfr_meter = PIN->fr_meter; + } + + /* Prime meridian */ + prime_meridians = proj_list_prime_meridians(); + s = 0; + if ((name = pj_param(ctx, start, "spm").s) != NULL) { + const char *value = NULL; + char *next_str = NULL; + + for (i = 0; prime_meridians[i].id != NULL; ++i ) + { + if( strcmp(name,prime_meridians[i].id) == 0 ) + { + value = prime_meridians[i].defn; + break; + } + } + + if( value == NULL + && (dmstor_ctx(ctx,name,&next_str) != 0.0 || *name == '0') + && *next_str == '\0' ) + value = name; + + if (!value) + return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_PRIME_MERIDIAN); + PIN->from_greenwich = dmstor_ctx(ctx,value,NULL); + } + else + PIN->from_greenwich = 0.0; + + /* Private object for the geodesic functions */ + PIN->geod = static_cast(pj_calloc (1, sizeof (struct geod_geodesic))); + if (0==PIN->geod) + return pj_default_destructor (PIN, ENOMEM); + geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es))); + + /* Projection specific initialization */ + err = proj_errno_reset (PIN); + PIN = proj(PIN); + if (proj_errno (PIN)) { + pj_free(PIN); + return 0; + } + proj_errno_restore (PIN, err); + return PIN; +} diff --git a/src/pj_initcache.c b/src/pj_initcache.c deleted file mode 100644 index 3c347e4b..00000000 --- a/src/pj_initcache.c +++ /dev/null @@ -1,184 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: init file definition cache. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2009, 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 "projects.h" - -static int cache_count = 0; -static int cache_alloc = 0; -static char **cache_key = NULL; -static paralist **cache_paralist = NULL; - -/************************************************************************/ -/* pj_clone_paralist() */ -/* */ -/* Allocate a copy of a parameter list. */ -/************************************************************************/ - -paralist *pj_clone_paralist( const paralist *list) -{ - paralist *list_copy = NULL, *next_copy = NULL; - - for( ; list != NULL; list = list->next ) - { - paralist *newitem = (paralist *) - pj_malloc(sizeof(paralist) + strlen(list->param)); - - newitem->used = 0; - newitem->next = 0; - strcpy( newitem->param, list->param ); - - if( list_copy == NULL ) - list_copy = newitem; - else - next_copy->next = newitem; - - next_copy = newitem; - } - - return list_copy; -} - -/************************************************************************/ -/* pj_clear_initcache() */ -/* */ -/* Clear out all memory held in the init file cache. */ -/************************************************************************/ - -void pj_clear_initcache() -{ - if( cache_alloc > 0 ) - { - int i; - - pj_acquire_lock(); - - for( i = 0; i < cache_count; i++ ) - { - paralist *n, *t = cache_paralist[i]; - - pj_dalloc( cache_key[i] ); - - /* free parameter list elements */ - for (; t != NULL; t = n) { - n = t->next; - pj_dalloc(t); - } - } - - pj_dalloc( cache_key ); - pj_dalloc( cache_paralist ); - cache_count = 0; - cache_alloc= 0; - cache_key = NULL; - cache_paralist = NULL; - - pj_release_lock(); - } -} - -/************************************************************************/ -/* pj_search_initcache() */ -/* */ -/* Search for a matching definition in the init cache. */ -/************************************************************************/ - -paralist *pj_search_initcache( const char *filekey ) - -{ - int i; - paralist *result = NULL; - - pj_acquire_lock(); - - for( i = 0; result == NULL && i < cache_count; i++) - { - if( strcmp(filekey,cache_key[i]) == 0 ) - { - result = pj_clone_paralist( cache_paralist[i] ); - } - } - - pj_release_lock(); - - return result; -} - -/************************************************************************/ -/* pj_insert_initcache() */ -/* */ -/* Insert a paralist definition in the init file cache. */ -/************************************************************************/ - -void pj_insert_initcache( const char *filekey, const paralist *list ) - -{ - pj_acquire_lock(); - - /* - ** Grow list if required. - */ - if( cache_count == cache_alloc ) - { - char **cache_key_new; - paralist **cache_paralist_new; - - cache_alloc = cache_alloc * 2 + 15; - - cache_key_new = (char **) pj_malloc(sizeof(char*) * cache_alloc); - if( cache_key && cache_count ) - { - memcpy( cache_key_new, cache_key, sizeof(char*) * cache_count); - } - pj_dalloc( cache_key ); - cache_key = cache_key_new; - - cache_paralist_new = (paralist **) - pj_malloc(sizeof(paralist*) * cache_alloc); - if( cache_paralist && cache_count ) - { - memcpy( cache_paralist_new, cache_paralist, - sizeof(paralist*) * cache_count ); - } - pj_dalloc( cache_paralist ); - cache_paralist = cache_paralist_new; - } - - /* - ** Duplicate the filekey and paralist, and insert in cache. - */ - cache_key[cache_count] = (char *) pj_malloc(strlen(filekey)+1); - strcpy( cache_key[cache_count], filekey ); - - cache_paralist[cache_count] = pj_clone_paralist( list ); - - cache_count++; - - pj_release_lock(); -} - diff --git a/src/pj_initcache.cpp b/src/pj_initcache.cpp new file mode 100644 index 00000000..3c347e4b --- /dev/null +++ b/src/pj_initcache.cpp @@ -0,0 +1,184 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: init file definition cache. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2009, 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 "projects.h" + +static int cache_count = 0; +static int cache_alloc = 0; +static char **cache_key = NULL; +static paralist **cache_paralist = NULL; + +/************************************************************************/ +/* pj_clone_paralist() */ +/* */ +/* Allocate a copy of a parameter list. */ +/************************************************************************/ + +paralist *pj_clone_paralist( const paralist *list) +{ + paralist *list_copy = NULL, *next_copy = NULL; + + for( ; list != NULL; list = list->next ) + { + paralist *newitem = (paralist *) + pj_malloc(sizeof(paralist) + strlen(list->param)); + + newitem->used = 0; + newitem->next = 0; + strcpy( newitem->param, list->param ); + + if( list_copy == NULL ) + list_copy = newitem; + else + next_copy->next = newitem; + + next_copy = newitem; + } + + return list_copy; +} + +/************************************************************************/ +/* pj_clear_initcache() */ +/* */ +/* Clear out all memory held in the init file cache. */ +/************************************************************************/ + +void pj_clear_initcache() +{ + if( cache_alloc > 0 ) + { + int i; + + pj_acquire_lock(); + + for( i = 0; i < cache_count; i++ ) + { + paralist *n, *t = cache_paralist[i]; + + pj_dalloc( cache_key[i] ); + + /* free parameter list elements */ + for (; t != NULL; t = n) { + n = t->next; + pj_dalloc(t); + } + } + + pj_dalloc( cache_key ); + pj_dalloc( cache_paralist ); + cache_count = 0; + cache_alloc= 0; + cache_key = NULL; + cache_paralist = NULL; + + pj_release_lock(); + } +} + +/************************************************************************/ +/* pj_search_initcache() */ +/* */ +/* Search for a matching definition in the init cache. */ +/************************************************************************/ + +paralist *pj_search_initcache( const char *filekey ) + +{ + int i; + paralist *result = NULL; + + pj_acquire_lock(); + + for( i = 0; result == NULL && i < cache_count; i++) + { + if( strcmp(filekey,cache_key[i]) == 0 ) + { + result = pj_clone_paralist( cache_paralist[i] ); + } + } + + pj_release_lock(); + + return result; +} + +/************************************************************************/ +/* pj_insert_initcache() */ +/* */ +/* Insert a paralist definition in the init file cache. */ +/************************************************************************/ + +void pj_insert_initcache( const char *filekey, const paralist *list ) + +{ + pj_acquire_lock(); + + /* + ** Grow list if required. + */ + if( cache_count == cache_alloc ) + { + char **cache_key_new; + paralist **cache_paralist_new; + + cache_alloc = cache_alloc * 2 + 15; + + cache_key_new = (char **) pj_malloc(sizeof(char*) * cache_alloc); + if( cache_key && cache_count ) + { + memcpy( cache_key_new, cache_key, sizeof(char*) * cache_count); + } + pj_dalloc( cache_key ); + cache_key = cache_key_new; + + cache_paralist_new = (paralist **) + pj_malloc(sizeof(paralist*) * cache_alloc); + if( cache_paralist && cache_count ) + { + memcpy( cache_paralist_new, cache_paralist, + sizeof(paralist*) * cache_count ); + } + pj_dalloc( cache_paralist ); + cache_paralist = cache_paralist_new; + } + + /* + ** Duplicate the filekey and paralist, and insert in cache. + */ + cache_key[cache_count] = (char *) pj_malloc(strlen(filekey)+1); + strcpy( cache_key[cache_count], filekey ); + + cache_paralist[cache_count] = pj_clone_paralist( list ); + + cache_count++; + + pj_release_lock(); +} + diff --git a/src/pj_internal.c b/src/pj_internal.c deleted file mode 100644 index 7eb917be..00000000 --- a/src/pj_internal.c +++ /dev/null @@ -1,445 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: This is primarily material originating from pj_obs_api.c - * (now proj_4D_api.c), that does not fit into the API - * category. Hence this pile of tubings and fittings for - * PROJ.4 internal plumbing. - * - * Author: Thomas Knudsen, thokn@sdfe.dk, 2017-07-05 - * - ****************************************************************************** - * Copyright (c) 2016, 2017, 2018, 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. - *****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "geodesic.h" -#include "proj_internal.h" -#include "projects.h" - - -enum pj_io_units pj_left (PJ *P) { - enum pj_io_units u = P->inverted? P->right: P->left; - if (u==PJ_IO_UNITS_CLASSIC) - return PJ_IO_UNITS_PROJECTED; - return u; -} - -enum pj_io_units pj_right (PJ *P) { - enum pj_io_units u = P->inverted? P->left: P->right; - if (u==PJ_IO_UNITS_CLASSIC) - return PJ_IO_UNITS_PROJECTED; - return u; -} - - -/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than constants */ -PJ_COORD proj_coord_error (void) { - PJ_COORD c; - c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL; - return c; -} - - - -/**************************************************************************************/ -PJ_COORD pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { -/*************************************************************************************** -Behave mostly as proj_trans, but attempt to use 2D interfaces only. -Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement -chained calls starting out with a call to its 2D interface. -***************************************************************************************/ - if (0==P) - return coo; - if (P->inverted) - direction = -direction; - switch (direction) { - case PJ_FWD: - coo.xy = pj_fwd (coo.lp, P); - return coo; - case PJ_INV: - coo.lp = pj_inv (coo.xy, P); - return coo; - case PJ_IDENT: - return coo; - default: - break; - } - proj_errno_set (P, EINVAL); - return proj_coord_error (); -} - - -/**************************************************************************************/ -PJ_COORD pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { -/*************************************************************************************** -Companion to pj_approx_2D_trans. - -Behave mostly as proj_trans, but attempt to use 3D interfaces only. -Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement -chained calls starting out with a call to its 3D interface. -***************************************************************************************/ - if (0==P) - return coo; - if (P->inverted) - direction = -direction; - switch (direction) { - case PJ_FWD: - coo.xyz = pj_fwd3d (coo.lpz, P); - return coo; - case PJ_INV: - coo.lpz = pj_inv3d (coo.xyz, P); - return coo; - case PJ_IDENT: - return coo; - default: - break; - } - proj_errno_set (P, EINVAL); - return proj_coord_error (); -} - -/**************************************************************************************/ -int pj_has_inverse(PJ *P) { -/*************************************************************************************** -Check if a a PJ has an inverse. -***************************************************************************************/ - return ( (P->inverted && (P->fwd || P->fwd3d || P->fwd4d) ) || - ( P->inv || P->inv3d || P->inv4d) ); -} - - -/* Move P to a new context - or to the default context if 0 is specified */ -void proj_context_set (PJ *P, PJ_CONTEXT *ctx) { - if (0==ctx) - ctx = pj_get_default_ctx (); - pj_set_ctx (P, ctx); -} - - -void proj_context_inherit (PJ *parent, PJ *child) { - if (0==parent) - pj_set_ctx (child, pj_get_default_ctx()); - else - pj_set_ctx (child, pj_get_ctx(parent)); -} - - - -/*****************************************************************************/ -char *pj_chomp (char *c) { -/****************************************************************************** -Strip pre- and postfix whitespace. Inline comments (indicated by '#') are -considered whitespace. -******************************************************************************/ - size_t i, n; - char *comment; - char *start = c; - - if (0==c) - return 0; - - comment = strchr (c, '#'); - if (comment) - *comment = 0; - - n = strlen (c); - if (0==n) - return c; - - /* Eliminate postfix whitespace */ - for (i = n - 1; (i > 0) && (isspace (c[i]) || ';'==c[i]); i--) - c[i] = 0; - - /* Find start of non-whitespace */ - while (0 != *start && (';'==*start || isspace (*start))) - start++; - - n = strlen (start); - if (0==n) { - c[0] = 0; - return c; - } - - memmove (c, start, n + 1); - return c; -} - - - -/*****************************************************************************/ -char *pj_shrink (char *c) { -/****************************************************************************** -Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy, -consuming their surrounding whitespace. -******************************************************************************/ - size_t i, j, n; - - /* Flag showing that a whitespace (ws) has been written after last non-ws */ - size_t ws; - - if (0==c) - return 0; - - pj_chomp (c); - n = strlen (c); - - /* First collapse repeated whitespace (including +/;) */ - i = 0; - ws = 0; - for (j = 0; j < n; j++) { - - /* Eliminate prefix '+', only if preceded by whitespace */ - /* (i.e. keep it in 1.23e+08) */ - if ((i > 0) && ('+'==c[j]) && ws) - c[j] = ' '; - if ((i==0) && ('+'==c[j])) - c[j] = ' '; - - if (isspace (c[j]) || ';'==c[j]) { - if (0==ws && (i > 0)) - c[i++] = ' '; - ws = 1; - continue; - } - else { - ws = 0; - c[i++] = c[j]; - } - } - c[i] = 0; - n = strlen(c); - - /* Then make ',' and '=' greedy */ - i = 0; - for (j = 0; j < n; j++) { - if (i==0) { - c[i++] = c[j]; - continue; - } - - /* Skip space before '='/',' */ - if ('='==c[j] || ','==c[j]) { - if (c[i - 1]==' ') - c[i - 1] = c[j]; - else - c[i++] = c[j]; - continue; - } - - if (' '==c[j] && ('='==c[i - 1] || ','==c[i - 1]) ) - continue; - - c[i++] = c[j]; - } - c[i] = 0; - return c; -} - - - -/*****************************************************************************/ -size_t pj_trim_argc (char *args) { -/****************************************************************************** -Trim all unnecessary whitespace (and non-essential syntactic tokens) from the -argument string, args, and count its number of elements. -******************************************************************************/ - size_t i, m, n; - pj_shrink (args); - n = strlen (args); - if (n==0) - return 0; - for (i = m = 0; i < n; i++) { - if (' '==args[i]) { - args[i] = 0; - m++; - } - } - return m + 1; -} - - - -/*****************************************************************************/ -char **pj_trim_argv (size_t argc, char *args) { -/****************************************************************************** -Create an argv-style array from elements placed in the argument string, args. - -args is a trimmed string as returned by pj_trim_argc(), and argc is the number -of trimmed strings found (i.e. the return value of pj_trim_args()). Hence, - int argc = pj_trim_argc (args); - char **argv = pj_trim_argv (argc, args); -will produce a classic style (argc, argv) pair from a string of whitespace -separated args. No new memory is allocated for storing the individual args -(they stay in the args string), but for the pointers to the args a new array -is allocated and returned. - -It is the duty of the caller to free this array. -******************************************************************************/ - size_t i, j; - char **argv; - - if (0==args) - return 0; - if (0==argc) - return 0; - - - /* turn the input string into an array of strings */ - argv = (char **) calloc (argc, sizeof (char *)); - if (0==argv) - return 0; - argv[0] = args; - j = 1; - for (i = 0; ; i++) { - if (0==args[i]) { - argv[j++] = args + (i + 1); - } - if (j==argc) - break; - } - return argv; -} - - - -/*****************************************************************************/ -char *pj_make_args (size_t argc, char **argv) { -/****************************************************************************** -pj_make_args is the inverse of the pj_trim_argc/pj_trim_argv combo: It -converts free format command line input to something proj_create can consume. - -Allocates, and returns, an array of char, large enough to hold a whitespace -separated copy of the args in argv. It is the duty of the caller to free this -array. -******************************************************************************/ - size_t i, n; - char *p; - - for (i = n = 0; i < argc; i++) - n += strlen (argv[i]); - - p = pj_calloc (n + argc + 1, sizeof (char)); - if (0==p) - return 0; - if (0==argc) - return p; - - for (i = 0; i < argc; i++) { - strcat (p, argv[i]); - strcat (p, " "); - } - return pj_shrink (p); -} - - - -/*****************************************************************************/ -void proj_context_errno_set (PJ_CONTEXT *ctx, int err) { -/****************************************************************************** -Raise an error directly on a context, without going through a PJ belonging -to that context. -******************************************************************************/ - if (0==ctx) - ctx = pj_get_default_ctx(); - pj_ctx_set_errno (ctx, err); -} - -/* logging */ - -/* pj_vlog resides in pj_log.c and relates to pj_log as vsprintf relates to sprintf */ -void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ); - - -/***************************************************************************************/ -PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { -/**************************************************************************************** - Set logging level 0-3. Higher number means more debug info. 0 turns it off -****************************************************************************************/ - PJ_LOG_LEVEL previous; - if (0==ctx) - ctx = pj_get_default_ctx(); - if (0==ctx) - return PJ_LOG_TELL; - previous = abs (ctx->debug_level); - if (PJ_LOG_TELL==log_level) - return previous; - ctx->debug_level = log_level; - return previous; -} - - -/*****************************************************************************/ -void proj_log_error (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting the most severe events. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args); - va_end( args ); -} - - -/*****************************************************************************/ -void proj_log_debug (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting debugging information. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args); - va_end( args ); -} - - -/*****************************************************************************/ -void proj_log_trace (PJ *P, const char *fmt, ...) { -/****************************************************************************** - For reporting embarrasingly detailed debugging information. -******************************************************************************/ - va_list args; - va_start( args, fmt ); - pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args); - va_end( args ); -} - - -/*****************************************************************************/ -void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { -/****************************************************************************** - Put a new logging function into P's context. The opaque object app_data is - passed as first arg at each call to the logger -******************************************************************************/ - if (0==ctx) - pj_get_default_ctx (); - if (0==ctx) - return; - ctx->app_data = app_data; - if (0!=logf) - ctx->logger = logf; -} diff --git a/src/pj_internal.cpp b/src/pj_internal.cpp new file mode 100644 index 00000000..ac9fe1e0 --- /dev/null +++ b/src/pj_internal.cpp @@ -0,0 +1,445 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: This is primarily material originating from pj_obs_api.c + * (now proj_4D_api.c), that does not fit into the API + * category. Hence this pile of tubings and fittings for + * PROJ.4 internal plumbing. + * + * Author: Thomas Knudsen, thokn@sdfe.dk, 2017-07-05 + * + ****************************************************************************** + * Copyright (c) 2016, 2017, 2018, 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "geodesic.h" +#include "proj_internal.h" +#include "projects.h" + + +enum pj_io_units pj_left (PJ *P) { + enum pj_io_units u = P->inverted? P->right: P->left; + if (u==PJ_IO_UNITS_CLASSIC) + return PJ_IO_UNITS_PROJECTED; + return u; +} + +enum pj_io_units pj_right (PJ *P) { + enum pj_io_units u = P->inverted? P->left: P->right; + if (u==PJ_IO_UNITS_CLASSIC) + return PJ_IO_UNITS_PROJECTED; + return u; +} + + +/* Work around non-constness of MSVC HUGE_VAL by providing functions rather than constants */ +PJ_COORD proj_coord_error (void) { + PJ_COORD c; + c.v[0] = c.v[1] = c.v[2] = c.v[3] = HUGE_VAL; + return c; +} + + + +/**************************************************************************************/ +PJ_COORD pj_approx_2D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { +/*************************************************************************************** +Behave mostly as proj_trans, but attempt to use 2D interfaces only. +Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement +chained calls starting out with a call to its 2D interface. +***************************************************************************************/ + if (0==P) + return coo; + if (P->inverted) + direction = static_cast(-direction); + switch (direction) { + case PJ_FWD: + coo.xy = pj_fwd (coo.lp, P); + return coo; + case PJ_INV: + coo.lp = pj_inv (coo.xy, P); + return coo; + case PJ_IDENT: + return coo; + default: + break; + } + proj_errno_set (P, EINVAL); + return proj_coord_error (); +} + + +/**************************************************************************************/ +PJ_COORD pj_approx_3D_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coo) { +/*************************************************************************************** +Companion to pj_approx_2D_trans. + +Behave mostly as proj_trans, but attempt to use 3D interfaces only. +Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement +chained calls starting out with a call to its 3D interface. +***************************************************************************************/ + if (0==P) + return coo; + if (P->inverted) + direction = static_cast(-direction); + switch (direction) { + case PJ_FWD: + coo.xyz = pj_fwd3d (coo.lpz, P); + return coo; + case PJ_INV: + coo.lpz = pj_inv3d (coo.xyz, P); + return coo; + case PJ_IDENT: + return coo; + default: + break; + } + proj_errno_set (P, EINVAL); + return proj_coord_error (); +} + +/**************************************************************************************/ +int pj_has_inverse(PJ *P) { +/*************************************************************************************** +Check if a a PJ has an inverse. +***************************************************************************************/ + return ( (P->inverted && (P->fwd || P->fwd3d || P->fwd4d) ) || + ( P->inv || P->inv3d || P->inv4d) ); +} + + +/* Move P to a new context - or to the default context if 0 is specified */ +void proj_context_set (PJ *P, PJ_CONTEXT *ctx) { + if (0==ctx) + ctx = pj_get_default_ctx (); + pj_set_ctx (P, ctx); +} + + +void proj_context_inherit (PJ *parent, PJ *child) { + if (0==parent) + pj_set_ctx (child, pj_get_default_ctx()); + else + pj_set_ctx (child, pj_get_ctx(parent)); +} + + + +/*****************************************************************************/ +char *pj_chomp (char *c) { +/****************************************************************************** +Strip pre- and postfix whitespace. Inline comments (indicated by '#') are +considered whitespace. +******************************************************************************/ + size_t i, n; + char *comment; + char *start = c; + + if (0==c) + return 0; + + comment = strchr (c, '#'); + if (comment) + *comment = 0; + + n = strlen (c); + if (0==n) + return c; + + /* Eliminate postfix whitespace */ + for (i = n - 1; (i > 0) && (isspace (c[i]) || ';'==c[i]); i--) + c[i] = 0; + + /* Find start of non-whitespace */ + while (0 != *start && (';'==*start || isspace (*start))) + start++; + + n = strlen (start); + if (0==n) { + c[0] = 0; + return c; + } + + memmove (c, start, n + 1); + return c; +} + + + +/*****************************************************************************/ +char *pj_shrink (char *c) { +/****************************************************************************** +Collapse repeated whitespace. Remove '+' and ';'. Make ',' and '=' greedy, +consuming their surrounding whitespace. +******************************************************************************/ + size_t i, j, n; + + /* Flag showing that a whitespace (ws) has been written after last non-ws */ + size_t ws; + + if (0==c) + return 0; + + pj_chomp (c); + n = strlen (c); + + /* First collapse repeated whitespace (including +/;) */ + i = 0; + ws = 0; + for (j = 0; j < n; j++) { + + /* Eliminate prefix '+', only if preceded by whitespace */ + /* (i.e. keep it in 1.23e+08) */ + if ((i > 0) && ('+'==c[j]) && ws) + c[j] = ' '; + if ((i==0) && ('+'==c[j])) + c[j] = ' '; + + if (isspace (c[j]) || ';'==c[j]) { + if (0==ws && (i > 0)) + c[i++] = ' '; + ws = 1; + continue; + } + else { + ws = 0; + c[i++] = c[j]; + } + } + c[i] = 0; + n = strlen(c); + + /* Then make ',' and '=' greedy */ + i = 0; + for (j = 0; j < n; j++) { + if (i==0) { + c[i++] = c[j]; + continue; + } + + /* Skip space before '='/',' */ + if ('='==c[j] || ','==c[j]) { + if (c[i - 1]==' ') + c[i - 1] = c[j]; + else + c[i++] = c[j]; + continue; + } + + if (' '==c[j] && ('='==c[i - 1] || ','==c[i - 1]) ) + continue; + + c[i++] = c[j]; + } + c[i] = 0; + return c; +} + + + +/*****************************************************************************/ +size_t pj_trim_argc (char *args) { +/****************************************************************************** +Trim all unnecessary whitespace (and non-essential syntactic tokens) from the +argument string, args, and count its number of elements. +******************************************************************************/ + size_t i, m, n; + pj_shrink (args); + n = strlen (args); + if (n==0) + return 0; + for (i = m = 0; i < n; i++) { + if (' '==args[i]) { + args[i] = 0; + m++; + } + } + return m + 1; +} + + + +/*****************************************************************************/ +char **pj_trim_argv (size_t argc, char *args) { +/****************************************************************************** +Create an argv-style array from elements placed in the argument string, args. + +args is a trimmed string as returned by pj_trim_argc(), and argc is the number +of trimmed strings found (i.e. the return value of pj_trim_args()). Hence, + int argc = pj_trim_argc (args); + char **argv = pj_trim_argv (argc, args); +will produce a classic style (argc, argv) pair from a string of whitespace +separated args. No new memory is allocated for storing the individual args +(they stay in the args string), but for the pointers to the args a new array +is allocated and returned. + +It is the duty of the caller to free this array. +******************************************************************************/ + size_t i, j; + char **argv; + + if (0==args) + return 0; + if (0==argc) + return 0; + + + /* turn the input string into an array of strings */ + argv = (char **) calloc (argc, sizeof (char *)); + if (0==argv) + return 0; + argv[0] = args; + j = 1; + for (i = 0; ; i++) { + if (0==args[i]) { + argv[j++] = args + (i + 1); + } + if (j==argc) + break; + } + return argv; +} + + + +/*****************************************************************************/ +char *pj_make_args (size_t argc, char **argv) { +/****************************************************************************** +pj_make_args is the inverse of the pj_trim_argc/pj_trim_argv combo: It +converts free format command line input to something proj_create can consume. + +Allocates, and returns, an array of char, large enough to hold a whitespace +separated copy of the args in argv. It is the duty of the caller to free this +array. +******************************************************************************/ + size_t i, n; + char *p; + + for (i = n = 0; i < argc; i++) + n += strlen (argv[i]); + + p = static_cast(pj_calloc (n + argc + 1, sizeof (char))); + if (0==p) + return 0; + if (0==argc) + return p; + + for (i = 0; i < argc; i++) { + strcat (p, argv[i]); + strcat (p, " "); + } + return pj_shrink (p); +} + + + +/*****************************************************************************/ +void proj_context_errno_set (PJ_CONTEXT *ctx, int err) { +/****************************************************************************** +Raise an error directly on a context, without going through a PJ belonging +to that context. +******************************************************************************/ + if (0==ctx) + ctx = pj_get_default_ctx(); + pj_ctx_set_errno (ctx, err); +} + +/* logging */ + +/* pj_vlog resides in pj_log.c and relates to pj_log as vsprintf relates to sprintf */ +void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ); + + +/***************************************************************************************/ +PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { +/**************************************************************************************** + Set logging level 0-3. Higher number means more debug info. 0 turns it off +****************************************************************************************/ + PJ_LOG_LEVEL previous; + if (0==ctx) + ctx = pj_get_default_ctx(); + if (0==ctx) + return PJ_LOG_TELL; + previous = static_cast(abs (ctx->debug_level)); + if (PJ_LOG_TELL==log_level) + return previous; + ctx->debug_level = log_level; + return previous; +} + + +/*****************************************************************************/ +void proj_log_error (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting the most severe events. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_ERROR , fmt, args); + va_end( args ); +} + + +/*****************************************************************************/ +void proj_log_debug (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MAJOR , fmt, args); + va_end( args ); +} + + +/*****************************************************************************/ +void proj_log_trace (PJ *P, const char *fmt, ...) { +/****************************************************************************** + For reporting embarrasingly detailed debugging information. +******************************************************************************/ + va_list args; + va_start( args, fmt ); + pj_vlog (pj_get_ctx (P), PJ_LOG_DEBUG_MINOR , fmt, args); + va_end( args ); +} + + +/*****************************************************************************/ +void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { +/****************************************************************************** + Put a new logging function into P's context. The opaque object app_data is + passed as first arg at each call to the logger +******************************************************************************/ + if (0==ctx) + pj_get_default_ctx (); + if (0==ctx) + return; + ctx->app_data = app_data; + if (0!=logf) + ctx->logger = logf; +} diff --git a/src/pj_inv.c b/src/pj_inv.c deleted file mode 100644 index aaa8fea9..00000000 --- a/src/pj_inv.c +++ /dev/null @@ -1,238 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Inverse operation invocation - * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 - * Based on material from Gerald Evenden (original pj_inv) - * and Piyush Agram (original pj_inv3d) - * - ****************************************************************************** - * Copyright (c) 2000, Frank Warmerdam - * Copyright (c) 2018, 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. - *****************************************************************************/ -#include -#include - -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" - -#define INPUT_UNITS P->right -#define OUTPUT_UNITS P->left - -static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) { - if (coo.v[0] == HUGE_VAL || coo.v[1] == HUGE_VAL || coo.v[2] == HUGE_VAL) { - proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); - return proj_coord_error (); - } - - /* The helmert datum shift will choke unless it gets a sensible 4D coordinate */ - if (HUGE_VAL==coo.v[2] && P->helmert) coo.v[2] = 0.0; - if (HUGE_VAL==coo.v[3] && P->helmert) coo.v[3] = 0.0; - - if (P->axisswap) - coo = proj_trans (P->axisswap, PJ_INV, coo); - - /* Handle remaining possible input types */ - switch (INPUT_UNITS) { - case PJ_IO_UNITS_WHATEVER: - return coo; - - /* de-scale and de-offset */ - case PJ_IO_UNITS_CARTESIAN: - coo.xyz.x *= P->to_meter; - coo.xyz.y *= P->to_meter; - coo.xyz.z *= P->to_meter; - if (P->is_geocent) { - coo = proj_trans (P->cart, PJ_INV, coo); - } - - return coo; - - case PJ_IO_UNITS_PROJECTED: - case PJ_IO_UNITS_CLASSIC: - coo.xyz.x = P->to_meter * coo.xyz.x - P->x0; - coo.xyz.y = P->to_meter * coo.xyz.y - P->y0; - coo.xyz.z = P->vto_meter * coo.xyz.z - P->z0; - if (INPUT_UNITS==PJ_IO_UNITS_PROJECTED) - return coo; - - /* Classic proj.4 functions expect plane coordinates in units of the semimajor axis */ - /* Multiplying by ra, rather than dividing by a because the CalCOFI projection */ - /* stomps on a and hence (apparently) depends on this to roundtrip correctly */ - /* (CalCOFI avoids further scaling by stomping - but a better solution is possible) */ - coo.xyz.x *= P->ra; - coo.xyz.y *= P->ra; - return coo; - - case PJ_IO_UNITS_ANGULAR: - coo.lpz.z = P->vto_meter * coo.lpz.z - P->z0; - break; - } - - /* Should not happen, so we could return pj_coord_err here */ - return coo; -} - - - -static PJ_COORD inv_finalize (PJ *P, PJ_COORD coo) { - if (coo.xyz.x == HUGE_VAL) { - proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); - return proj_coord_error (); - } - - if (OUTPUT_UNITS==PJ_IO_UNITS_ANGULAR) { - - /* Distance from central meridian, taking system zero meridian into account */ - coo.lp.lam = coo.lp.lam + P->from_greenwich + P->lam0; - - /* adjust longitude to central meridian */ - if (0==P->over) - coo.lpz.lam = adjlon(coo.lpz.lam); - - if (P->vgridshift) - coo = proj_trans (P->vgridshift, PJ_INV, coo); /* Go geometric from orthometric */ - if (coo.lp.lam==HUGE_VAL) - return coo; - if (P->hgridshift) - coo = proj_trans (P->hgridshift, PJ_FWD, coo); - else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { - coo = proj_trans (P->cart, PJ_FWD, coo); /* Go cartesian in local frame */ - if( P->helmert ) - coo = proj_trans (P->helmert, PJ_FWD, coo); /* Step into WGS84 */ - coo = proj_trans (P->cart_wgs84, PJ_INV, coo); /* Go back to angular using WGS84 ellps */ - } - if (coo.lp.lam==HUGE_VAL) - return coo; - - /* If input latitude was geocentrical, convert back to geocentrical */ - if (P->geoc) - coo = pj_geocentric_latitude (P, PJ_FWD, coo); - } - - return coo; -} - - -static PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { - if (proj_errno(P)) - return proj_coord_error(); - - proj_errno_restore(P, last_errno); - return coord; -} - - -LP pj_inv(XY xy, PJ *P) { - int last_errno; - PJ_COORD coo = {{0,0,0,0}}; - coo.xy = xy; - - last_errno = proj_errno_reset(P); - - if (!P->skip_inv_prepare) - coo = inv_prepare (P, coo); - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().lp; - - /* Do the transformation, using the lowest dimensional transformer available */ - if (P->inv) - coo.lp = P->inv(coo.xy, P); - else if (P->inv3d) - coo.lpz = P->inv3d (coo.xyz, P); - else if (P->inv4d) - coo = P->inv4d (coo, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error ().lp; - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().lp; - - if (!P->skip_inv_finalize) - coo = inv_finalize (P, coo); - - return error_or_coord(P, coo, last_errno).lp; -} - - - -LPZ pj_inv3d (XYZ xyz, PJ *P) { - int last_errno; - PJ_COORD coo = {{0,0,0,0}}; - coo.xyz = xyz; - - last_errno = proj_errno_reset(P); - - if (!P->skip_inv_prepare) - coo = inv_prepare (P, coo); - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().lpz; - - /* Do the transformation, using the lowest dimensional transformer feasible */ - if (P->inv3d) - coo.lpz = P->inv3d (coo.xyz, P); - else if (P->inv4d) - coo = P->inv4d (coo, P); - else if (P->inv) - coo.lp = P->inv (coo.xy, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error ().lpz; - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error ().lpz; - - if (!P->skip_inv_finalize) - coo = inv_finalize (P, coo); - - return error_or_coord(P, coo, last_errno).lpz; -} - - - -PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P) { - int last_errno = proj_errno_reset(P); - - if (!P->skip_inv_prepare) - coo = inv_prepare (P, coo); - if (HUGE_VAL==coo.v[0]) - return proj_coord_error (); - - /* Call the highest dimensional converter available */ - if (P->inv4d) - coo = P->inv4d (coo, P); - else if (P->inv3d) - coo.lpz = P->inv3d (coo.xyz, P); - else if (P->inv) - coo.lp = P->inv (coo.xy, P); - else { - proj_errno_set (P, EINVAL); - return proj_coord_error (); - } - if (HUGE_VAL==coo.v[0]) - return proj_coord_error (); - - if (!P->skip_inv_finalize) - coo = inv_finalize (P, coo); - - return error_or_coord(P, coo, last_errno); -} diff --git a/src/pj_inv.cpp b/src/pj_inv.cpp new file mode 100644 index 00000000..aaa8fea9 --- /dev/null +++ b/src/pj_inv.cpp @@ -0,0 +1,238 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Inverse operation invocation + * Author: Thomas Knudsen, thokn@sdfe.dk, 2018-01-02 + * Based on material from Gerald Evenden (original pj_inv) + * and Piyush Agram (original pj_inv3d) + * + ****************************************************************************** + * Copyright (c) 2000, Frank Warmerdam + * Copyright (c) 2018, 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. + *****************************************************************************/ +#include +#include + +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" + +#define INPUT_UNITS P->right +#define OUTPUT_UNITS P->left + +static PJ_COORD inv_prepare (PJ *P, PJ_COORD coo) { + if (coo.v[0] == HUGE_VAL || coo.v[1] == HUGE_VAL || coo.v[2] == HUGE_VAL) { + proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); + return proj_coord_error (); + } + + /* The helmert datum shift will choke unless it gets a sensible 4D coordinate */ + if (HUGE_VAL==coo.v[2] && P->helmert) coo.v[2] = 0.0; + if (HUGE_VAL==coo.v[3] && P->helmert) coo.v[3] = 0.0; + + if (P->axisswap) + coo = proj_trans (P->axisswap, PJ_INV, coo); + + /* Handle remaining possible input types */ + switch (INPUT_UNITS) { + case PJ_IO_UNITS_WHATEVER: + return coo; + + /* de-scale and de-offset */ + case PJ_IO_UNITS_CARTESIAN: + coo.xyz.x *= P->to_meter; + coo.xyz.y *= P->to_meter; + coo.xyz.z *= P->to_meter; + if (P->is_geocent) { + coo = proj_trans (P->cart, PJ_INV, coo); + } + + return coo; + + case PJ_IO_UNITS_PROJECTED: + case PJ_IO_UNITS_CLASSIC: + coo.xyz.x = P->to_meter * coo.xyz.x - P->x0; + coo.xyz.y = P->to_meter * coo.xyz.y - P->y0; + coo.xyz.z = P->vto_meter * coo.xyz.z - P->z0; + if (INPUT_UNITS==PJ_IO_UNITS_PROJECTED) + return coo; + + /* Classic proj.4 functions expect plane coordinates in units of the semimajor axis */ + /* Multiplying by ra, rather than dividing by a because the CalCOFI projection */ + /* stomps on a and hence (apparently) depends on this to roundtrip correctly */ + /* (CalCOFI avoids further scaling by stomping - but a better solution is possible) */ + coo.xyz.x *= P->ra; + coo.xyz.y *= P->ra; + return coo; + + case PJ_IO_UNITS_ANGULAR: + coo.lpz.z = P->vto_meter * coo.lpz.z - P->z0; + break; + } + + /* Should not happen, so we could return pj_coord_err here */ + return coo; +} + + + +static PJ_COORD inv_finalize (PJ *P, PJ_COORD coo) { + if (coo.xyz.x == HUGE_VAL) { + proj_errno_set (P, PJD_ERR_INVALID_X_OR_Y); + return proj_coord_error (); + } + + if (OUTPUT_UNITS==PJ_IO_UNITS_ANGULAR) { + + /* Distance from central meridian, taking system zero meridian into account */ + coo.lp.lam = coo.lp.lam + P->from_greenwich + P->lam0; + + /* adjust longitude to central meridian */ + if (0==P->over) + coo.lpz.lam = adjlon(coo.lpz.lam); + + if (P->vgridshift) + coo = proj_trans (P->vgridshift, PJ_INV, coo); /* Go geometric from orthometric */ + if (coo.lp.lam==HUGE_VAL) + return coo; + if (P->hgridshift) + coo = proj_trans (P->hgridshift, PJ_FWD, coo); + else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { + coo = proj_trans (P->cart, PJ_FWD, coo); /* Go cartesian in local frame */ + if( P->helmert ) + coo = proj_trans (P->helmert, PJ_FWD, coo); /* Step into WGS84 */ + coo = proj_trans (P->cart_wgs84, PJ_INV, coo); /* Go back to angular using WGS84 ellps */ + } + if (coo.lp.lam==HUGE_VAL) + return coo; + + /* If input latitude was geocentrical, convert back to geocentrical */ + if (P->geoc) + coo = pj_geocentric_latitude (P, PJ_FWD, coo); + } + + return coo; +} + + +static PJ_COORD error_or_coord(PJ *P, PJ_COORD coord, int last_errno) { + if (proj_errno(P)) + return proj_coord_error(); + + proj_errno_restore(P, last_errno); + return coord; +} + + +LP pj_inv(XY xy, PJ *P) { + int last_errno; + PJ_COORD coo = {{0,0,0,0}}; + coo.xy = xy; + + last_errno = proj_errno_reset(P); + + if (!P->skip_inv_prepare) + coo = inv_prepare (P, coo); + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().lp; + + /* Do the transformation, using the lowest dimensional transformer available */ + if (P->inv) + coo.lp = P->inv(coo.xy, P); + else if (P->inv3d) + coo.lpz = P->inv3d (coo.xyz, P); + else if (P->inv4d) + coo = P->inv4d (coo, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error ().lp; + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().lp; + + if (!P->skip_inv_finalize) + coo = inv_finalize (P, coo); + + return error_or_coord(P, coo, last_errno).lp; +} + + + +LPZ pj_inv3d (XYZ xyz, PJ *P) { + int last_errno; + PJ_COORD coo = {{0,0,0,0}}; + coo.xyz = xyz; + + last_errno = proj_errno_reset(P); + + if (!P->skip_inv_prepare) + coo = inv_prepare (P, coo); + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().lpz; + + /* Do the transformation, using the lowest dimensional transformer feasible */ + if (P->inv3d) + coo.lpz = P->inv3d (coo.xyz, P); + else if (P->inv4d) + coo = P->inv4d (coo, P); + else if (P->inv) + coo.lp = P->inv (coo.xy, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error ().lpz; + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error ().lpz; + + if (!P->skip_inv_finalize) + coo = inv_finalize (P, coo); + + return error_or_coord(P, coo, last_errno).lpz; +} + + + +PJ_COORD pj_inv4d (PJ_COORD coo, PJ *P) { + int last_errno = proj_errno_reset(P); + + if (!P->skip_inv_prepare) + coo = inv_prepare (P, coo); + if (HUGE_VAL==coo.v[0]) + return proj_coord_error (); + + /* Call the highest dimensional converter available */ + if (P->inv4d) + coo = P->inv4d (coo, P); + else if (P->inv3d) + coo.lpz = P->inv3d (coo.xyz, P); + else if (P->inv) + coo.lp = P->inv (coo.xy, P); + else { + proj_errno_set (P, EINVAL); + return proj_coord_error (); + } + if (HUGE_VAL==coo.v[0]) + return proj_coord_error (); + + if (!P->skip_inv_finalize) + coo = inv_finalize (P, coo); + + return error_or_coord(P, coo, last_errno); +} diff --git a/src/pj_list.c b/src/pj_list.c deleted file mode 100644 index 33313875..00000000 --- a/src/pj_list.c +++ /dev/null @@ -1,32 +0,0 @@ -/* Projection System: default list of projections -** Use local definition of PJ_LIST_H for subset. -*/ - -#include "proj.h" - -#define USE_PJ_LIST_H 1 -#include "projects.h" - - -/* Generate prototypes for projection functions */ -#define PROJ_HEAD(id, name) struct PJconsts *pj_##id(struct PJconsts*); -#include "pj_list.h" -#undef PROJ_HEAD - -/* Generate extern declarations for description strings */ -#define PROJ_HEAD(id, name) extern const char * const pj_s_##id; -#include "pj_list.h" -#undef PROJ_HEAD - -/* Generate the null-terminated list of projection functions with associated mnemonics and descriptions */ -#define PROJ_HEAD(id, name) {#id, pj_##id, &pj_s_##id}, -const struct PJ_LIST pj_list[] = { -#include "pj_list.h" - {0, 0, 0}, -}; -#undef PROJ_HEAD - - -const PJ_OPERATIONS *proj_list_operations(void) { - return pj_list; -} diff --git a/src/pj_list.cpp b/src/pj_list.cpp new file mode 100644 index 00000000..55ea36c2 --- /dev/null +++ b/src/pj_list.cpp @@ -0,0 +1,32 @@ +/* Projection System: default list of projections +** Use local definition of PJ_LIST_H for subset. +*/ + +#include "proj.h" + +#define USE_PJ_LIST_H 1 +#include "projects.h" + + +/* Generate prototypes for projection functions */ +#define PROJ_HEAD(id, name) extern "C" struct PJconsts *pj_##id(struct PJconsts*); +#include "pj_list.h" +#undef PROJ_HEAD + +/* Generate extern declarations for description strings */ +#define PROJ_HEAD(id, name) extern "C" const char * const pj_s_##id; +#include "pj_list.h" +#undef PROJ_HEAD + +/* Generate the null-terminated list of projection functions with associated mnemonics and descriptions */ +#define PROJ_HEAD(id, name) {#id, pj_##id, &pj_s_##id}, +const struct PJ_LIST pj_list[] = { +#include "pj_list.h" + {0, 0, 0}, +}; +#undef PROJ_HEAD + + +const PJ_OPERATIONS *proj_list_operations(void) { + return pj_list; +} diff --git a/src/pj_log.c b/src/pj_log.c deleted file mode 100644 index 6654691c..00000000 --- a/src/pj_log.c +++ /dev/null @@ -1,98 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of pj_log() function. - * 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 -#include - -#include "proj.h" -#include "projects.h" - -/************************************************************************/ -/* pj_stderr_logger() */ -/************************************************************************/ - -void pj_stderr_logger( void *app_data, int level, const char *msg ) - -{ - (void) app_data; - (void) level; - fprintf( stderr, "%s\n", msg ); -} - -/************************************************************************/ -/* pj_vlog() */ -/************************************************************************/ -void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ); -/* Workhorse for the log functions - relates to pj_log as vsprintf relates to sprintf */ -void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ) - -{ - char *msg_buf; - int debug_level = ctx->debug_level; - int shutup_unless_errno_set = debug_level < 0; - - /* For negative debug levels, we first start logging when errno is set */ - if (ctx->last_errno==0 && shutup_unless_errno_set) - return; - - if (debug_level < 0) - debug_level = -debug_level; - - if( level > debug_level ) - return; - - msg_buf = (char *) malloc(100000); - if( msg_buf == NULL ) - return; - - /* we should use vsnprintf where available once we add configure detect.*/ - vsprintf( msg_buf, fmt, args ); - - ctx->logger( ctx->app_data, level, msg_buf ); - - free( msg_buf ); -} - - -/************************************************************************/ -/* pj_log() */ -/************************************************************************/ - -void pj_log( projCtx ctx, int level, const char *fmt, ... ) - -{ - va_list args; - - if( level > ctx->debug_level ) - return; - - va_start( args, fmt ); - pj_vlog( ctx, level, fmt, args ); - va_end( args ); -} diff --git a/src/pj_log.cpp b/src/pj_log.cpp new file mode 100644 index 00000000..6654691c --- /dev/null +++ b/src/pj_log.cpp @@ -0,0 +1,98 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Implementation of pj_log() function. + * 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 +#include + +#include "proj.h" +#include "projects.h" + +/************************************************************************/ +/* pj_stderr_logger() */ +/************************************************************************/ + +void pj_stderr_logger( void *app_data, int level, const char *msg ) + +{ + (void) app_data; + (void) level; + fprintf( stderr, "%s\n", msg ); +} + +/************************************************************************/ +/* pj_vlog() */ +/************************************************************************/ +void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ); +/* Workhorse for the log functions - relates to pj_log as vsprintf relates to sprintf */ +void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ) + +{ + char *msg_buf; + int debug_level = ctx->debug_level; + int shutup_unless_errno_set = debug_level < 0; + + /* For negative debug levels, we first start logging when errno is set */ + if (ctx->last_errno==0 && shutup_unless_errno_set) + return; + + if (debug_level < 0) + debug_level = -debug_level; + + if( level > debug_level ) + return; + + msg_buf = (char *) malloc(100000); + if( msg_buf == NULL ) + return; + + /* we should use vsnprintf where available once we add configure detect.*/ + vsprintf( msg_buf, fmt, args ); + + ctx->logger( ctx->app_data, level, msg_buf ); + + free( msg_buf ); +} + + +/************************************************************************/ +/* pj_log() */ +/************************************************************************/ + +void pj_log( projCtx ctx, int level, const char *fmt, ... ) + +{ + va_list args; + + if( level > ctx->debug_level ) + return; + + va_start( args, fmt ); + pj_vlog( ctx, level, fmt, args ); + va_end( args ); +} diff --git a/src/pj_malloc.c b/src/pj_malloc.c deleted file mode 100644 index c45da85d..00000000 --- a/src/pj_malloc.c +++ /dev/null @@ -1,239 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Memory management for proj.4. - * This version includes an implementation of generic destructors, - * for memory deallocation for the large majority of PJ-objects - * that do not allocate anything else than the PJ-object itself, - * and its associated opaque object - i.e. no additional malloc'ed - * memory inside the opaque object. - * - * Author: Gerald I. Evenden (Original proj.4 author), - * Frank Warmerdam (2000) pj_malloc? - * Thomas Knudsen (2016) - freeup/dealloc parts - * - ****************************************************************************** - * Copyright (c) 2000, Frank Warmerdam - * 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. - *****************************************************************************/ - -/* allocate and deallocate memory */ -/* These routines are used so that applications can readily replace -** projection system memory allocation/deallocation call with custom -** application procedures. */ - -#include -#include -#include -#include - -#include "proj.h" -#include "projects.h" - -/**********************************************************************/ -void *pj_malloc(size_t size) { -/*********************************************************************** -Currently, pj_malloc is a hack to solve an errno problem. -The problem is described in more details at -https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=86420. -It seems, that pj_init and similar functions incorrectly -(under debian/glibs-2.3.2) assume that pj_malloc resets -errno after success. pj_malloc tries to mimic this. - -NOTE (2017-09-29): The problem described at the bugzilla page -referred to above, is most likely a case of someone not -understanding the proper usage of errno. We should review -whether "the problem is actually a problem" in PROJ.4 code. - -Library specific allocators can be useful, and improve -interoperability, if properly used. That is, by making them -run/initialization time switchable, somewhat like the file i/o -interface. - -But as things stand, we are more likely to get benefit -from reviewing the code for proper errno usage, which is hard, -due to the presence of context local and global pj_errnos. - -Probably, these were introduced in order to support incomplete -implementations of thread local errnos at an early phase of the -implementation of multithreading support in PROJ.4). - -It is likely too late to get rid of contexts, but we can still -benefit from a better usage of errno. -***********************************************************************/ - int old_errno = errno; - void *res = malloc(size); - if ( res && !old_errno ) - errno = 0; - return res; -} - - -/**********************************************************************/ -void *pj_calloc (size_t n, size_t size) { -/*********************************************************************** -pj_calloc is the pj-equivalent of calloc(). - -It allocates space for an array of elements of size . -The array is initialized to zeros. -***********************************************************************/ - void *res = pj_malloc (n*size); - if (0==res) - return 0; - memset (res, 0, n*size); - return res; -} - - -/**********************************************************************/ -void pj_dalloc(void *ptr) { -/**********************************************************************/ - free(ptr); -} - - -/**********************************************************************/ -void *pj_dealloc (void *ptr) { -/*********************************************************************** -pj_dealloc supports the common use case of "clean up and return a null -pointer" to signal an error in a multi level allocation: - - struct foo { int bar; int *baz; }; - - struct foo *p = pj_calloc (1, sizeof (struct foo)); - if (0==p) - return 0; - - p->baz = pj_calloc (10, sizeof(int)); - if (0==p->baz) - return pj_dealloc (p); // clean up + signal error by 0-return - - return p; // success - -***********************************************************************/ - if (0==ptr) - return 0; - pj_dalloc (ptr); - return 0; -} - -/**********************************************************************/ -char *pj_strdup(const char *str) -/**********************************************************************/ -{ - size_t len = strlen(str) + 1; - char *dup = pj_malloc(len); - if (dup) - memcpy(dup, str, len); - return dup; -} - - -/*****************************************************************************/ -void *pj_dealloc_params (PJ_CONTEXT *ctx, paralist *start, int errlev) { -/***************************************************************************** - Companion to pj_default_destructor (below). Deallocates a linked list - of "+proj=xxx" initialization parameters. - - Also called from pj_init_ctx when encountering errors before the PJ - proper is allocated. -******************************************************************************/ - paralist *t, *n; - for (t = start; t; t = n) { - n = t->next; - pj_dealloc(t); - } - pj_ctx_set_errno (ctx, errlev); - return (void *) 0; -} - - - - -/************************************************************************/ -/* pj_free() */ -/* */ -/* This is the application callable entry point for destroying */ -/* a projection definition. It does work generic to all */ -/* projection types, and then calls the projection specific */ -/* free function, P->destructor(), to do local work. */ -/* In most cases P->destructor()==pj_default_destructor. */ -/************************************************************************/ - -void pj_free(PJ *P) { - if (0==P) - return; - /* free projection parameters - all the hard work is done by */ - /* pj_default_destructor, which is supposed */ - /* to be called as the last step of the local destructor */ - /* pointed to by P->destructor. In most cases, */ - /* pj_default_destructor actually *is* what is pointed to */ - P->destructor (P, proj_errno(P)); -} - - - - -/*****************************************************************************/ -void *pj_default_destructor (PJ *P, int errlev) { /* Destructor */ -/***************************************************************************** - Does memory deallocation for "plain" PJ objects, i.e. that vast majority - of PJs where the opaque object does not contain any additionally - allocated memory below the P->opaque level. -******************************************************************************/ - - /* Even if P==0, we set the errlev on pj_error and the default context */ - /* Note that both, in the multithreaded case, may then contain undefined */ - /* values. This is expected behaviour. For MT have one ctx per thread */ - if (0!=errlev) - pj_ctx_set_errno (pj_get_ctx(P), errlev); - - if (0==P) - return 0; - - /* free grid lists */ - pj_dealloc( P->gridlist ); - pj_dealloc( P->vgridlist_geoid ); - pj_dealloc( P->catalog_name ); - - /* We used to call pj_dalloc( P->catalog ), but this will leak */ - /* memory. The safe way to clear catalog and grid is to call */ - /* pj_gc_unloadall(pj_get_default_ctx()); and pj_deallocate_grids(); */ - /* TODO: we should probably have a public pj_cleanup() method to do all */ - /* that */ - - /* free the interface to Charles Karney's geodesic library */ - pj_dealloc( P->geod ); - - /* free parameter list elements */ - pj_dealloc_params (pj_get_ctx(P), P->params, errlev); - pj_dealloc (P->def_full); - - /* free the cs2cs emulation elements */ - pj_free (P->axisswap); - pj_free (P->helmert); - pj_free (P->cart); - pj_free (P->cart_wgs84); - pj_free (P->hgridshift); - pj_free (P->vgridshift); - - pj_dealloc (P->opaque); - return pj_dealloc(P); -} diff --git a/src/pj_malloc.cpp b/src/pj_malloc.cpp new file mode 100644 index 00000000..66977cf4 --- /dev/null +++ b/src/pj_malloc.cpp @@ -0,0 +1,240 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Memory management for proj.4. + * This version includes an implementation of generic destructors, + * for memory deallocation for the large majority of PJ-objects + * that do not allocate anything else than the PJ-object itself, + * and its associated opaque object - i.e. no additional malloc'ed + * memory inside the opaque object. + * + * Author: Gerald I. Evenden (Original proj.4 author), + * Frank Warmerdam (2000) pj_malloc? + * Thomas Knudsen (2016) - freeup/dealloc parts + * + ****************************************************************************** + * Copyright (c) 2000, Frank Warmerdam + * 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. + *****************************************************************************/ + +/* allocate and deallocate memory */ +/* These routines are used so that applications can readily replace +** projection system memory allocation/deallocation call with custom +** application procedures. */ + +#include +#include +#include +#include + +#include "proj.h" +#include "projects.h" + +/**********************************************************************/ +void *pj_malloc(size_t size) { +/*********************************************************************** +Currently, pj_malloc is a hack to solve an errno problem. +The problem is described in more details at +https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=86420. +It seems, that pj_init and similar functions incorrectly +(under debian/glibs-2.3.2) assume that pj_malloc resets +errno after success. pj_malloc tries to mimic this. + +NOTE (2017-09-29): The problem described at the bugzilla page +referred to above, is most likely a case of someone not +understanding the proper usage of errno. We should review +whether "the problem is actually a problem" in PROJ.4 code. + +Library specific allocators can be useful, and improve +interoperability, if properly used. That is, by making them +run/initialization time switchable, somewhat like the file i/o +interface. + +But as things stand, we are more likely to get benefit +from reviewing the code for proper errno usage, which is hard, +due to the presence of context local and global pj_errnos. + +Probably, these were introduced in order to support incomplete +implementations of thread local errnos at an early phase of the +implementation of multithreading support in PROJ.4). + +It is likely too late to get rid of contexts, but we can still +benefit from a better usage of errno. +***********************************************************************/ + int old_errno = errno; + void *res = malloc(size); + if ( res && !old_errno ) + errno = 0; + return res; +} + + +/**********************************************************************/ +void *pj_calloc (size_t n, size_t size) { +/*********************************************************************** +pj_calloc is the pj-equivalent of calloc(). + +It allocates space for an array of elements of size . +The array is initialized to zeros. +***********************************************************************/ + void *res = pj_malloc (n*size); + if (0==res) + return 0; + memset (res, 0, n*size); + return res; +} + + +/**********************************************************************/ +void pj_dalloc(void *ptr) { +/**********************************************************************/ + free(ptr); +} + + +/**********************************************************************/ +void *pj_dealloc (void *ptr) { +/*********************************************************************** +pj_dealloc supports the common use case of "clean up and return a null +pointer" to signal an error in a multi level allocation: + + struct foo { int bar; int *baz; }; + + struct foo *p = pj_calloc (1, sizeof (struct foo)); + if (0==p) + return 0; + + p->baz = pj_calloc (10, sizeof(int)); + if (0==p->baz) + return pj_dealloc (p); // clean up + signal error by 0-return + + return p; // success + +***********************************************************************/ + if (0==ptr) + return 0; + pj_dalloc (ptr); + return 0; +} + +/**********************************************************************/ +char *pj_strdup(const char *str) +/**********************************************************************/ +{ + size_t len = strlen(str) + 1; + char *dup = static_cast(pj_malloc(len)); + if (dup) + memcpy(dup, str, len); + return dup; +} + + +/*****************************************************************************/ +void *pj_dealloc_params (PJ_CONTEXT *ctx, paralist *start, int errlev) { +/***************************************************************************** + Companion to pj_default_destructor (below). Deallocates a linked list + of "+proj=xxx" initialization parameters. + + Also called from pj_init_ctx when encountering errors before the PJ + proper is allocated. +******************************************************************************/ + paralist *t, *n; + for (t = start; t; t = n) { + n = t->next; + pj_dealloc(t); + } + pj_ctx_set_errno (ctx, errlev); + return (void *) 0; +} + + + + +/************************************************************************/ +/* pj_free() */ +/* */ +/* This is the application callable entry point for destroying */ +/* a projection definition. It does work generic to all */ +/* projection types, and then calls the projection specific */ +/* free function, P->destructor(), to do local work. */ +/* In most cases P->destructor()==pj_default_destructor. */ +/************************************************************************/ + +void pj_free(PJ *P) { + if (0==P) + return; + /* free projection parameters - all the hard work is done by */ + /* pj_default_destructor, which is supposed */ + /* to be called as the last step of the local destructor */ + /* pointed to by P->destructor. In most cases, */ + /* pj_default_destructor actually *is* what is pointed to */ + P->destructor (P, proj_errno(P)); +} + + + + +/*****************************************************************************/ +PJ *pj_default_destructor (PJ *P, int errlev) { /* Destructor */ +/***************************************************************************** + Does memory deallocation for "plain" PJ objects, i.e. that vast majority + of PJs where the opaque object does not contain any additionally + allocated memory below the P->opaque level. +******************************************************************************/ + + /* Even if P==0, we set the errlev on pj_error and the default context */ + /* Note that both, in the multithreaded case, may then contain undefined */ + /* values. This is expected behaviour. For MT have one ctx per thread */ + if (0!=errlev) + pj_ctx_set_errno (pj_get_ctx(P), errlev); + + if (0==P) + return 0; + + /* free grid lists */ + pj_dealloc( P->gridlist ); + pj_dealloc( P->vgridlist_geoid ); + pj_dealloc( P->catalog_name ); + + /* We used to call pj_dalloc( P->catalog ), but this will leak */ + /* memory. The safe way to clear catalog and grid is to call */ + /* pj_gc_unloadall(pj_get_default_ctx()); and pj_deallocate_grids(); */ + /* TODO: we should probably have a public pj_cleanup() method to do all */ + /* that */ + + /* free the interface to Charles Karney's geodesic library */ + pj_dealloc( P->geod ); + + /* free parameter list elements */ + pj_dealloc_params (pj_get_ctx(P), P->params, errlev); + pj_dealloc (P->def_full); + + /* free the cs2cs emulation elements */ + pj_free (P->axisswap); + pj_free (P->helmert); + pj_free (P->cart); + pj_free (P->cart_wgs84); + pj_free (P->hgridshift); + pj_free (P->vgridshift); + + pj_dealloc (static_cast(P->opaque)); + pj_dealloc(P); + return nullptr; +} diff --git a/src/pj_math.c b/src/pj_math.c deleted file mode 100644 index 540ab9eb..00000000 --- a/src/pj_math.c +++ /dev/null @@ -1,108 +0,0 @@ -/****************************************************************************** - * Project: PROJ - * Purpose: Make C99 math functions available on C89 systems - * Author: Kristian Evers - * - ****************************************************************************** - * Copyright (c) 2018, 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. - *****************************************************************************/ - -#include "proj_math.h" - -/* pj_isnan is used in gie.c which means that is has to */ -/* be exported in the Windows DLL and therefore needs */ -/* to be declared even though we have isnan() on the */ -/* system. */ - -#ifdef HAVE_C99_MATH -int pj_isnan (double x); -#endif - -/* Returns 0 if not a NaN and non-zero if val is a NaN */ -int pj_isnan (double x) { - /* cppcheck-suppress duplicateExpression */ - return x != x; -} - -#if !(defined(HAVE_C99_MATH) && HAVE_C99_MATH) - -/* Compute hypotenuse */ -double pj_hypot(double x, double y) { - x = fabs(x); - y = fabs(y); - if ( x < y ) { - x /= y; - return ( y * sqrt( 1. + x * x ) ); - } else { - y /= (x != 0.0 ? x : 1.0); - return ( x * sqrt( 1. + y * y ) ); - } -} - -/* Compute log(1+x) accurately */ -double pj_log1p(double x) { - volatile double - y = 1 + x, - z = y - 1; - /* Here's the explanation for this magic: y = 1 + z, exactly, and z - * approx x, thus log(y)/z (which is nearly constant near z = 0) returns - * a good approximation to the true log(1 + x)/x. The multiplication x * - * (log(y)/z) introduces little additional error. */ - return z == 0 ? x : x * log(y) / z; -} - -/* Compute asinh(x) accurately */ -double pj_asinh(double x) { - double y = fabs(x); /* Enforce odd parity */ - y = log1p(y * (1 + y/(hypot(1.0, y) + 1))); - return x > 0 ? y : (x < 0 ? -y : x); -} - -double pj_round(double x) { - /* The handling of corner cases is copied from boost; see - * https://github.com/boostorg/math/pull/8 - * with improvements to return -0.0 when appropriate */ - double t; - if (x == 0) - return x; /* Retain sign of 0 */ - else if (0 < x && x < 0.5) - return +0.0; - else if (0 > x && x > -0.5) - return -0.0; - else if (x > 0) { - t = ceil(x); - return 0.5 < t - x ? t - 1 : t; - } else { /* Includes NaN */ - t = floor(x); - return 0.5 < x - t ? t + 1 : t; - } -} - -long pj_lround(double x) { - /* Default value for overflow + NaN + (x == LONG_MIN) */ - long r = LONG_MIN; - x = round(x); - if (fabs(x) < -(double)LONG_MIN) /* Assume (double)LONG_MIN is exact */ - r = (int)x; - return r; -} - -#endif /* !(defined(HAVE_C99_MATH) && HAVE_C99_MATH) */ diff --git a/src/pj_math.cpp b/src/pj_math.cpp new file mode 100644 index 00000000..540ab9eb --- /dev/null +++ b/src/pj_math.cpp @@ -0,0 +1,108 @@ +/****************************************************************************** + * Project: PROJ + * Purpose: Make C99 math functions available on C89 systems + * Author: Kristian Evers + * + ****************************************************************************** + * Copyright (c) 2018, 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. + *****************************************************************************/ + +#include "proj_math.h" + +/* pj_isnan is used in gie.c which means that is has to */ +/* be exported in the Windows DLL and therefore needs */ +/* to be declared even though we have isnan() on the */ +/* system. */ + +#ifdef HAVE_C99_MATH +int pj_isnan (double x); +#endif + +/* Returns 0 if not a NaN and non-zero if val is a NaN */ +int pj_isnan (double x) { + /* cppcheck-suppress duplicateExpression */ + return x != x; +} + +#if !(defined(HAVE_C99_MATH) && HAVE_C99_MATH) + +/* Compute hypotenuse */ +double pj_hypot(double x, double y) { + x = fabs(x); + y = fabs(y); + if ( x < y ) { + x /= y; + return ( y * sqrt( 1. + x * x ) ); + } else { + y /= (x != 0.0 ? x : 1.0); + return ( x * sqrt( 1. + y * y ) ); + } +} + +/* Compute log(1+x) accurately */ +double pj_log1p(double x) { + volatile double + y = 1 + x, + z = y - 1; + /* Here's the explanation for this magic: y = 1 + z, exactly, and z + * approx x, thus log(y)/z (which is nearly constant near z = 0) returns + * a good approximation to the true log(1 + x)/x. The multiplication x * + * (log(y)/z) introduces little additional error. */ + return z == 0 ? x : x * log(y) / z; +} + +/* Compute asinh(x) accurately */ +double pj_asinh(double x) { + double y = fabs(x); /* Enforce odd parity */ + y = log1p(y * (1 + y/(hypot(1.0, y) + 1))); + return x > 0 ? y : (x < 0 ? -y : x); +} + +double pj_round(double x) { + /* The handling of corner cases is copied from boost; see + * https://github.com/boostorg/math/pull/8 + * with improvements to return -0.0 when appropriate */ + double t; + if (x == 0) + return x; /* Retain sign of 0 */ + else if (0 < x && x < 0.5) + return +0.0; + else if (0 > x && x > -0.5) + return -0.0; + else if (x > 0) { + t = ceil(x); + return 0.5 < t - x ? t - 1 : t; + } else { /* Includes NaN */ + t = floor(x); + return 0.5 < x - t ? t + 1 : t; + } +} + +long pj_lround(double x) { + /* Default value for overflow + NaN + (x == LONG_MIN) */ + long r = LONG_MIN; + x = round(x); + if (fabs(x) < -(double)LONG_MIN) /* Assume (double)LONG_MIN is exact */ + r = (int)x; + return r; +} + +#endif /* !(defined(HAVE_C99_MATH) && HAVE_C99_MATH) */ diff --git a/src/pj_mlfn.c b/src/pj_mlfn.c deleted file mode 100644 index 02e05c3a..00000000 --- a/src/pj_mlfn.c +++ /dev/null @@ -1,64 +0,0 @@ -#include - -#include "projects.h" - -/* meridional distance for ellipsoid and inverse -** 8th degree - accurate to < 1e-5 meters when used in conjunction -** with typical major axis values. -** Inverse determines phi to EPS (1e-11) radians, about 1e-6 seconds. -*/ -#define C00 1. -#define C02 .25 -#define C04 .046875 -#define C06 .01953125 -#define C08 .01068115234375 -#define C22 .75 -#define C44 .46875 -#define C46 .01302083333333333333 -#define C48 .00712076822916666666 -#define C66 .36458333333333333333 -#define C68 .00569661458333333333 -#define C88 .3076171875 -#define EPS 1e-11 -#define MAX_ITER 10 -#define EN_SIZE 5 - -double *pj_enfn(double es) { - double t, *en; - - en = (double *) pj_malloc(EN_SIZE * sizeof (double)); - if (0==en) - return 0; - - en[0] = C00 - es * (C02 + es * (C04 + es * (C06 + es * C08))); - en[1] = es * (C22 - es * (C04 + es * (C06 + es * C08))); - en[2] = (t = es * es) * (C44 - es * (C46 + es * C48)); - en[3] = (t *= es) * (C66 - es * C68); - en[4] = t * es * C88; - - return en; -} - - double -pj_mlfn(double phi, double sphi, double cphi, double *en) { - cphi *= sphi; - sphi *= sphi; - return(en[0] * phi - cphi * (en[1] + sphi*(en[2] - + sphi*(en[3] + sphi*en[4])))); -} - double -pj_inv_mlfn(projCtx ctx, double arg, double es, double *en) { - double s, t, phi, k = 1./(1.-es); - int i; - - phi = arg; - for (i = MAX_ITER; i ; --i) { /* rarely goes over 2 iterations */ - s = sin(phi); - t = 1. - es * s * s; - phi -= t = (pj_mlfn(phi, s, cos(phi), en) - arg) * (t * sqrt(t)) * k; - if (fabs(t) < EPS) - return phi; - } - pj_ctx_set_errno( ctx, PJD_ERR_NON_CONV_INV_MERI_DIST ); - return phi; -} diff --git a/src/pj_mlfn.cpp b/src/pj_mlfn.cpp new file mode 100644 index 00000000..02e05c3a --- /dev/null +++ b/src/pj_mlfn.cpp @@ -0,0 +1,64 @@ +#include + +#include "projects.h" + +/* meridional distance for ellipsoid and inverse +** 8th degree - accurate to < 1e-5 meters when used in conjunction +** with typical major axis values. +** Inverse determines phi to EPS (1e-11) radians, about 1e-6 seconds. +*/ +#define C00 1. +#define C02 .25 +#define C04 .046875 +#define C06 .01953125 +#define C08 .01068115234375 +#define C22 .75 +#define C44 .46875 +#define C46 .01302083333333333333 +#define C48 .00712076822916666666 +#define C66 .36458333333333333333 +#define C68 .00569661458333333333 +#define C88 .3076171875 +#define EPS 1e-11 +#define MAX_ITER 10 +#define EN_SIZE 5 + +double *pj_enfn(double es) { + double t, *en; + + en = (double *) pj_malloc(EN_SIZE * sizeof (double)); + if (0==en) + return 0; + + en[0] = C00 - es * (C02 + es * (C04 + es * (C06 + es * C08))); + en[1] = es * (C22 - es * (C04 + es * (C06 + es * C08))); + en[2] = (t = es * es) * (C44 - es * (C46 + es * C48)); + en[3] = (t *= es) * (C66 - es * C68); + en[4] = t * es * C88; + + return en; +} + + double +pj_mlfn(double phi, double sphi, double cphi, double *en) { + cphi *= sphi; + sphi *= sphi; + return(en[0] * phi - cphi * (en[1] + sphi*(en[2] + + sphi*(en[3] + sphi*en[4])))); +} + double +pj_inv_mlfn(projCtx ctx, double arg, double es, double *en) { + double s, t, phi, k = 1./(1.-es); + int i; + + phi = arg; + for (i = MAX_ITER; i ; --i) { /* rarely goes over 2 iterations */ + s = sin(phi); + t = 1. - es * s * s; + phi -= t = (pj_mlfn(phi, s, cos(phi), en) - arg) * (t * sqrt(t)) * k; + if (fabs(t) < EPS) + return phi; + } + pj_ctx_set_errno( ctx, PJD_ERR_NON_CONV_INV_MERI_DIST ); + return phi; +} diff --git a/src/pj_msfn.c b/src/pj_msfn.c deleted file mode 100644 index 999e73a7..00000000 --- a/src/pj_msfn.c +++ /dev/null @@ -1,7 +0,0 @@ -/* determine constant small m */ -#include -#include "projects.h" - double -pj_msfn(double sinphi, double cosphi, double es) { - return (cosphi / sqrt (1. - es * sinphi * sinphi)); -} diff --git a/src/pj_msfn.cpp b/src/pj_msfn.cpp new file mode 100644 index 00000000..999e73a7 --- /dev/null +++ b/src/pj_msfn.cpp @@ -0,0 +1,7 @@ +/* determine constant small m */ +#include +#include "projects.h" + double +pj_msfn(double sinphi, double cosphi, double es) { + return (cosphi / sqrt (1. - es * sinphi * sinphi)); +} diff --git a/src/pj_mutex.c b/src/pj_mutex.c deleted file mode 100644 index dc4a441b..00000000 --- a/src/pj_mutex.c +++ /dev/null @@ -1,261 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Mutex (thread lock) functions. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2009, 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. - *****************************************************************************/ - - -/* projects.h and windows.h conflict - avoid this! */ - -#if defined(MUTEX_pthread) && !defined(_XOPEN_SOURCE) && !defined(__sun) -/* For pthread_mutexattr_settype */ -#define _XOPEN_SOURCE 500 -#endif - -/* For PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP */ -#define _GNU_SOURCE - -#ifndef _WIN32 -#include "proj_config.h" -#include "projects.h" -#else -#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#endif -#include "proj_api.h" -#endif - -/* on win32 we always use win32 mutexes, even if pthreads are available */ -#if defined(_WIN32) && !defined(MUTEX_stub) -#ifndef MUTEX_win32 -# define MUTEX_win32 -#endif -# undef MUTEX_pthread -#endif - -#if !defined(MUTEX_stub) && !defined(MUTEX_pthread) && !defined(MUTEX_win32) -# define MUTEX_stub -#endif - -/************************************************************************/ -/* ==================================================================== */ -/* stub mutex implementation */ -/* ==================================================================== */ -/************************************************************************/ - -#ifdef MUTEX_stub - -/************************************************************************/ -/* pj_acquire_lock() */ -/* */ -/* Acquire the PROJ.4 lock. */ -/************************************************************************/ - -void pj_acquire_lock() -{ -} - -/************************************************************************/ -/* pj_release_lock() */ -/* */ -/* Release the PROJ.4 lock. */ -/************************************************************************/ - -void pj_release_lock() -{ -} - -/************************************************************************/ -/* pj_cleanup_lock() */ -/************************************************************************/ -void pj_cleanup_lock() -{ -} - -#endif /* def MUTEX_stub */ - -/************************************************************************/ -/* ==================================================================== */ -/* pthread mutex implementation */ -/* ==================================================================== */ -/************************************************************************/ - -#ifdef MUTEX_pthread - -#include "pthread.h" - -#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP -static pthread_mutex_t core_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; -#else -static pthread_mutex_t core_lock; - -/************************************************************************/ -/* pj_create_lock() */ -/************************************************************************/ - -static void pj_create_lock() -{ - /* - ** We need to ensure the core mutex is created in recursive mode - */ - pthread_mutexattr_t mutex_attr; - - pthread_mutexattr_init(&mutex_attr); -#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE - pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE); -#else - pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE_NP); -#endif - pthread_mutex_init(&core_lock, &mutex_attr); -} - -#endif /* PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP */ - -/************************************************************************/ -/* pj_acquire_lock() */ -/* */ -/* Acquire the PROJ.4 lock. */ -/************************************************************************/ - -void pj_acquire_lock() -{ - -#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP - static pthread_once_t sOnceKey = PTHREAD_ONCE_INIT; - if( pthread_once(&sOnceKey, pj_create_lock) != 0 ) - { - fprintf(stderr, "pthread_once() failed in pj_acquire_lock().\n"); - } -#endif - - pthread_mutex_lock( &core_lock); -} - -/************************************************************************/ -/* pj_release_lock() */ -/* */ -/* Release the PROJ.4 lock. */ -/************************************************************************/ - -void pj_release_lock() -{ - pthread_mutex_unlock( &core_lock ); -} - -/************************************************************************/ -/* pj_cleanup_lock() */ -/************************************************************************/ -void pj_cleanup_lock() -{ -} - -#endif /* def MUTEX_pthread */ - -/************************************************************************/ -/* ==================================================================== */ -/* win32 mutex implementation */ -/* ==================================================================== */ -/************************************************************************/ - -#ifdef MUTEX_win32 - -#include - -static HANDLE mutex_lock = NULL; - -#if _WIN32_WINNT >= 0x0600 - -/************************************************************************/ -/* pj_create_lock() */ -/************************************************************************/ - -static BOOL CALLBACK pj_create_lock(PINIT_ONCE InitOnce, - PVOID Parameter, - PVOID *Context) -{ - (void)InitOnce; - (void)Parameter; - (void)Context; - mutex_lock = CreateMutex( NULL, FALSE, NULL ); - return TRUE; -} -#endif - -/************************************************************************/ -/* pj_init_lock() */ -/************************************************************************/ - -static void pj_init_lock() - -{ -#if _WIN32_WINNT >= 0x0600 - static INIT_ONCE sInitOnce = INIT_ONCE_STATIC_INIT; - InitOnceExecuteOnce( &sInitOnce, pj_create_lock, NULL, NULL ); -#else - if( mutex_lock == NULL ) - mutex_lock = CreateMutex( NULL, FALSE, NULL ); -#endif -} - -/************************************************************************/ -/* pj_acquire_lock() */ -/* */ -/* Acquire the PROJ.4 lock. */ -/************************************************************************/ - -void pj_acquire_lock() -{ - if( mutex_lock == NULL ) - pj_init_lock(); - - WaitForSingleObject( mutex_lock, INFINITE ); -} - -/************************************************************************/ -/* pj_release_lock() */ -/* */ -/* Release the PROJ.4 lock. */ -/************************************************************************/ - -void pj_release_lock() -{ - if( mutex_lock == NULL ) - pj_init_lock(); - else - ReleaseMutex( mutex_lock ); -} - -/************************************************************************/ -/* pj_cleanup_lock() */ -/************************************************************************/ -void pj_cleanup_lock() -{ - if( mutex_lock != NULL ) - { - CloseHandle( mutex_lock ); - mutex_lock = NULL; - } -} - -#endif /* def MUTEX_win32 */ diff --git a/src/pj_mutex.cpp b/src/pj_mutex.cpp new file mode 100644 index 00000000..dc4a441b --- /dev/null +++ b/src/pj_mutex.cpp @@ -0,0 +1,261 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Mutex (thread lock) functions. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2009, 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. + *****************************************************************************/ + + +/* projects.h and windows.h conflict - avoid this! */ + +#if defined(MUTEX_pthread) && !defined(_XOPEN_SOURCE) && !defined(__sun) +/* For pthread_mutexattr_settype */ +#define _XOPEN_SOURCE 500 +#endif + +/* For PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP */ +#define _GNU_SOURCE + +#ifndef _WIN32 +#include "proj_config.h" +#include "projects.h" +#else +#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#endif +#include "proj_api.h" +#endif + +/* on win32 we always use win32 mutexes, even if pthreads are available */ +#if defined(_WIN32) && !defined(MUTEX_stub) +#ifndef MUTEX_win32 +# define MUTEX_win32 +#endif +# undef MUTEX_pthread +#endif + +#if !defined(MUTEX_stub) && !defined(MUTEX_pthread) && !defined(MUTEX_win32) +# define MUTEX_stub +#endif + +/************************************************************************/ +/* ==================================================================== */ +/* stub mutex implementation */ +/* ==================================================================== */ +/************************************************************************/ + +#ifdef MUTEX_stub + +/************************************************************************/ +/* pj_acquire_lock() */ +/* */ +/* Acquire the PROJ.4 lock. */ +/************************************************************************/ + +void pj_acquire_lock() +{ +} + +/************************************************************************/ +/* pj_release_lock() */ +/* */ +/* Release the PROJ.4 lock. */ +/************************************************************************/ + +void pj_release_lock() +{ +} + +/************************************************************************/ +/* pj_cleanup_lock() */ +/************************************************************************/ +void pj_cleanup_lock() +{ +} + +#endif /* def MUTEX_stub */ + +/************************************************************************/ +/* ==================================================================== */ +/* pthread mutex implementation */ +/* ==================================================================== */ +/************************************************************************/ + +#ifdef MUTEX_pthread + +#include "pthread.h" + +#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +static pthread_mutex_t core_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +#else +static pthread_mutex_t core_lock; + +/************************************************************************/ +/* pj_create_lock() */ +/************************************************************************/ + +static void pj_create_lock() +{ + /* + ** We need to ensure the core mutex is created in recursive mode + */ + pthread_mutexattr_t mutex_attr; + + pthread_mutexattr_init(&mutex_attr); +#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE + pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE); +#else + pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE_NP); +#endif + pthread_mutex_init(&core_lock, &mutex_attr); +} + +#endif /* PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP */ + +/************************************************************************/ +/* pj_acquire_lock() */ +/* */ +/* Acquire the PROJ.4 lock. */ +/************************************************************************/ + +void pj_acquire_lock() +{ + +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + static pthread_once_t sOnceKey = PTHREAD_ONCE_INIT; + if( pthread_once(&sOnceKey, pj_create_lock) != 0 ) + { + fprintf(stderr, "pthread_once() failed in pj_acquire_lock().\n"); + } +#endif + + pthread_mutex_lock( &core_lock); +} + +/************************************************************************/ +/* pj_release_lock() */ +/* */ +/* Release the PROJ.4 lock. */ +/************************************************************************/ + +void pj_release_lock() +{ + pthread_mutex_unlock( &core_lock ); +} + +/************************************************************************/ +/* pj_cleanup_lock() */ +/************************************************************************/ +void pj_cleanup_lock() +{ +} + +#endif /* def MUTEX_pthread */ + +/************************************************************************/ +/* ==================================================================== */ +/* win32 mutex implementation */ +/* ==================================================================== */ +/************************************************************************/ + +#ifdef MUTEX_win32 + +#include + +static HANDLE mutex_lock = NULL; + +#if _WIN32_WINNT >= 0x0600 + +/************************************************************************/ +/* pj_create_lock() */ +/************************************************************************/ + +static BOOL CALLBACK pj_create_lock(PINIT_ONCE InitOnce, + PVOID Parameter, + PVOID *Context) +{ + (void)InitOnce; + (void)Parameter; + (void)Context; + mutex_lock = CreateMutex( NULL, FALSE, NULL ); + return TRUE; +} +#endif + +/************************************************************************/ +/* pj_init_lock() */ +/************************************************************************/ + +static void pj_init_lock() + +{ +#if _WIN32_WINNT >= 0x0600 + static INIT_ONCE sInitOnce = INIT_ONCE_STATIC_INIT; + InitOnceExecuteOnce( &sInitOnce, pj_create_lock, NULL, NULL ); +#else + if( mutex_lock == NULL ) + mutex_lock = CreateMutex( NULL, FALSE, NULL ); +#endif +} + +/************************************************************************/ +/* pj_acquire_lock() */ +/* */ +/* Acquire the PROJ.4 lock. */ +/************************************************************************/ + +void pj_acquire_lock() +{ + if( mutex_lock == NULL ) + pj_init_lock(); + + WaitForSingleObject( mutex_lock, INFINITE ); +} + +/************************************************************************/ +/* pj_release_lock() */ +/* */ +/* Release the PROJ.4 lock. */ +/************************************************************************/ + +void pj_release_lock() +{ + if( mutex_lock == NULL ) + pj_init_lock(); + else + ReleaseMutex( mutex_lock ); +} + +/************************************************************************/ +/* pj_cleanup_lock() */ +/************************************************************************/ +void pj_cleanup_lock() +{ + if( mutex_lock != NULL ) + { + CloseHandle( mutex_lock ); + mutex_lock = NULL; + } +} + +#endif /* def MUTEX_win32 */ diff --git a/src/pj_open_lib.c b/src/pj_open_lib.c deleted file mode 100644 index 6b908360..00000000 --- a/src/pj_open_lib.c +++ /dev/null @@ -1,247 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implementation of pj_open_lib(), and pj_set_finder(). These - * provide a standard interface for opening projections support - * data files. - * Author: Gerald Evenden, Frank Warmerdam - * - ****************************************************************************** - * Copyright (c) 1995, Gerald Evenden - * Copyright (c) 2002, 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 PJ_LIB__ - -#include -#include -#include -#include -#include - -#include "proj_internal.h" -#include "projects.h" - -static const char *(*pj_finder)(const char *) = NULL; -static int path_count = 0; -static char **search_path = NULL; -static const char * proj_lib_name = -#ifdef PROJ_LIB -PROJ_LIB; -#else -0; -#endif - -/************************************************************************/ -/* pj_set_finder() */ -/************************************************************************/ - -void pj_set_finder( const char *(*new_finder)(const char *) ) - -{ - pj_finder = new_finder; -} - -/************************************************************************/ -/* pj_set_searchpath() */ -/* */ -/* Path control for callers that can't practically provide */ -/* pj_set_finder() style callbacks. Call with (0,NULL) as args */ -/* to clear the searchpath set. */ -/************************************************************************/ - -void pj_set_searchpath ( int count, const char **path ) -{ - int i; - - if (path_count > 0 && search_path != NULL) - { - for (i = 0; i < path_count; i++) - { - pj_dalloc(search_path[i]); - } - pj_dalloc(search_path); - path_count = 0; - search_path = NULL; - } - - if( count > 0 ) - { - search_path = pj_malloc(sizeof *search_path * count); - for (i = 0; i < count; i++) - { - search_path[i] = pj_malloc(strlen(path[i]) + 1); - strcpy(search_path[i], path[i]); - } - } - - path_count = count; -} - -/* just a couple of helper functions that lets other functions - access the otherwise private search path */ -const char * const *proj_get_searchpath(void) { - return (const char * const *)search_path; -} - -int proj_get_path_count(void) { - return path_count; -} -/************************************************************************/ -/* pj_open_lib_ex() */ -/************************************************************************/ - -static PAFile -pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, - char* out_full_filename, size_t out_full_filename_size) { - char fname[MAX_PATH_FILENAME+1]; - const char *sysname; - PAFile fid; - int n = 0; - int i; -#ifdef WIN32 - static const char dir_chars[] = "/\\"; -#else - static const char dir_chars[] = "/"; -#endif - - if( out_full_filename != NULL && out_full_filename_size > 0 ) - out_full_filename[0] = '\0'; - - /* check if ~/name */ - if (*name == '~' && strchr(dir_chars,name[1]) ) - if ((sysname = getenv("HOME")) != NULL) { - if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) - { - return NULL; - } - (void)strcpy(fname, sysname); - fname[n = (int)strlen(fname)] = DIR_CHAR; - fname[++n] = '\0'; - (void)strcpy(fname+n, name + 1); - sysname = fname; - } else - return NULL; - - /* or fixed path: /name, ./name or ../name */ - else if (strchr(dir_chars,*name) - || (*name == '.' && strchr(dir_chars,name[1])) - || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2])) - || (name[1] == ':' && strchr(dir_chars,name[2])) ) - sysname = name; - - /* or try to use application provided file finder */ - else if( pj_finder != NULL && pj_finder( name ) != NULL ) - sysname = pj_finder( name ); - - /* or is environment PROJ_LIB defined */ - else if ((sysname = getenv("PROJ_LIB")) || (sysname = proj_lib_name)) { - if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) - { - return NULL; - } - (void)strcpy(fname, sysname); - fname[n = (int)strlen(fname)] = DIR_CHAR; - fname[++n] = '\0'; - (void)strcpy(fname+n, name); - sysname = fname; - } else /* just try it bare bones */ - sysname = name; - - if ((fid = pj_ctx_fopen(ctx, sysname, mode)) != NULL) - { - if( out_full_filename != NULL && out_full_filename_size > 0 ) - { - strncpy(out_full_filename, sysname, out_full_filename_size); - out_full_filename[out_full_filename_size-1] = '\0'; - } - errno = 0; - } - - /* If none of those work and we have a search path, try it */ - if (!fid && path_count > 0) - { - for (i = 0; fid == NULL && i < path_count; i++) - { - if( strlen(search_path[i]) + 1 + strlen(name) + 1 <= sizeof(fname) ) - { - sprintf(fname, "%s%c%s", search_path[i], DIR_CHAR, name); - sysname = fname; - fid = pj_ctx_fopen(ctx, sysname, mode); - } - } - if (fid) - { - if( out_full_filename != NULL && out_full_filename_size > 0 ) - { - strncpy(out_full_filename, sysname, out_full_filename_size); - out_full_filename[out_full_filename_size-1] = '\0'; - } - errno = 0; - } - } - - if( ctx->last_errno == 0 && errno != 0 ) - pj_ctx_set_errno( ctx, errno ); - - pj_log( ctx, PJ_LOG_DEBUG_MAJOR, - "pj_open_lib(%s): call fopen(%s) - %s", - name, sysname, - fid == NULL ? "failed" : "succeeded" ); - - return(fid); -} - -/************************************************************************/ -/* pj_open_lib() */ -/************************************************************************/ - -PAFile -pj_open_lib(projCtx ctx, const char *name, const char *mode) { - return pj_open_lib_ex(ctx, name, mode, NULL, 0); -} - -/************************************************************************/ -/* pj_find_file() */ -/************************************************************************/ - -/** Returns the full filename corresponding to a proj resource file specified - * as a short filename. - * - * @param ctx context. - * @param short_filename short filename (e.g. egm96_15.gtx) - * @param out_full_filename output buffer, of size out_full_filename_size, that - * will receive the full filename on success. - * Will be zero-terminated. - * @param out_full_filename_size size of out_full_filename. - * @return 1 if the file was found, 0 otherwise. - */ -int pj_find_file(projCtx ctx, const char *short_filename, - char* out_full_filename, size_t out_full_filename_size) -{ - PAFile f = pj_open_lib_ex(ctx, short_filename, "rb", out_full_filename, - out_full_filename_size); - if( f != NULL ) - { - pj_ctx_fclose(ctx, f); - return 1; - } - return 0; -} diff --git a/src/pj_open_lib.cpp b/src/pj_open_lib.cpp new file mode 100644 index 00000000..31a2c2e1 --- /dev/null +++ b/src/pj_open_lib.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Implementation of pj_open_lib(), and pj_set_finder(). These + * provide a standard interface for opening projections support + * data files. + * Author: Gerald Evenden, Frank Warmerdam + * + ****************************************************************************** + * Copyright (c) 1995, Gerald Evenden + * Copyright (c) 2002, 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 PJ_LIB__ + +#include +#include +#include +#include +#include + +#include "proj_internal.h" +#include "projects.h" + +static const char *(*pj_finder)(const char *) = NULL; +static int path_count = 0; +static char **search_path = NULL; +static const char * proj_lib_name = +#ifdef PROJ_LIB +PROJ_LIB; +#else +0; +#endif + +/************************************************************************/ +/* pj_set_finder() */ +/************************************************************************/ + +void pj_set_finder( const char *(*new_finder)(const char *) ) + +{ + pj_finder = new_finder; +} + +/************************************************************************/ +/* pj_set_searchpath() */ +/* */ +/* Path control for callers that can't practically provide */ +/* pj_set_finder() style callbacks. Call with (0,NULL) as args */ +/* to clear the searchpath set. */ +/************************************************************************/ + +void pj_set_searchpath ( int count, const char **path ) +{ + int i; + + if (path_count > 0 && search_path != NULL) + { + for (i = 0; i < path_count; i++) + { + pj_dalloc(search_path[i]); + } + pj_dalloc(search_path); + path_count = 0; + search_path = NULL; + } + + if( count > 0 ) + { + search_path = static_cast(pj_malloc(sizeof *search_path * count)); + for (i = 0; i < count; i++) + { + search_path[i] = static_cast(pj_malloc(strlen(path[i]) + 1)); + strcpy(search_path[i], path[i]); + } + } + + path_count = count; +} + +/* just a couple of helper functions that lets other functions + access the otherwise private search path */ +const char * const *proj_get_searchpath(void) { + return (const char * const *)search_path; +} + +int proj_get_path_count(void) { + return path_count; +} +/************************************************************************/ +/* pj_open_lib_ex() */ +/************************************************************************/ + +static PAFile +pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, + char* out_full_filename, size_t out_full_filename_size) { + char fname[MAX_PATH_FILENAME+1]; + const char *sysname; + PAFile fid; + int n = 0; + int i; +#ifdef WIN32 + static const char dir_chars[] = "/\\"; +#else + static const char dir_chars[] = "/"; +#endif + + if( out_full_filename != NULL && out_full_filename_size > 0 ) + out_full_filename[0] = '\0'; + + /* check if ~/name */ + if (*name == '~' && strchr(dir_chars,name[1]) ) + if ((sysname = getenv("HOME")) != NULL) { + if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) + { + return NULL; + } + (void)strcpy(fname, sysname); + fname[n = (int)strlen(fname)] = DIR_CHAR; + fname[++n] = '\0'; + (void)strcpy(fname+n, name + 1); + sysname = fname; + } else + return NULL; + + /* or fixed path: /name, ./name or ../name */ + else if (strchr(dir_chars,*name) + || (*name == '.' && strchr(dir_chars,name[1])) + || (!strncmp(name, "..", 2) && strchr(dir_chars,name[2])) + || (name[1] == ':' && strchr(dir_chars,name[2])) ) + sysname = name; + + /* or try to use application provided file finder */ + else if( pj_finder != NULL && pj_finder( name ) != NULL ) + sysname = pj_finder( name ); + + /* or is environment PROJ_LIB defined */ + else if ((sysname = getenv("PROJ_LIB")) || (sysname = proj_lib_name)) { + if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) + { + return NULL; + } + (void)strcpy(fname, sysname); + fname[n = (int)strlen(fname)] = DIR_CHAR; + fname[++n] = '\0'; + (void)strcpy(fname+n, name); + sysname = fname; + } else /* just try it bare bones */ + sysname = name; + + if ((fid = pj_ctx_fopen(ctx, sysname, mode)) != NULL) + { + if( out_full_filename != NULL && out_full_filename_size > 0 ) + { + strncpy(out_full_filename, sysname, out_full_filename_size); + out_full_filename[out_full_filename_size-1] = '\0'; + } + errno = 0; + } + + /* If none of those work and we have a search path, try it */ + if (!fid && path_count > 0) + { + for (i = 0; fid == NULL && i < path_count; i++) + { + if( strlen(search_path[i]) + 1 + strlen(name) + 1 <= sizeof(fname) ) + { + sprintf(fname, "%s%c%s", search_path[i], DIR_CHAR, name); + sysname = fname; + fid = pj_ctx_fopen(ctx, sysname, mode); + } + } + if (fid) + { + if( out_full_filename != NULL && out_full_filename_size > 0 ) + { + strncpy(out_full_filename, sysname, out_full_filename_size); + out_full_filename[out_full_filename_size-1] = '\0'; + } + errno = 0; + } + } + + if( ctx->last_errno == 0 && errno != 0 ) + pj_ctx_set_errno( ctx, errno ); + + pj_log( ctx, PJ_LOG_DEBUG_MAJOR, + "pj_open_lib(%s): call fopen(%s) - %s", + name, sysname, + fid == NULL ? "failed" : "succeeded" ); + + return(fid); +} + +/************************************************************************/ +/* pj_open_lib() */ +/************************************************************************/ + +PAFile +pj_open_lib(projCtx ctx, const char *name, const char *mode) { + return pj_open_lib_ex(ctx, name, mode, NULL, 0); +} + +/************************************************************************/ +/* pj_find_file() */ +/************************************************************************/ + +/** Returns the full filename corresponding to a proj resource file specified + * as a short filename. + * + * @param ctx context. + * @param short_filename short filename (e.g. egm96_15.gtx) + * @param out_full_filename output buffer, of size out_full_filename_size, that + * will receive the full filename on success. + * Will be zero-terminated. + * @param out_full_filename_size size of out_full_filename. + * @return 1 if the file was found, 0 otherwise. + */ +int pj_find_file(projCtx ctx, const char *short_filename, + char* out_full_filename, size_t out_full_filename_size) +{ + PAFile f = pj_open_lib_ex(ctx, short_filename, "rb", out_full_filename, + out_full_filename_size); + if( f != NULL ) + { + pj_ctx_fclose(ctx, f); + return 1; + } + return 0; +} diff --git a/src/pj_param.c b/src/pj_param.c deleted file mode 100644 index 4078dc83..00000000 --- a/src/pj_param.c +++ /dev/null @@ -1,189 +0,0 @@ -/* put parameters in linked list and retrieve */ - -#include -#include -#include -#include -#include - -#include "projects.h" - -/* create parameter list entry */ -paralist *pj_mkparam(const char *str) { - paralist *newitem; - - if((newitem = (paralist *)pj_malloc(sizeof(paralist) + strlen(str))) != NULL) { - newitem->used = 0; - newitem->next = 0; - if (*str == '+') - ++str; - (void)strcpy(newitem->param, str); - } - return newitem; -} - - -/* As pj_mkparam, but payload ends at first whitespace, rather than at end of */ -paralist *pj_mkparam_ws (const char *str) { - paralist *newitem; - size_t len = 0; - - if (0==str) - return 0; - - /* Find start and length of string */ - while (isspace (*str)) - str++; - while ((!isspace(str[len])) && 0!=str[len]) - len++; - if (*str == '+') { - str++; - len--; - } - - /* Use calloc to automagically 0-terminate the copy */ - newitem = (paralist *) pj_calloc (1, sizeof(paralist) + len + 1); - if (0==newitem) - return 0; - memmove(newitem->param, str, len); - - newitem->used = 0; - newitem->next = 0; - - return newitem; -} - -/**************************************************************************************/ -paralist *pj_param_exists (paralist *list, const char *parameter) { -/*************************************************************************************** - Determine whether a given parameter exists in a paralist. If it does, return - a pointer to the corresponding list element - otherwise return 0. - - In support of the pipeline syntax, the search is terminated once a "+step" list - element is reached, in which case a 0 is returned, unless the parameter - searched for is actually "step", in which case a pointer to the "step" list - element is returned. - - This function is equivalent to the pj_param (...) call with the "opt" argument - set to the parameter name preceeeded by a 't'. But by using this one, one avoids - writing the code allocating memory for a new copy of parameter name, and prepending - the t (for compile time known names, this is obviously not an issue). -***************************************************************************************/ - paralist *next = list; - char *c = strchr (parameter, '='); - size_t len = strlen (parameter); - if (c) - len = c - parameter; - if (list==0) - return 0; - - for (next = list; next; next = next->next) { - if (0==strncmp (parameter, next->param, len) && (next->param[len]=='=' || next->param[len]==0)) { - next->used = 1; - return next; - } - if (0==strcmp (parameter, "step")) - return 0; - } - - return 0; -} - - -/************************************************************************/ -/* pj_param() */ -/* */ -/* Test for presence or get parameter value. The first */ -/* character in `opt' is a parameter type which can take the */ -/* values: */ -/* */ -/* `t' - test for presence, return TRUE/FALSE in PROJVALUE.i */ -/* `i' - integer value returned in PROJVALUE.i */ -/* `d' - simple valued real input returned in PROJVALUE.f */ -/* `r' - degrees (DMS translation applied), returned as */ -/* radians in PROJVALUE.f */ -/* `s' - string returned in PROJVALUE.s */ -/* `b' - test for t/T/f/F, return in PROJVALUE.i */ -/* */ -/* Search is terminated when "step" is found, in which case */ -/* 0 is returned, unless "step" was the target searched for. */ -/* */ -/************************************************************************/ - -PROJVALUE pj_param (projCtx ctx, paralist *pl, const char *opt) { - - int type; - unsigned l; - PROJVALUE value = {0}; - - if ( ctx == NULL ) - ctx = pj_get_default_ctx(); - - type = *opt++; - - if (0==strchr ("tbirds", type)) { - fprintf(stderr, "invalid request to pj_param, fatal\n"); - exit(1); - } - - pl = pj_param_exists (pl, opt); - if (type == 't') { - value.i = pl != 0; - return value; - } - - /* Not found */ - if (0==pl) { - /* Return value after the switch, so that the return path is */ - /* taken in all cases */ - switch (type) { - case 'b': case 'i': - value.i = 0; - break; - case 'd': case 'r': - value.f = 0.; - break; - case 's': - value.s = 0; - break; - } - return value; - } - - /* Found parameter - now find its value */ - pl->used |= 1; - l = (int) strlen(opt); - opt = pl->param + l; - if (*opt == '=') - ++opt; - - switch (type) { - case 'i': /* integer input */ - value.i = atoi(opt); - break; - case 'd': /* simple real input */ - value.f = pj_atof(opt); - break; - case 'r': /* degrees input */ - value.f = dmstor_ctx(ctx, opt, 0); - break; - case 's': /* char string */ - value.s = (char *) opt; - break; - case 'b': /* boolean */ - switch (*opt) { - case 'F': case 'f': - value.i = 0; - break; - case '\0': case 'T': case 't': - value.i = 1; - break; - default: - pj_ctx_set_errno (ctx, PJD_ERR_INVALID_BOOLEAN_PARAM); - value.i = 0; - break; - } - break; - } - return value; -} diff --git a/src/pj_param.cpp b/src/pj_param.cpp new file mode 100644 index 00000000..1887afe9 --- /dev/null +++ b/src/pj_param.cpp @@ -0,0 +1,189 @@ +/* put parameters in linked list and retrieve */ + +#include +#include +#include +#include +#include + +#include "projects.h" + +/* create parameter list entry */ +paralist *pj_mkparam(const char *str) { + paralist *newitem; + + if((newitem = (paralist *)pj_malloc(sizeof(paralist) + strlen(str))) != NULL) { + newitem->used = 0; + newitem->next = 0; + if (*str == '+') + ++str; + (void)strcpy(newitem->param, str); + } + return newitem; +} + + +/* As pj_mkparam, but payload ends at first whitespace, rather than at end of */ +paralist *pj_mkparam_ws (const char *str) { + paralist *newitem; + size_t len = 0; + + if (0==str) + return 0; + + /* Find start and length of string */ + while (isspace (*str)) + str++; + while ((!isspace(str[len])) && 0!=str[len]) + len++; + if (*str == '+') { + str++; + len--; + } + + /* Use calloc to automagically 0-terminate the copy */ + newitem = (paralist *) pj_calloc (1, sizeof(paralist) + len + 1); + if (0==newitem) + return 0; + memmove(newitem->param, str, len); + + newitem->used = 0; + newitem->next = 0; + + return newitem; +} + +/**************************************************************************************/ +paralist *pj_param_exists (paralist *list, const char *parameter) { +/*************************************************************************************** + Determine whether a given parameter exists in a paralist. If it does, return + a pointer to the corresponding list element - otherwise return 0. + + In support of the pipeline syntax, the search is terminated once a "+step" list + element is reached, in which case a 0 is returned, unless the parameter + searched for is actually "step", in which case a pointer to the "step" list + element is returned. + + This function is equivalent to the pj_param (...) call with the "opt" argument + set to the parameter name preceeeded by a 't'. But by using this one, one avoids + writing the code allocating memory for a new copy of parameter name, and prepending + the t (for compile time known names, this is obviously not an issue). +***************************************************************************************/ + paralist *next = list; + const char *c = strchr (parameter, '='); + size_t len = strlen (parameter); + if (c) + len = c - parameter; + if (list==0) + return 0; + + for (next = list; next; next = next->next) { + if (0==strncmp (parameter, next->param, len) && (next->param[len]=='=' || next->param[len]==0)) { + next->used = 1; + return next; + } + if (0==strcmp (parameter, "step")) + return 0; + } + + return 0; +} + + +/************************************************************************/ +/* pj_param() */ +/* */ +/* Test for presence or get parameter value. The first */ +/* character in `opt' is a parameter type which can take the */ +/* values: */ +/* */ +/* `t' - test for presence, return TRUE/FALSE in PROJVALUE.i */ +/* `i' - integer value returned in PROJVALUE.i */ +/* `d' - simple valued real input returned in PROJVALUE.f */ +/* `r' - degrees (DMS translation applied), returned as */ +/* radians in PROJVALUE.f */ +/* `s' - string returned in PROJVALUE.s */ +/* `b' - test for t/T/f/F, return in PROJVALUE.i */ +/* */ +/* Search is terminated when "step" is found, in which case */ +/* 0 is returned, unless "step" was the target searched for. */ +/* */ +/************************************************************************/ + +PROJVALUE pj_param (projCtx ctx, paralist *pl, const char *opt) { + + int type; + unsigned l; + PROJVALUE value = {0}; + + if ( ctx == NULL ) + ctx = pj_get_default_ctx(); + + type = *opt++; + + if (0==strchr ("tbirds", type)) { + fprintf(stderr, "invalid request to pj_param, fatal\n"); + exit(1); + } + + pl = pj_param_exists (pl, opt); + if (type == 't') { + value.i = pl != 0; + return value; + } + + /* Not found */ + if (0==pl) { + /* Return value after the switch, so that the return path is */ + /* taken in all cases */ + switch (type) { + case 'b': case 'i': + value.i = 0; + break; + case 'd': case 'r': + value.f = 0.; + break; + case 's': + value.s = 0; + break; + } + return value; + } + + /* Found parameter - now find its value */ + pl->used |= 1; + l = (int) strlen(opt); + opt = pl->param + l; + if (*opt == '=') + ++opt; + + switch (type) { + case 'i': /* integer input */ + value.i = atoi(opt); + break; + case 'd': /* simple real input */ + value.f = pj_atof(opt); + break; + case 'r': /* degrees input */ + value.f = dmstor_ctx(ctx, opt, 0); + break; + case 's': /* char string */ + value.s = (char *) opt; + break; + case 'b': /* boolean */ + switch (*opt) { + case 'F': case 'f': + value.i = 0; + break; + case '\0': case 'T': case 't': + value.i = 1; + break; + default: + pj_ctx_set_errno (ctx, PJD_ERR_INVALID_BOOLEAN_PARAM); + value.i = 0; + break; + } + break; + } + return value; +} diff --git a/src/pj_phi2.c b/src/pj_phi2.c deleted file mode 100644 index a83302e6..00000000 --- a/src/pj_phi2.c +++ /dev/null @@ -1,46 +0,0 @@ -/* Determine latitude angle phi-2. */ - -#include - -#include "projects.h" - -static const double TOL = 1.0e-10; -static const int N_ITER = 15; - -/*****************************************************************************/ -double pj_phi2(projCtx ctx, double ts, double e) { -/****************************************************************************** -Determine latitude angle phi-2. -Inputs: - ts = exp(-psi) where psi is the isometric latitude (dimensionless) - e = eccentricity of the ellipsoid (dimensionless) -Output: - phi = geographic latitude (radians) -Here isometric latitude is defined by - psi = log( tan(pi/4 + phi/2) * - ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) - = asinh(tan(phi)) - e * atanh(e * sin(phi)) -This routine inverts this relation using the iterative scheme given -by Snyder (1987), Eqs. (7-9) - (7-11) -*******************************************************************************/ - double eccnth = .5 * e; - double Phi = M_HALFPI - 2. * atan(ts); - double con; - int i = N_ITER; - - for(;;) { - double dphi; - con = e * sin(Phi); - dphi = M_HALFPI - 2. * atan(ts * pow((1. - con) / - (1. + con), eccnth)) - Phi; - - Phi += dphi; - - if (fabs(dphi) > TOL && --i) - continue; - break; - } - if (i <= 0) - pj_ctx_set_errno(ctx, PJD_ERR_NON_CON_INV_PHI2); - return Phi; -} diff --git a/src/pj_phi2.cpp b/src/pj_phi2.cpp new file mode 100644 index 00000000..a83302e6 --- /dev/null +++ b/src/pj_phi2.cpp @@ -0,0 +1,46 @@ +/* Determine latitude angle phi-2. */ + +#include + +#include "projects.h" + +static const double TOL = 1.0e-10; +static const int N_ITER = 15; + +/*****************************************************************************/ +double pj_phi2(projCtx ctx, double ts, double e) { +/****************************************************************************** +Determine latitude angle phi-2. +Inputs: + ts = exp(-psi) where psi is the isometric latitude (dimensionless) + e = eccentricity of the ellipsoid (dimensionless) +Output: + phi = geographic latitude (radians) +Here isometric latitude is defined by + psi = log( tan(pi/4 + phi/2) * + ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) + = asinh(tan(phi)) - e * atanh(e * sin(phi)) +This routine inverts this relation using the iterative scheme given +by Snyder (1987), Eqs. (7-9) - (7-11) +*******************************************************************************/ + double eccnth = .5 * e; + double Phi = M_HALFPI - 2. * atan(ts); + double con; + int i = N_ITER; + + for(;;) { + double dphi; + con = e * sin(Phi); + dphi = M_HALFPI - 2. * atan(ts * pow((1. - con) / + (1. + con), eccnth)) - Phi; + + Phi += dphi; + + if (fabs(dphi) > TOL && --i) + continue; + break; + } + if (i <= 0) + pj_ctx_set_errno(ctx, PJD_ERR_NON_CON_INV_PHI2); + return Phi; +} diff --git a/src/pj_pr_list.c b/src/pj_pr_list.c deleted file mode 100644 index 4e71e471..00000000 --- a/src/pj_pr_list.c +++ /dev/null @@ -1,104 +0,0 @@ -/* print projection's list of parameters */ - -#include -#include -#include - -#include "projects.h" - -#define LINE_LEN 72 - static int -pr_list(PJ *P, int not_used) { - paralist *t; - int l, n = 1, flag = 0; - - (void)putchar('#'); - for (t = P->params; t; t = t->next) - if ((!not_used && t->used) || (not_used && !t->used)) { - l = (int)strlen(t->param) + 1; - if (n + l > LINE_LEN) { - (void)fputs("\n#", stdout); - n = 2; - } - (void)putchar(' '); - if (*(t->param) != '+') - (void)putchar('+'); - (void)fputs(t->param, stdout); - n += l; - } else - flag = 1; - if (n > 1) - (void)putchar('\n'); - return flag; -} - void /* print link list of projection parameters */ -pj_pr_list(PJ *P) { - char const *s; - - (void)putchar('#'); - for (s = P->descr; *s ; ++s) { - (void)putchar(*s); - if (*s == '\n') - (void)putchar('#'); - } - (void)putchar('\n'); - if (pr_list(P, 0)) { - (void)fputs("#--- following specified but NOT used\n", stdout); - (void)pr_list(P, 1); - } -} - -/************************************************************************/ -/* pj_get_def() */ -/* */ -/* Returns the PROJ.4 command string that would produce this */ -/* definition expanded as much as possible. For instance, */ -/* +init= calls and +datum= definitions would be expanded. */ -/************************************************************************/ - -char *pj_get_def( PJ *P, int options ) - -{ - paralist *t; - int l; - char *definition; - size_t def_max = 10; - (void) options; - - definition = (char *) pj_malloc(def_max); - if (!definition) - return NULL; - definition[0] = '\0'; - - for (t = P->params; t; t = t->next) - { - /* skip unused parameters ... mostly appended defaults and stuff */ - if (!t->used) - continue; - - /* grow the resulting string if needed */ - l = (int)strlen(t->param) + 1; - if( strlen(definition) + l + 5 > def_max ) - { - char *def2; - - def_max = def_max * 2 + l + 5; - def2 = (char *) pj_malloc(def_max); - if (def2) { - strcpy( def2, definition ); - pj_dalloc( definition ); - definition = def2; - } - else { - pj_dalloc( definition ); - return NULL; - } - } - - /* append this parameter */ - strcat( definition, " +" ); - strcat( definition, t->param ); - } - - return definition; -} diff --git a/src/pj_pr_list.cpp b/src/pj_pr_list.cpp new file mode 100644 index 00000000..4e71e471 --- /dev/null +++ b/src/pj_pr_list.cpp @@ -0,0 +1,104 @@ +/* print projection's list of parameters */ + +#include +#include +#include + +#include "projects.h" + +#define LINE_LEN 72 + static int +pr_list(PJ *P, int not_used) { + paralist *t; + int l, n = 1, flag = 0; + + (void)putchar('#'); + for (t = P->params; t; t = t->next) + if ((!not_used && t->used) || (not_used && !t->used)) { + l = (int)strlen(t->param) + 1; + if (n + l > LINE_LEN) { + (void)fputs("\n#", stdout); + n = 2; + } + (void)putchar(' '); + if (*(t->param) != '+') + (void)putchar('+'); + (void)fputs(t->param, stdout); + n += l; + } else + flag = 1; + if (n > 1) + (void)putchar('\n'); + return flag; +} + void /* print link list of projection parameters */ +pj_pr_list(PJ *P) { + char const *s; + + (void)putchar('#'); + for (s = P->descr; *s ; ++s) { + (void)putchar(*s); + if (*s == '\n') + (void)putchar('#'); + } + (void)putchar('\n'); + if (pr_list(P, 0)) { + (void)fputs("#--- following specified but NOT used\n", stdout); + (void)pr_list(P, 1); + } +} + +/************************************************************************/ +/* pj_get_def() */ +/* */ +/* Returns the PROJ.4 command string that would produce this */ +/* definition expanded as much as possible. For instance, */ +/* +init= calls and +datum= definitions would be expanded. */ +/************************************************************************/ + +char *pj_get_def( PJ *P, int options ) + +{ + paralist *t; + int l; + char *definition; + size_t def_max = 10; + (void) options; + + definition = (char *) pj_malloc(def_max); + if (!definition) + return NULL; + definition[0] = '\0'; + + for (t = P->params; t; t = t->next) + { + /* skip unused parameters ... mostly appended defaults and stuff */ + if (!t->used) + continue; + + /* grow the resulting string if needed */ + l = (int)strlen(t->param) + 1; + if( strlen(definition) + l + 5 > def_max ) + { + char *def2; + + def_max = def_max * 2 + l + 5; + def2 = (char *) pj_malloc(def_max); + if (def2) { + strcpy( def2, definition ); + pj_dalloc( definition ); + definition = def2; + } + else { + pj_dalloc( definition ); + return NULL; + } + } + + /* append this parameter */ + strcat( definition, " +" ); + strcat( definition, t->param ); + } + + return definition; +} diff --git a/src/pj_qsfn.c b/src/pj_qsfn.c deleted file mode 100644 index c18a7b95..00000000 --- a/src/pj_qsfn.c +++ /dev/null @@ -1,22 +0,0 @@ -/* determine small q */ -#include -#include "projects.h" - -# define EPSILON 1.0e-7 - -double pj_qsfn(double sinphi, double e, double one_es) { - double con, div1, div2; - - if (e >= EPSILON) { - con = e * sinphi; - div1 = 1.0 - con * con; - div2 = 1.0 + con; - - /* avoid zero division, fail gracefully */ - if (div1 == 0.0 || div2 == 0.0) - return HUGE_VAL; - - return (one_es * (sinphi / div1 - (.5 / e) * log ((1. - con) / div2 ))); - } else - return (sinphi + sinphi); -} diff --git a/src/pj_qsfn.cpp b/src/pj_qsfn.cpp new file mode 100644 index 00000000..c18a7b95 --- /dev/null +++ b/src/pj_qsfn.cpp @@ -0,0 +1,22 @@ +/* determine small q */ +#include +#include "projects.h" + +# define EPSILON 1.0e-7 + +double pj_qsfn(double sinphi, double e, double one_es) { + double con, div1, div2; + + if (e >= EPSILON) { + con = e * sinphi; + div1 = 1.0 - con * con; + div2 = 1.0 + con; + + /* avoid zero division, fail gracefully */ + if (div1 == 0.0 || div2 == 0.0) + return HUGE_VAL; + + return (one_es * (sinphi / div1 - (.5 / e) * log ((1. - con) / div2 ))); + } else + return (sinphi + sinphi); +} diff --git a/src/pj_release.c b/src/pj_release.c deleted file mode 100644 index 9beb45ef..00000000 --- a/src/pj_release.c +++ /dev/null @@ -1,18 +0,0 @@ -/* <<< Release Notice for library >>> */ - -#include "proj.h" -#include "projects.h" - -#define STR_HELPER(x) #x -#define STR(x) STR_HELPER(x) - -char const pj_release[] = - "Rel. " - STR(PROJ_VERSION_MAJOR)"." - STR(PROJ_VERSION_MINOR)"." - STR(PROJ_VERSION_PATCH)", " - "March 1st, 2019"; - -const char *pj_get_release() { - return pj_release; -} diff --git a/src/pj_release.cpp b/src/pj_release.cpp new file mode 100644 index 00000000..9beb45ef --- /dev/null +++ b/src/pj_release.cpp @@ -0,0 +1,18 @@ +/* <<< Release Notice for library >>> */ + +#include "proj.h" +#include "projects.h" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +char const pj_release[] = + "Rel. " + STR(PROJ_VERSION_MAJOR)"." + STR(PROJ_VERSION_MINOR)"." + STR(PROJ_VERSION_PATCH)", " + "March 1st, 2019"; + +const char *pj_get_release() { + return pj_release; +} diff --git a/src/pj_strerrno.c b/src/pj_strerrno.c deleted file mode 100644 index 16042f79..00000000 --- a/src/pj_strerrno.c +++ /dev/null @@ -1,109 +0,0 @@ -/* list of projection system pj_errno values */ - -#include -#include -#include - -#include "proj.h" -#include "projects.h" - -static const char * const -pj_err_list[] = { - "no arguments in initialization list", /* -1 */ - "no options found in 'init' file", /* -2 */ - "no colon in init= string", /* -3 */ - "projection not named", /* -4 */ - "unknown projection id", /* -5 */ - "effective eccentricity = 1.", /* -6 */ - "unknown unit conversion id", /* -7 */ - "invalid boolean param argument", /* -8 */ - "unknown elliptical parameter name", /* -9 */ - "reciprocal flattening (1/f) = 0", /* -10 */ - "|radius reference latitude| > 90", /* -11 */ - "squared eccentricity < 0", /* -12 */ - "major axis or radius = 0 or not given", /* -13 */ - "latitude or longitude exceeded limits", /* -14 */ - "invalid x or y", /* -15 */ - "improperly formed DMS value", /* -16 */ - "non-convergent inverse meridional dist", /* -17 */ - "non-convergent inverse phi2", /* -18 */ - "acos/asin: |arg| >1.+1e-14", /* -19 */ - "tolerance condition error", /* -20 */ - "conic lat_1 = -lat_2", /* -21 */ - "lat_1 >= 90", /* -22 */ - "lat_1 = 0", /* -23 */ - "lat_ts >= 90", /* -24 */ - "no distance between control points", /* -25 */ - "projection not selected to be rotated", /* -26 */ - "W <= 0 or M <= 0", /* -27 */ - "lsat not in 1-5 range", /* -28 */ - "path not in range", /* -29 */ - "h <= 0", /* -30 */ - "k <= 0", /* -31 */ - "lat_0 = 0 or 90 or alpha = 90", /* -32 */ - "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -33 */ - "elliptical usage required", /* -34 */ - "invalid UTM zone number", /* -35 */ - "arg(s) out of range for Tcheby eval", /* -36 */ - "failed to find projection to be rotated", /* -37 */ - "failed to load datum shift file", /* -38 */ - "both n & m must be spec'd and > 0", /* -39 */ - "n <= 0, n > 1 or not specified", /* -40 */ - "lat_1 or lat_2 not specified", /* -41 */ - "|lat_1| == |lat_2|", /* -42 */ - "lat_0 is pi/2 from mean lat", /* -43 */ - "unparseable coordinate system definition", /* -44 */ - "geocentric transformation missing z or ellps", /* -45 */ - "unknown prime meridian conversion id", /* -46 */ - "illegal axis orientation combination", /* -47 */ - "point not within available datum shift grids", /* -48 */ - "invalid sweep axis, choose x or y", /* -49 */ - "malformed pipeline", /* -50 */ - "unit conversion factor must be > 0", /* -51 */ - "invalid scale", /* -52 */ - "non-convergent computation", /* -53 */ - "missing required arguments", /* -54 */ - "lat_0 = 0", /* -55 */ - "ellipsoidal usage unsupported", /* -56 */ - "only one +init allowed for non-pipeline operations", /* -57 */ - "argument not numerical or out of range", /* -58 */ - "inconsistent unit type between input and output", /* -59 */ - - /* When adding error messages, remember to update ID defines in - projects.h, and transient_error array in pj_transform */ -}; - -char *pj_strerrno(int err) { - const int max_error = 9999; - static char note[50]; - size_t adjusted_err; - - if (0==err) - return 0; - - /* System error codes are positive */ - if (err > 0) { -#ifdef HAVE_STRERROR - return strerror(err); -#else - /* Defend string boundary against exorbitantly large err values */ - /* which may occur on platforms with 64-bit ints */ - sprintf(note, "no system list, errno: %d\n", - (err < max_error) ? err: max_error); - return note; -#endif - } - - /* PROJ.4 error codes are negative: -1 to -9999 */ - adjusted_err = err < -max_error ? max_error : -err - 1; - if (adjusted_err < (sizeof(pj_err_list) / sizeof(char *))) - return (char *)pj_err_list[adjusted_err]; - - sprintf(note, "invalid projection system error (%d)", - (err > -max_error) ? err: -max_error); - return note; -} - -const char* proj_errno_string(int err) { - return pj_strerrno(err); -} diff --git a/src/pj_strerrno.cpp b/src/pj_strerrno.cpp new file mode 100644 index 00000000..16042f79 --- /dev/null +++ b/src/pj_strerrno.cpp @@ -0,0 +1,109 @@ +/* list of projection system pj_errno values */ + +#include +#include +#include + +#include "proj.h" +#include "projects.h" + +static const char * const +pj_err_list[] = { + "no arguments in initialization list", /* -1 */ + "no options found in 'init' file", /* -2 */ + "no colon in init= string", /* -3 */ + "projection not named", /* -4 */ + "unknown projection id", /* -5 */ + "effective eccentricity = 1.", /* -6 */ + "unknown unit conversion id", /* -7 */ + "invalid boolean param argument", /* -8 */ + "unknown elliptical parameter name", /* -9 */ + "reciprocal flattening (1/f) = 0", /* -10 */ + "|radius reference latitude| > 90", /* -11 */ + "squared eccentricity < 0", /* -12 */ + "major axis or radius = 0 or not given", /* -13 */ + "latitude or longitude exceeded limits", /* -14 */ + "invalid x or y", /* -15 */ + "improperly formed DMS value", /* -16 */ + "non-convergent inverse meridional dist", /* -17 */ + "non-convergent inverse phi2", /* -18 */ + "acos/asin: |arg| >1.+1e-14", /* -19 */ + "tolerance condition error", /* -20 */ + "conic lat_1 = -lat_2", /* -21 */ + "lat_1 >= 90", /* -22 */ + "lat_1 = 0", /* -23 */ + "lat_ts >= 90", /* -24 */ + "no distance between control points", /* -25 */ + "projection not selected to be rotated", /* -26 */ + "W <= 0 or M <= 0", /* -27 */ + "lsat not in 1-5 range", /* -28 */ + "path not in range", /* -29 */ + "h <= 0", /* -30 */ + "k <= 0", /* -31 */ + "lat_0 = 0 or 90 or alpha = 90", /* -32 */ + "lat_1=lat_2 or lat_1=0 or lat_2=90", /* -33 */ + "elliptical usage required", /* -34 */ + "invalid UTM zone number", /* -35 */ + "arg(s) out of range for Tcheby eval", /* -36 */ + "failed to find projection to be rotated", /* -37 */ + "failed to load datum shift file", /* -38 */ + "both n & m must be spec'd and > 0", /* -39 */ + "n <= 0, n > 1 or not specified", /* -40 */ + "lat_1 or lat_2 not specified", /* -41 */ + "|lat_1| == |lat_2|", /* -42 */ + "lat_0 is pi/2 from mean lat", /* -43 */ + "unparseable coordinate system definition", /* -44 */ + "geocentric transformation missing z or ellps", /* -45 */ + "unknown prime meridian conversion id", /* -46 */ + "illegal axis orientation combination", /* -47 */ + "point not within available datum shift grids", /* -48 */ + "invalid sweep axis, choose x or y", /* -49 */ + "malformed pipeline", /* -50 */ + "unit conversion factor must be > 0", /* -51 */ + "invalid scale", /* -52 */ + "non-convergent computation", /* -53 */ + "missing required arguments", /* -54 */ + "lat_0 = 0", /* -55 */ + "ellipsoidal usage unsupported", /* -56 */ + "only one +init allowed for non-pipeline operations", /* -57 */ + "argument not numerical or out of range", /* -58 */ + "inconsistent unit type between input and output", /* -59 */ + + /* When adding error messages, remember to update ID defines in + projects.h, and transient_error array in pj_transform */ +}; + +char *pj_strerrno(int err) { + const int max_error = 9999; + static char note[50]; + size_t adjusted_err; + + if (0==err) + return 0; + + /* System error codes are positive */ + if (err > 0) { +#ifdef HAVE_STRERROR + return strerror(err); +#else + /* Defend string boundary against exorbitantly large err values */ + /* which may occur on platforms with 64-bit ints */ + sprintf(note, "no system list, errno: %d\n", + (err < max_error) ? err: max_error); + return note; +#endif + } + + /* PROJ.4 error codes are negative: -1 to -9999 */ + adjusted_err = err < -max_error ? max_error : -err - 1; + if (adjusted_err < (sizeof(pj_err_list) / sizeof(char *))) + return (char *)pj_err_list[adjusted_err]; + + sprintf(note, "invalid projection system error (%d)", + (err > -max_error) ? err: -max_error); + return note; +} + +const char* proj_errno_string(int err) { + return pj_strerrno(err); +} diff --git a/src/pj_strtod.c b/src/pj_strtod.c deleted file mode 100644 index f604a013..00000000 --- a/src/pj_strtod.c +++ /dev/null @@ -1,195 +0,0 @@ -/****************************************************************************** - * - * Derived from GDAL port/cpl_strtod.cpp - * Purpose: Functions to convert ASCII string to floating point number. - * Author: Andrey Kiselev, dron@ak4719.spb.edu. - * - ****************************************************************************** - * Copyright (c) 2006, Andrey Kiselev - * Copyright (c) 2008-2012, Even Rouault - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - ****************************************************************************/ - -#include -#include -#include -#include - -#include "projects.h" - -/* Windows nmake build doesn't have a proj_config.h, but HAVE_LOCALECONV */ -/* is defined in the compilation line */ -#ifndef HAVE_LOCALECONV -#include "proj_config.h" -#endif - -#define PJ_STRTOD_WORK_BUFFER_SIZE 64 - -/************************************************************************/ -/* pj_atof() */ -/************************************************************************/ - -/** - * Converts ASCII string to floating point number. - * - * This function converts the initial portion of the string pointed to - * by nptr to double floating point representation. The behaviour is the - * same as - * - * pj_strtod(nptr, (char **)NULL); - * - * This function does the same as standard atof(3), but does not take - * locale in account. That means, the decimal delimiter is always '.' - * (decimal point). - * - * @param nptr Pointer to string to convert. - * - * @return Converted value. - */ -double pj_atof( const char* nptr ) -{ - return pj_strtod(nptr, NULL); -} - - -/************************************************************************/ -/* replace_point_by_locale_point() */ -/************************************************************************/ - -static char* replace_point_by_locale_point(const char* pszNumber, char point, - char* pszWorkBuffer) -{ -#if !defined(HAVE_LOCALECONV) - -#if defined(_MSC_VER) /* Visual C++ */ -#pragma message("localeconv not available") -#else -#warning "localeconv not available" -#endif - - static char byPoint = 0; - if (byPoint == 0) - { - char szBuf[16]; - sprintf(szBuf, "%.1f", 1.0); - byPoint = szBuf[1]; - } - if (point != byPoint) - { - const char* pszPoint = strchr(pszNumber, point); - if (pszPoint) - { - char* pszNew; - if( strlen(pszNumber) < PJ_STRTOD_WORK_BUFFER_SIZE ) - { - strcpy(pszWorkBuffer, pszNumber); - pszNew = pszWorkBuffer; - } - else { - pszNew = pj_strdup(pszNumber); - if (!pszNew) - return NULL; - } - pszNew[pszPoint - pszNumber] = byPoint; - return pszNew; - } - } -#else - struct lconv *poLconv = localeconv(); - if ( poLconv - && poLconv->decimal_point - && poLconv->decimal_point[0] != '\0' ) - { - char byPoint = poLconv->decimal_point[0]; - - if (point != byPoint) - { - const char* pszLocalePoint = strchr(pszNumber, byPoint); - const char* pszPoint = strchr(pszNumber, point); - if (pszPoint || pszLocalePoint) - { - char* pszNew; - if( strlen(pszNumber) < PJ_STRTOD_WORK_BUFFER_SIZE ) - { - strcpy(pszWorkBuffer, pszNumber); - pszNew = pszWorkBuffer; - } - else { - pszNew = pj_strdup(pszNumber); - if (!pszNew) - return NULL; - } - if( pszLocalePoint ) - pszNew[pszLocalePoint - pszNumber] = ' '; - if( pszPoint ) - pszNew[pszPoint - pszNumber] = byPoint; - return pszNew; - } - } - } -#endif - return (char*) pszNumber; -} - -/************************************************************************/ -/* pj_strtod() */ -/************************************************************************/ - -/** - * Converts ASCII string to floating point number. - * - * This function converts the initial portion of the string pointed to - * by nptr to double floating point representation. This function does the - * same as standard strtod(3), but does not take locale in account and use - * decimal point. - * - * @param nptr Pointer to string to convert. - * @param endptr If is not NULL, a pointer to the character after the last - * character used in the conversion is stored in the location referenced - * by endptr. - * - * @return Converted value. - */ -double pj_strtod( const char *nptr, char **endptr ) -{ -/* -------------------------------------------------------------------- */ -/* We are implementing a simple method here: copy the input string */ -/* into the temporary buffer, replace the specified decimal delimiter */ -/* with the one, taken from locale settings and use standard strtod() */ -/* on that buffer. */ -/* -------------------------------------------------------------------- */ - double dfValue; - int nError; - char szWorkBuffer[PJ_STRTOD_WORK_BUFFER_SIZE]; - - char* pszNumber = replace_point_by_locale_point(nptr, '.', szWorkBuffer); - - dfValue = strtod( pszNumber, endptr ); - nError = errno; - - if ( endptr ) - *endptr = (char *)nptr + (*endptr - pszNumber); - - if (pszNumber != (char*) nptr && pszNumber != szWorkBuffer ) - free( pszNumber ); - - errno = nError; - return dfValue; -} diff --git a/src/pj_strtod.cpp b/src/pj_strtod.cpp new file mode 100644 index 00000000..f604a013 --- /dev/null +++ b/src/pj_strtod.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * + * Derived from GDAL port/cpl_strtod.cpp + * Purpose: Functions to convert ASCII string to floating point number. + * Author: Andrey Kiselev, dron@ak4719.spb.edu. + * + ****************************************************************************** + * Copyright (c) 2006, Andrey Kiselev + * Copyright (c) 2008-2012, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include +#include +#include +#include + +#include "projects.h" + +/* Windows nmake build doesn't have a proj_config.h, but HAVE_LOCALECONV */ +/* is defined in the compilation line */ +#ifndef HAVE_LOCALECONV +#include "proj_config.h" +#endif + +#define PJ_STRTOD_WORK_BUFFER_SIZE 64 + +/************************************************************************/ +/* pj_atof() */ +/************************************************************************/ + +/** + * Converts ASCII string to floating point number. + * + * This function converts the initial portion of the string pointed to + * by nptr to double floating point representation. The behaviour is the + * same as + * + * pj_strtod(nptr, (char **)NULL); + * + * This function does the same as standard atof(3), but does not take + * locale in account. That means, the decimal delimiter is always '.' + * (decimal point). + * + * @param nptr Pointer to string to convert. + * + * @return Converted value. + */ +double pj_atof( const char* nptr ) +{ + return pj_strtod(nptr, NULL); +} + + +/************************************************************************/ +/* replace_point_by_locale_point() */ +/************************************************************************/ + +static char* replace_point_by_locale_point(const char* pszNumber, char point, + char* pszWorkBuffer) +{ +#if !defined(HAVE_LOCALECONV) + +#if defined(_MSC_VER) /* Visual C++ */ +#pragma message("localeconv not available") +#else +#warning "localeconv not available" +#endif + + static char byPoint = 0; + if (byPoint == 0) + { + char szBuf[16]; + sprintf(szBuf, "%.1f", 1.0); + byPoint = szBuf[1]; + } + if (point != byPoint) + { + const char* pszPoint = strchr(pszNumber, point); + if (pszPoint) + { + char* pszNew; + if( strlen(pszNumber) < PJ_STRTOD_WORK_BUFFER_SIZE ) + { + strcpy(pszWorkBuffer, pszNumber); + pszNew = pszWorkBuffer; + } + else { + pszNew = pj_strdup(pszNumber); + if (!pszNew) + return NULL; + } + pszNew[pszPoint - pszNumber] = byPoint; + return pszNew; + } + } +#else + struct lconv *poLconv = localeconv(); + if ( poLconv + && poLconv->decimal_point + && poLconv->decimal_point[0] != '\0' ) + { + char byPoint = poLconv->decimal_point[0]; + + if (point != byPoint) + { + const char* pszLocalePoint = strchr(pszNumber, byPoint); + const char* pszPoint = strchr(pszNumber, point); + if (pszPoint || pszLocalePoint) + { + char* pszNew; + if( strlen(pszNumber) < PJ_STRTOD_WORK_BUFFER_SIZE ) + { + strcpy(pszWorkBuffer, pszNumber); + pszNew = pszWorkBuffer; + } + else { + pszNew = pj_strdup(pszNumber); + if (!pszNew) + return NULL; + } + if( pszLocalePoint ) + pszNew[pszLocalePoint - pszNumber] = ' '; + if( pszPoint ) + pszNew[pszPoint - pszNumber] = byPoint; + return pszNew; + } + } + } +#endif + return (char*) pszNumber; +} + +/************************************************************************/ +/* pj_strtod() */ +/************************************************************************/ + +/** + * Converts ASCII string to floating point number. + * + * This function converts the initial portion of the string pointed to + * by nptr to double floating point representation. This function does the + * same as standard strtod(3), but does not take locale in account and use + * decimal point. + * + * @param nptr Pointer to string to convert. + * @param endptr If is not NULL, a pointer to the character after the last + * character used in the conversion is stored in the location referenced + * by endptr. + * + * @return Converted value. + */ +double pj_strtod( const char *nptr, char **endptr ) +{ +/* -------------------------------------------------------------------- */ +/* We are implementing a simple method here: copy the input string */ +/* into the temporary buffer, replace the specified decimal delimiter */ +/* with the one, taken from locale settings and use standard strtod() */ +/* on that buffer. */ +/* -------------------------------------------------------------------- */ + double dfValue; + int nError; + char szWorkBuffer[PJ_STRTOD_WORK_BUFFER_SIZE]; + + char* pszNumber = replace_point_by_locale_point(nptr, '.', szWorkBuffer); + + dfValue = strtod( pszNumber, endptr ); + nError = errno; + + if ( endptr ) + *endptr = (char *)nptr + (*endptr - pszNumber); + + if (pszNumber != (char*) nptr && pszNumber != szWorkBuffer ) + free( pszNumber ); + + errno = nError; + return dfValue; +} diff --git a/src/pj_transform.c b/src/pj_transform.c deleted file mode 100644 index 6982676e..00000000 --- a/src/pj_transform.c +++ /dev/null @@ -1,1060 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Perform overall coordinate system to coordinate system - * transformations (pj_transform() function) including reprojection - * and datum shifting. - * 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. - *****************************************************************************/ - -#include -#include -#include - -#include "projects.h" -#include "geocent.h" - - -/* Apply transformation to observation - in forward or inverse direction */ -/* Copied from proj.h */ -enum PJ_DIRECTION { - PJ_FWD = 1, /* Forward */ - PJ_IDENT = 0, /* Do nothing */ - PJ_INV = -1 /* Inverse */ -}; -typedef enum PJ_DIRECTION PJ_DIRECTION; - -/* Copied from proj.h FIXME */ -int proj_errno_reset (const PJ *P); - - -static int adjust_axis( projCtx ctx, const char *axis, int denormalize_flag, - long point_count, int point_offset, - double *x, double *y, double *z ); - -#ifndef SRS_WGS84_SEMIMAJOR -#define SRS_WGS84_SEMIMAJOR 6378137.0 -#endif - -#ifndef SRS_WGS84_ESQUARED -#define SRS_WGS84_ESQUARED 0.0066943799901413165 -#endif - -#define Dx_BF (defn->datum_params[0]) -#define Dy_BF (defn->datum_params[1]) -#define Dz_BF (defn->datum_params[2]) -#define Rx_BF (defn->datum_params[3]) -#define Ry_BF (defn->datum_params[4]) -#define Rz_BF (defn->datum_params[5]) -#define M_BF (defn->datum_params[6]) - -/* -** This table is intended to indicate for any given error code -** whether that error will occur for all locations (ie. -** it is a problem with the coordinate system as a whole) in which case the -** value would be 0, or if the problem is with the point being transformed -** in which case the value is 1. -** -** At some point we might want to move this array in with the error message -** list or something, but while experimenting with it this should be fine. -** -** -** NOTE (2017-10-01): Non-transient errors really should have resulted in a -** PJ==0 during initialization, and hence should be handled at the level -** before calling pj_transform. The only obvious example of the contrary -** appears to be the PJD_ERR_GRID_AREA case, which may also be taken to -** mean "no grids available" -** -** -*/ - -static const int transient_error[60] = { - /* 0 1 2 3 4 5 6 7 8 9 */ - /* 0 to 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 10 to 19 */ 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, - /* 20 to 29 */ 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, - /* 30 to 39 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* 40 to 49 */ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - /* 50 to 59 */ 1, 0, 1, 0, 1, 1, 1, 1, 0, 0 }; - - -/* -------------------------------------------------------------------- */ -/* Read transient_error[] in a safe way. */ -/* -------------------------------------------------------------------- */ -static int get_transient_error_value(int pos_index) -{ - const int array_size = - (int)(sizeof(transient_error) / sizeof(transient_error[0])); - if( pos_index < 0 || pos_index >= array_size ) { - return 0; - } - return transient_error[pos_index]; -} - - -/* -------------------------------------------------------------------- */ -/* Transform unusual input coordinate axis orientation to */ -/* standard form if needed. */ -/* -------------------------------------------------------------------- */ -static int adjust_axes (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { - /* Nothing to do? */ - if (0==strcmp(P->axis,"enu")) - return 0; - - return adjust_axis( P->ctx, P->axis, - dir==PJ_FWD ? 1: 0, n, dist, x, y, z ); -} - - - -/* ----------------------------------------------------------------------- */ -/* Transform geographic (lat/long) source coordinates to */ -/* cartesian ("geocentric"), if needed */ -/* ----------------------------------------------------------------------- */ -static int geographic_to_cartesian (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { - int res; - long i; - double fac = P->to_meter; - - /* Nothing to do? */ - if (!P->is_geocent) - return 0; - - if ( z == NULL ) { - pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); - return PJD_ERR_GEOCENTRIC; - } - - if (PJ_FWD==dir) { - fac = P->fr_meter; - res = pj_geodetic_to_geocentric( P->a_orig, P->es_orig, n, dist, x, y, z ); - if (res) - return res; - } - - if (fac != 1.0) { - for( i = 0; i < n; i++ ) { - if( x[dist*i] != HUGE_VAL ) { - x[dist*i] *= fac; - y[dist*i] *= fac; - z[dist*i] *= fac; - } - } - } - - if (PJ_FWD==dir) - return 0; - return pj_geocentric_to_geodetic( - P->a_orig, P->es_orig, - n, dist, - x, y, z - ); -} - - - - - - - - - - -/* -------------------------------------------------------------------- */ -/* Transform destination points to projection coordinates, if */ -/* desired. */ -/* */ -/* Ought to fold this into projected_to_geographic */ -/* -------------------------------------------------------------------- */ -static int geographic_to_projected (PJ *P, long n, int dist, double *x, double *y, double *z) { - long i; - - /* Nothing to do? */ - if (P->is_latlong && !P->geoc && P->vto_meter == 1.0) - return 0; - if (P->is_geocent) - return 0; - - if(P->fwd3d != NULL && !(z == NULL && P->is_latlong)) - { - /* Three dimensions must be defined */ - if ( z == NULL) - { - pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); - return PJD_ERR_GEOCENTRIC; - } - - for( i = 0; i < n; i++ ) - { - XYZ projected_loc; - LPZ geodetic_loc; - - geodetic_loc.u = x[dist*i]; - geodetic_loc.v = y[dist*i]; - geodetic_loc.w = z[dist*i]; - - if (geodetic_loc.u == HUGE_VAL) - continue; - - proj_errno_reset( P ); - projected_loc = pj_fwd3d( geodetic_loc, P); - if( P->ctx->last_errno != 0 ) - { - if( (P->ctx->last_errno != EDOM - && P->ctx->last_errno != ERANGE) - && (P->ctx->last_errno > 0 - || P->ctx->last_errno < -44 || n == 1 - || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) - { - return P->ctx->last_errno; - } - else - { - projected_loc.u = HUGE_VAL; - projected_loc.v = HUGE_VAL; - projected_loc.w = HUGE_VAL; - } - } - - x[dist*i] = projected_loc.u; - y[dist*i] = projected_loc.v; - z[dist*i] = projected_loc.w; - } - return 0; - } - - for( i = 0; i ctx->last_errno != 0 ) - { - if( (P->ctx->last_errno != EDOM - && P->ctx->last_errno != ERANGE) - && (P->ctx->last_errno > 0 - || P->ctx->last_errno < -44 || n == 1 - || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) - { - return P->ctx->last_errno; - } - else - { - projected_loc.u = HUGE_VAL; - projected_loc.v = HUGE_VAL; - } - } - - x[dist*i] = projected_loc.u; - y[dist*i] = projected_loc.v; - } - return 0; -} - - - - - -/* ----------------------------------------------------------------------- */ -/* Transform projected source coordinates to lat/long, if needed */ -/* ----------------------------------------------------------------------- */ -static int projected_to_geographic (PJ *P, long n, int dist, double *x, double *y, double *z) { - long i; - - /* Nothing to do? */ - if (P->is_latlong && !P->geoc && P->vto_meter == 1.0) - return 0; - if (P->is_geocent) - return 0; - - /* Check first if projection is invertible. */ - if( (P->inv3d == NULL) && (P->inv == NULL)) - { - pj_ctx_set_errno(pj_get_ctx(P), PJD_ERR_NON_CONV_INV_MERI_DIST); - pj_log( pj_get_ctx(P), PJ_LOG_ERROR, - "pj_transform(): source projection not invertable" ); - return PJD_ERR_NON_CONV_INV_MERI_DIST; - } - - /* If invertible - First try inv3d if defined */ - if (P->inv3d != NULL && !(z == NULL && P->is_latlong)) - { - /* Three dimensions must be defined */ - if ( z == NULL) - { - pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); - return PJD_ERR_GEOCENTRIC; - } - - for (i=0; i < n; i++) - { - XYZ projected_loc; - XYZ geodetic_loc; - - projected_loc.u = x[dist*i]; - projected_loc.v = y[dist*i]; - projected_loc.w = z[dist*i]; - - if (projected_loc.u == HUGE_VAL) - continue; - - proj_errno_reset( P ); - geodetic_loc = pj_inv3d(projected_loc, P); - if( P->ctx->last_errno != 0 ) - { - if( (P->ctx->last_errno != EDOM - && P->ctx->last_errno != ERANGE) - && (P->ctx->last_errno > 0 - || P->ctx->last_errno < -44 || n == 1 - || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) - { - return P->ctx->last_errno; - } - else - { - geodetic_loc.u = HUGE_VAL; - geodetic_loc.v = HUGE_VAL; - geodetic_loc.w = HUGE_VAL; - } - } - - x[dist*i] = geodetic_loc.u; - y[dist*i] = geodetic_loc.v; - z[dist*i] = geodetic_loc.w; - - } - return 0; - } - - /* Fallback to the original PROJ.4 API 2d inversion - inv */ - for( i = 0; i < n; i++ ) { - XY projected_loc; - LP geodetic_loc; - - projected_loc.u = x[dist*i]; - projected_loc.v = y[dist*i]; - - if( projected_loc.u == HUGE_VAL ) - continue; - - proj_errno_reset( P ); - geodetic_loc = pj_inv( projected_loc, P ); - if( P->ctx->last_errno != 0 ) - { - if( (P->ctx->last_errno != EDOM - && P->ctx->last_errno != ERANGE) - && (P->ctx->last_errno > 0 - || P->ctx->last_errno < -44 || n == 1 - || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) - { - return P->ctx->last_errno; - } - else - { - geodetic_loc.u = HUGE_VAL; - geodetic_loc.v = HUGE_VAL; - } - } - - x[dist*i] = geodetic_loc.u; - y[dist*i] = geodetic_loc.v; - } - return 0; -} - - - -/* -------------------------------------------------------------------- */ -/* Adjust for the prime meridian if needed. */ -/* -------------------------------------------------------------------- */ -static int prime_meridian (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x) { - int i; - double pm = P->from_greenwich; - - /* Nothing to do? */ - if (pm==0.0) - return 0; - if (!(P->is_geocent || P->is_latlong)) - return 0; - - if (dir==PJ_FWD) - pm = -pm; - - for (i = 0; i < n; i++) - if (x[dist*i] != HUGE_VAL) - x[dist*i] += pm; - - return 0; -} - - - -/* -------------------------------------------------------------------- */ -/* Adjust for vertical scale factor if needed */ -/* -------------------------------------------------------------------- */ -static int height_unit (PJ *P, PJ_DIRECTION dir, long n, int dist, double *z) { - int i; - double fac = P->vto_meter; - - if (PJ_FWD==dir) - fac = P->vfr_meter; - - /* Nothing to do? */ - if (fac==1.0) - return 0; - if (0==z) - return 0; - if (P->is_latlong) - return 0; /* done in pj_inv3d() / pj_fwd3d() */ - - for (i = 0; i < n; i++) - if (z[dist*i] != HUGE_VAL ) - z[dist*i] *= fac; - - return 0; -} - - - -/* -------------------------------------------------------------------- */ -/* Transform to ellipsoidal heights if needed */ -/* -------------------------------------------------------------------- */ -static int geometric_to_orthometric (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { - int err; - if (0==P->has_geoid_vgrids) - return 0; - if (z==0) - return PJD_ERR_GEOCENTRIC; - err = pj_apply_vgridshift (P, "sgeoidgrids", - &(P->vgridlist_geoid), - &(P->vgridlist_geoid_count), - dir==PJ_FWD ? 1 : 0, n, dist, x, y, z ); - if (err) - return pj_ctx_get_errno(P->ctx); - return 0; -} - - - -/* -------------------------------------------------------------------- */ -/* Convert datums if needed, and possible. */ -/* -------------------------------------------------------------------- */ -static int datum_transform (PJ *P, PJ *Q, long n, int dist, double *x, double *y, double *z) { - if (0==pj_datum_transform (P, Q, n, dist, x, y, z)) - return 0; - if (P->ctx->last_errno) - return P->ctx->last_errno; - return Q->ctx->last_errno; -} - - - - - -/* -------------------------------------------------------------------- */ -/* If a wrapping center other than 0 is provided, rewrap around */ -/* the suggested center (for latlong coordinate systems only). */ -/* -------------------------------------------------------------------- */ -static int long_wrap (PJ *P, long n, int dist, double *x) { - long i; - - /* Nothing to do? */ - if (P->is_geocent) - return 0; - if (!P->is_long_wrap_set) - return 0; - if (!P->is_latlong) - return 0; - - for (i = 0; i < n; i++ ) { - double val = x[dist*i]; - if (val == HUGE_VAL) - continue; - - /* Get fast in ] -2 PI, 2 PI [ range */ - val = fmod(val, M_TWOPI); - while( val < P->long_wrap_center - M_PI ) - val += M_TWOPI; - while( val > P->long_wrap_center + M_PI ) - val -= M_TWOPI; - x[dist*i] = val; - } - return 0; -} - - - -/************************************************************************/ -/* pj_transform() */ -/* */ -/* Currently this function doesn't recognise if two projections */ -/* are identical (to short circuit reprojection) because it is */ -/* difficult to compare PJ structures (since there are some */ -/* projection specific components). */ -/************************************************************************/ - -int pj_transform( - PJ *src, PJ *dst, - long point_count, int point_offset, - double *x, double *y, double *z -){ - int err; - - src->ctx->last_errno = 0; - dst->ctx->last_errno = 0; - - if( point_offset == 0 ) - point_offset = 1; - - /* Bring input to "normal form": longitude, latitude, ellipsoidal height */ - - err = adjust_axes (src, PJ_INV, point_count, point_offset, x, y, z); - if (err) - return err; - err = geographic_to_cartesian (src, PJ_INV, point_count, point_offset, x, y, z); - if (err) - return err; - err = projected_to_geographic (src, point_count, point_offset, x, y, z); - if (err) - return err; - err = prime_meridian (src, PJ_INV, point_count, point_offset, x); - if (err) - return err; - err = height_unit (src, PJ_INV, point_count, point_offset, z); - if (err) - return err; - err = geometric_to_orthometric (src, PJ_INV, point_count, point_offset, x, y, z); - if (err) - return err; - - /* At the center of the process we do the datum shift (if needed) */ - - err = datum_transform(src, dst, point_count, point_offset, x, y, z ); - if (err) - return err; - - /* Now get out on the other side: Bring "normal form" to output form */ - - err = geometric_to_orthometric (dst, PJ_FWD, point_count, point_offset, x, y, z); - if (err) - return err; - err = height_unit (dst, PJ_FWD, point_count, point_offset, z); - if (err) - return err; - err = prime_meridian (dst, PJ_FWD, point_count, point_offset, x); - if (err) - return err; - err = geographic_to_cartesian (dst, PJ_FWD, point_count, point_offset, x, y, z); - if (err) - return err; - err = geographic_to_projected (dst, point_count, point_offset, x, y, z); - if (err) - return err; - err = long_wrap (dst, point_count, point_offset, x); - if (err) - return err; - err = adjust_axes (dst, PJ_FWD, point_count, point_offset, x, y, z); - if (err) - return err; - - return 0; -} - - - -/************************************************************************/ -/* pj_geodetic_to_geocentric() */ -/************************************************************************/ - -int pj_geodetic_to_geocentric( double a, double es, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - double b; - int i; - GeocentricInfo gi; - int ret_errno = 0; - - if( es == 0.0 ) - b = a; - else - b = a * sqrt(1-es); - - if( pj_Set_Geocentric_Parameters( &gi, a, b ) != 0 ) - { - return PJD_ERR_GEOCENTRIC; - } - - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - - if( x[io] == HUGE_VAL ) - continue; - - if( pj_Convert_Geodetic_To_Geocentric( &gi, y[io], x[io], z[io], - x+io, y+io, z+io ) != 0 ) - { - ret_errno = PJD_ERR_LAT_OR_LON_EXCEED_LIMIT; - x[io] = y[io] = HUGE_VAL; - /* but keep processing points! */ - } - } - - return ret_errno; -} - -/************************************************************************/ -/* pj_geocentric_to_geodetic() */ -/************************************************************************/ - -int pj_geocentric_to_geodetic( double a, double es, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - double b; - int i; - GeocentricInfo gi; - - if( es == 0.0 ) - b = a; - else - b = a * sqrt(1-es); - - if( pj_Set_Geocentric_Parameters( &gi, a, b ) != 0 ) - { - return PJD_ERR_GEOCENTRIC; - } - - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - - if( x[io] == HUGE_VAL ) - continue; - - pj_Convert_Geocentric_To_Geodetic( &gi, x[io], y[io], z[io], - y+io, x+io, z+io ); - } - - return 0; -} - -/************************************************************************/ -/* pj_compare_datums() */ -/* */ -/* Returns TRUE if the two datums are identical, otherwise */ -/* FALSE. */ -/************************************************************************/ - -int pj_compare_datums( PJ *srcdefn, PJ *dstdefn ) - -{ - if( srcdefn->datum_type != dstdefn->datum_type ) - { - return 0; - } - else if( srcdefn->a_orig != dstdefn->a_orig - || ABS(srcdefn->es_orig - dstdefn->es_orig) > 0.000000000050 ) - { - /* the tolerance for es is to ensure that GRS80 and WGS84 are - considered identical */ - return 0; - } - else if( srcdefn->datum_type == PJD_3PARAM ) - { - return (srcdefn->datum_params[0] == dstdefn->datum_params[0] - && srcdefn->datum_params[1] == dstdefn->datum_params[1] - && srcdefn->datum_params[2] == dstdefn->datum_params[2]); - } - else if( srcdefn->datum_type == PJD_7PARAM ) - { - return (srcdefn->datum_params[0] == dstdefn->datum_params[0] - && srcdefn->datum_params[1] == dstdefn->datum_params[1] - && srcdefn->datum_params[2] == dstdefn->datum_params[2] - && srcdefn->datum_params[3] == dstdefn->datum_params[3] - && srcdefn->datum_params[4] == dstdefn->datum_params[4] - && srcdefn->datum_params[5] == dstdefn->datum_params[5] - && srcdefn->datum_params[6] == dstdefn->datum_params[6]); - } - else if( srcdefn->datum_type == PJD_GRIDSHIFT ) - { - const char* srcnadgrids = - pj_param(srcdefn->ctx, srcdefn->params,"snadgrids").s; - const char* dstnadgrids = - pj_param(dstdefn->ctx, dstdefn->params,"snadgrids").s; - return srcnadgrids != 0 && dstnadgrids != 0 && - strcmp( srcnadgrids, dstnadgrids ) == 0; - } - else - return 1; -} - -/************************************************************************/ -/* pj_geocentic_to_wgs84() */ -/************************************************************************/ - -static -int pj_geocentric_to_wgs84( PJ *defn, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - int i; - - if( defn->datum_type == PJD_3PARAM ) - { - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - - if( x[io] == HUGE_VAL ) - continue; - - x[io] = x[io] + Dx_BF; - y[io] = y[io] + Dy_BF; - z[io] = z[io] + Dz_BF; - } - } - else if( defn->datum_type == PJD_7PARAM ) - { - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - double x_out, y_out, z_out; - - if( x[io] == HUGE_VAL ) - continue; - - x_out = M_BF*( x[io] - Rz_BF*y[io] + Ry_BF*z[io]) + Dx_BF; - y_out = M_BF*( Rz_BF*x[io] + y[io] - Rx_BF*z[io]) + Dy_BF; - z_out = M_BF*(-Ry_BF*x[io] + Rx_BF*y[io] + z[io]) + Dz_BF; - - x[io] = x_out; - y[io] = y_out; - z[io] = z_out; - } - } - - return 0; -} - -/************************************************************************/ -/* pj_geocentic_from_wgs84() */ -/************************************************************************/ - -static -int pj_geocentric_from_wgs84( PJ *defn, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - int i; - - if( defn->datum_type == PJD_3PARAM ) - { - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - - if( x[io] == HUGE_VAL ) - continue; - - x[io] = x[io] - Dx_BF; - y[io] = y[io] - Dy_BF; - z[io] = z[io] - Dz_BF; - } - } - else if( defn->datum_type == PJD_7PARAM ) - { - for( i = 0; i < point_count; i++ ) - { - long io = i * point_offset; - double x_tmp, y_tmp, z_tmp; - - if( x[io] == HUGE_VAL ) - continue; - - x_tmp = (x[io] - Dx_BF) / M_BF; - y_tmp = (y[io] - Dy_BF) / M_BF; - z_tmp = (z[io] - Dz_BF) / M_BF; - - x[io] = x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp; - y[io] = -Rz_BF*x_tmp + y_tmp + Rx_BF*z_tmp; - z[io] = Ry_BF*x_tmp - Rx_BF*y_tmp + z_tmp; - } - } - - return 0; -} - -/************************************************************************/ -/* pj_datum_transform() */ -/* */ -/* The input should be long/lat/z coordinates in radians in the */ -/* source datum, and the output should be long/lat/z */ -/* coordinates in radians in the destination datum. */ -/************************************************************************/ - -int pj_datum_transform( PJ *src, PJ *dst, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - double src_a, src_es, dst_a, dst_es; - int z_is_temp = FALSE; - -/* -------------------------------------------------------------------- */ -/* We cannot do any meaningful datum transformation if either */ -/* the source or destination are of an unknown datum type */ -/* (ie. only a +ellps declaration, no +datum). This is new */ -/* behavior for PROJ 4.6.0. */ -/* -------------------------------------------------------------------- */ - if( src->datum_type == PJD_UNKNOWN - || dst->datum_type == PJD_UNKNOWN ) - return 0; - -/* -------------------------------------------------------------------- */ -/* Short cut if the datums are identical. */ -/* -------------------------------------------------------------------- */ - if( pj_compare_datums( src, dst ) ) - return 0; - - src_a = src->a_orig; - src_es = src->es_orig; - - dst_a = dst->a_orig; - dst_es = dst->es_orig; - -/* -------------------------------------------------------------------- */ -/* Create a temporary Z array if one is not provided. */ -/* -------------------------------------------------------------------- */ - if( z == NULL ) - { - size_t bytes = sizeof(double) * point_count * point_offset; - z = (double *) pj_malloc(bytes); - memset( z, 0, bytes ); - z_is_temp = TRUE; - } - -#define CHECK_RETURN(defn) {if( defn->ctx->last_errno != 0 && (defn->ctx->last_errno > 0 || get_transient_error_value(-defn->ctx->last_errno) == 0) ) { if( z_is_temp ) pj_dalloc(z); return defn->ctx->last_errno; }} - -/* -------------------------------------------------------------------- */ -/* If this datum requires grid shifts, then apply it to geodetic */ -/* coordinates. */ -/* -------------------------------------------------------------------- */ - if( src->datum_type == PJD_GRIDSHIFT ) - { - pj_apply_gridshift_2( src, 0, point_count, point_offset, x, y, z ); - CHECK_RETURN(src); - - src_a = SRS_WGS84_SEMIMAJOR; - src_es = SRS_WGS84_ESQUARED; - } - - if( dst->datum_type == PJD_GRIDSHIFT ) - { - dst_a = SRS_WGS84_SEMIMAJOR; - dst_es = SRS_WGS84_ESQUARED; - } - -/* ==================================================================== */ -/* Do we need to go through geocentric coordinates? */ -/* ==================================================================== */ - if( src_es != dst_es || src_a != dst_a - || src->datum_type == PJD_3PARAM - || src->datum_type == PJD_7PARAM - || dst->datum_type == PJD_3PARAM - || dst->datum_type == PJD_7PARAM) - { -/* -------------------------------------------------------------------- */ -/* Convert to geocentric coordinates. */ -/* -------------------------------------------------------------------- */ - src->ctx->last_errno = - pj_geodetic_to_geocentric( src_a, src_es, - point_count, point_offset, x, y, z ); - CHECK_RETURN(src); - -/* -------------------------------------------------------------------- */ -/* Convert between datums. */ -/* -------------------------------------------------------------------- */ - if( src->datum_type == PJD_3PARAM - || src->datum_type == PJD_7PARAM ) - { - pj_geocentric_to_wgs84( src, point_count, point_offset,x,y,z); - CHECK_RETURN(src); - } - - if( dst->datum_type == PJD_3PARAM - || dst->datum_type == PJD_7PARAM ) - { - pj_geocentric_from_wgs84( dst, point_count,point_offset,x,y,z); - CHECK_RETURN(dst); - } - -/* -------------------------------------------------------------------- */ -/* Convert back to geodetic coordinates. */ -/* -------------------------------------------------------------------- */ - dst->ctx->last_errno = - pj_geocentric_to_geodetic( dst_a, dst_es, - point_count, point_offset, x, y, z ); - CHECK_RETURN(dst); - } - -/* -------------------------------------------------------------------- */ -/* Apply grid shift to destination if required. */ -/* -------------------------------------------------------------------- */ - if( dst->datum_type == PJD_GRIDSHIFT ) - { - pj_apply_gridshift_2( dst, 1, point_count, point_offset, x, y, z ); - CHECK_RETURN(dst); - } - - if( z_is_temp ) - pj_dalloc( z ); - - return 0; -} - -/************************************************************************/ -/* adjust_axis() */ -/* */ -/* Normalize or de-normalized the x/y/z axes. The normal form */ -/* is "enu" (easting, northing, up). */ -/************************************************************************/ -static int adjust_axis( projCtx ctx, - const char *axis, int denormalize_flag, - long point_count, int point_offset, - double *x, double *y, double *z ) - -{ - double x_in, y_in, z_in = 0.0; - int i, i_axis; - - if( !denormalize_flag ) - { - for( i = 0; i < point_count; i++ ) - { - x_in = x[point_offset*i]; - y_in = y[point_offset*i]; - if( z ) - z_in = z[point_offset*i]; - - for( i_axis = 0; i_axis < 3; i_axis++ ) - { - double value; - - if( i_axis == 0 ) - value = x_in; - else if( i_axis == 1 ) - value = y_in; - else - value = z_in; - - switch( axis[i_axis] ) - { - case 'e': - x[point_offset*i] = value; - break; - case 'w': - x[point_offset*i] = -value; - break; - case 'n': - y[point_offset*i] = value; - break; - case 's': - y[point_offset*i] = -value; - break; - case 'u': - if( z ) - z[point_offset*i] = value; - break; - case 'd': - if( z ) - z[point_offset*i] = -value; - break; - default: - pj_ctx_set_errno( ctx, PJD_ERR_AXIS ); - return PJD_ERR_AXIS; - } - } /* i_axis */ - } /* i (point) */ - } - - else /* denormalize */ - { - for( i = 0; i < point_count; i++ ) - { - x_in = x[point_offset*i]; - y_in = y[point_offset*i]; - if( z ) - z_in = z[point_offset*i]; - - for( i_axis = 0; i_axis < 3; i_axis++ ) - { - double *target; - - if( i_axis == 2 && z == NULL ) - continue; - - if( i_axis == 0 ) - target = x; - else if( i_axis == 1 ) - target = y; - else - target = z; - - switch( axis[i_axis] ) - { - case 'e': - target[point_offset*i] = x_in; break; - case 'w': - target[point_offset*i] = -x_in; break; - case 'n': - target[point_offset*i] = y_in; break; - case 's': - target[point_offset*i] = -y_in; break; - case 'u': - target[point_offset*i] = z_in; break; - case 'd': - target[point_offset*i] = -z_in; break; - default: - pj_ctx_set_errno( ctx, PJD_ERR_AXIS ); - return PJD_ERR_AXIS; - } - } /* i_axis */ - } /* i (point) */ - } - - return 0; -} diff --git a/src/pj_transform.cpp b/src/pj_transform.cpp new file mode 100644 index 00000000..53429967 --- /dev/null +++ b/src/pj_transform.cpp @@ -0,0 +1,1060 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Perform overall coordinate system to coordinate system + * transformations (pj_transform() function) including reprojection + * and datum shifting. + * 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. + *****************************************************************************/ + +#include +#include +#include + +#include "projects.h" +#include "geocent.h" + + +/* Apply transformation to observation - in forward or inverse direction */ +/* Copied from proj.h */ +enum PJ_DIRECTION { + PJ_FWD = 1, /* Forward */ + PJ_IDENT = 0, /* Do nothing */ + PJ_INV = -1 /* Inverse */ +}; +typedef enum PJ_DIRECTION PJ_DIRECTION; + +/* Copied from proj.h FIXME */ +extern "C" int proj_errno_reset (const PJ *P); + + +static int adjust_axis( projCtx ctx, const char *axis, int denormalize_flag, + long point_count, int point_offset, + double *x, double *y, double *z ); + +#ifndef SRS_WGS84_SEMIMAJOR +#define SRS_WGS84_SEMIMAJOR 6378137.0 +#endif + +#ifndef SRS_WGS84_ESQUARED +#define SRS_WGS84_ESQUARED 0.0066943799901413165 +#endif + +#define Dx_BF (defn->datum_params[0]) +#define Dy_BF (defn->datum_params[1]) +#define Dz_BF (defn->datum_params[2]) +#define Rx_BF (defn->datum_params[3]) +#define Ry_BF (defn->datum_params[4]) +#define Rz_BF (defn->datum_params[5]) +#define M_BF (defn->datum_params[6]) + +/* +** This table is intended to indicate for any given error code +** whether that error will occur for all locations (ie. +** it is a problem with the coordinate system as a whole) in which case the +** value would be 0, or if the problem is with the point being transformed +** in which case the value is 1. +** +** At some point we might want to move this array in with the error message +** list or something, but while experimenting with it this should be fine. +** +** +** NOTE (2017-10-01): Non-transient errors really should have resulted in a +** PJ==0 during initialization, and hence should be handled at the level +** before calling pj_transform. The only obvious example of the contrary +** appears to be the PJD_ERR_GRID_AREA case, which may also be taken to +** mean "no grids available" +** +** +*/ + +static const int transient_error[60] = { + /* 0 1 2 3 4 5 6 7 8 9 */ + /* 0 to 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 10 to 19 */ 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, + /* 20 to 29 */ 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, + /* 30 to 39 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 40 to 49 */ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + /* 50 to 59 */ 1, 0, 1, 0, 1, 1, 1, 1, 0, 0 }; + + +/* -------------------------------------------------------------------- */ +/* Read transient_error[] in a safe way. */ +/* -------------------------------------------------------------------- */ +static int get_transient_error_value(int pos_index) +{ + const int array_size = + (int)(sizeof(transient_error) / sizeof(transient_error[0])); + if( pos_index < 0 || pos_index >= array_size ) { + return 0; + } + return transient_error[pos_index]; +} + + +/* -------------------------------------------------------------------- */ +/* Transform unusual input coordinate axis orientation to */ +/* standard form if needed. */ +/* -------------------------------------------------------------------- */ +static int adjust_axes (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { + /* Nothing to do? */ + if (0==strcmp(P->axis,"enu")) + return 0; + + return adjust_axis( P->ctx, P->axis, + dir==PJ_FWD ? 1: 0, n, dist, x, y, z ); +} + + + +/* ----------------------------------------------------------------------- */ +/* Transform geographic (lat/long) source coordinates to */ +/* cartesian ("geocentric"), if needed */ +/* ----------------------------------------------------------------------- */ +static int geographic_to_cartesian (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { + int res; + long i; + double fac = P->to_meter; + + /* Nothing to do? */ + if (!P->is_geocent) + return 0; + + if ( z == NULL ) { + pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); + return PJD_ERR_GEOCENTRIC; + } + + if (PJ_FWD==dir) { + fac = P->fr_meter; + res = pj_geodetic_to_geocentric( P->a_orig, P->es_orig, n, dist, x, y, z ); + if (res) + return res; + } + + if (fac != 1.0) { + for( i = 0; i < n; i++ ) { + if( x[dist*i] != HUGE_VAL ) { + x[dist*i] *= fac; + y[dist*i] *= fac; + z[dist*i] *= fac; + } + } + } + + if (PJ_FWD==dir) + return 0; + return pj_geocentric_to_geodetic( + P->a_orig, P->es_orig, + n, dist, + x, y, z + ); +} + + + + + + + + + + +/* -------------------------------------------------------------------- */ +/* Transform destination points to projection coordinates, if */ +/* desired. */ +/* */ +/* Ought to fold this into projected_to_geographic */ +/* -------------------------------------------------------------------- */ +static int geographic_to_projected (PJ *P, long n, int dist, double *x, double *y, double *z) { + long i; + + /* Nothing to do? */ + if (P->is_latlong && !P->geoc && P->vto_meter == 1.0) + return 0; + if (P->is_geocent) + return 0; + + if(P->fwd3d != NULL && !(z == NULL && P->is_latlong)) + { + /* Three dimensions must be defined */ + if ( z == NULL) + { + pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); + return PJD_ERR_GEOCENTRIC; + } + + for( i = 0; i < n; i++ ) + { + XYZ projected_loc; + LPZ geodetic_loc; + + geodetic_loc.u = x[dist*i]; + geodetic_loc.v = y[dist*i]; + geodetic_loc.w = z[dist*i]; + + if (geodetic_loc.u == HUGE_VAL) + continue; + + proj_errno_reset( P ); + projected_loc = pj_fwd3d( geodetic_loc, P); + if( P->ctx->last_errno != 0 ) + { + if( (P->ctx->last_errno != EDOM + && P->ctx->last_errno != ERANGE) + && (P->ctx->last_errno > 0 + || P->ctx->last_errno < -44 || n == 1 + || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) + { + return P->ctx->last_errno; + } + else + { + projected_loc.u = HUGE_VAL; + projected_loc.v = HUGE_VAL; + projected_loc.w = HUGE_VAL; + } + } + + x[dist*i] = projected_loc.u; + y[dist*i] = projected_loc.v; + z[dist*i] = projected_loc.w; + } + return 0; + } + + for( i = 0; i ctx->last_errno != 0 ) + { + if( (P->ctx->last_errno != EDOM + && P->ctx->last_errno != ERANGE) + && (P->ctx->last_errno > 0 + || P->ctx->last_errno < -44 || n == 1 + || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) + { + return P->ctx->last_errno; + } + else + { + projected_loc.u = HUGE_VAL; + projected_loc.v = HUGE_VAL; + } + } + + x[dist*i] = projected_loc.u; + y[dist*i] = projected_loc.v; + } + return 0; +} + + + + + +/* ----------------------------------------------------------------------- */ +/* Transform projected source coordinates to lat/long, if needed */ +/* ----------------------------------------------------------------------- */ +static int projected_to_geographic (PJ *P, long n, int dist, double *x, double *y, double *z) { + long i; + + /* Nothing to do? */ + if (P->is_latlong && !P->geoc && P->vto_meter == 1.0) + return 0; + if (P->is_geocent) + return 0; + + /* Check first if projection is invertible. */ + if( (P->inv3d == NULL) && (P->inv == NULL)) + { + pj_ctx_set_errno(pj_get_ctx(P), PJD_ERR_NON_CONV_INV_MERI_DIST); + pj_log( pj_get_ctx(P), PJ_LOG_ERROR, + "pj_transform(): source projection not invertable" ); + return PJD_ERR_NON_CONV_INV_MERI_DIST; + } + + /* If invertible - First try inv3d if defined */ + if (P->inv3d != NULL && !(z == NULL && P->is_latlong)) + { + /* Three dimensions must be defined */ + if ( z == NULL) + { + pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); + return PJD_ERR_GEOCENTRIC; + } + + for (i=0; i < n; i++) + { + XYZ projected_loc; + XYZ geodetic_loc; + + projected_loc.u = x[dist*i]; + projected_loc.v = y[dist*i]; + projected_loc.w = z[dist*i]; + + if (projected_loc.u == HUGE_VAL) + continue; + + proj_errno_reset( P ); + geodetic_loc = pj_inv3d(projected_loc, P); + if( P->ctx->last_errno != 0 ) + { + if( (P->ctx->last_errno != EDOM + && P->ctx->last_errno != ERANGE) + && (P->ctx->last_errno > 0 + || P->ctx->last_errno < -44 || n == 1 + || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) + { + return P->ctx->last_errno; + } + else + { + geodetic_loc.u = HUGE_VAL; + geodetic_loc.v = HUGE_VAL; + geodetic_loc.w = HUGE_VAL; + } + } + + x[dist*i] = geodetic_loc.u; + y[dist*i] = geodetic_loc.v; + z[dist*i] = geodetic_loc.w; + + } + return 0; + } + + /* Fallback to the original PROJ.4 API 2d inversion - inv */ + for( i = 0; i < n; i++ ) { + XY projected_loc; + LP geodetic_loc; + + projected_loc.u = x[dist*i]; + projected_loc.v = y[dist*i]; + + if( projected_loc.u == HUGE_VAL ) + continue; + + proj_errno_reset( P ); + geodetic_loc = pj_inv( projected_loc, P ); + if( P->ctx->last_errno != 0 ) + { + if( (P->ctx->last_errno != EDOM + && P->ctx->last_errno != ERANGE) + && (P->ctx->last_errno > 0 + || P->ctx->last_errno < -44 || n == 1 + || get_transient_error_value(-P->ctx->last_errno) == 0 ) ) + { + return P->ctx->last_errno; + } + else + { + geodetic_loc.u = HUGE_VAL; + geodetic_loc.v = HUGE_VAL; + } + } + + x[dist*i] = geodetic_loc.u; + y[dist*i] = geodetic_loc.v; + } + return 0; +} + + + +/* -------------------------------------------------------------------- */ +/* Adjust for the prime meridian if needed. */ +/* -------------------------------------------------------------------- */ +static int prime_meridian (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x) { + int i; + double pm = P->from_greenwich; + + /* Nothing to do? */ + if (pm==0.0) + return 0; + if (!(P->is_geocent || P->is_latlong)) + return 0; + + if (dir==PJ_FWD) + pm = -pm; + + for (i = 0; i < n; i++) + if (x[dist*i] != HUGE_VAL) + x[dist*i] += pm; + + return 0; +} + + + +/* -------------------------------------------------------------------- */ +/* Adjust for vertical scale factor if needed */ +/* -------------------------------------------------------------------- */ +static int height_unit (PJ *P, PJ_DIRECTION dir, long n, int dist, double *z) { + int i; + double fac = P->vto_meter; + + if (PJ_FWD==dir) + fac = P->vfr_meter; + + /* Nothing to do? */ + if (fac==1.0) + return 0; + if (0==z) + return 0; + if (P->is_latlong) + return 0; /* done in pj_inv3d() / pj_fwd3d() */ + + for (i = 0; i < n; i++) + if (z[dist*i] != HUGE_VAL ) + z[dist*i] *= fac; + + return 0; +} + + + +/* -------------------------------------------------------------------- */ +/* Transform to ellipsoidal heights if needed */ +/* -------------------------------------------------------------------- */ +static int geometric_to_orthometric (PJ *P, PJ_DIRECTION dir, long n, int dist, double *x, double *y, double *z) { + int err; + if (0==P->has_geoid_vgrids) + return 0; + if (z==0) + return PJD_ERR_GEOCENTRIC; + err = pj_apply_vgridshift (P, "sgeoidgrids", + &(P->vgridlist_geoid), + &(P->vgridlist_geoid_count), + dir==PJ_FWD ? 1 : 0, n, dist, x, y, z ); + if (err) + return pj_ctx_get_errno(P->ctx); + return 0; +} + + + +/* -------------------------------------------------------------------- */ +/* Convert datums if needed, and possible. */ +/* -------------------------------------------------------------------- */ +static int datum_transform (PJ *P, PJ *Q, long n, int dist, double *x, double *y, double *z) { + if (0==pj_datum_transform (P, Q, n, dist, x, y, z)) + return 0; + if (P->ctx->last_errno) + return P->ctx->last_errno; + return Q->ctx->last_errno; +} + + + + + +/* -------------------------------------------------------------------- */ +/* If a wrapping center other than 0 is provided, rewrap around */ +/* the suggested center (for latlong coordinate systems only). */ +/* -------------------------------------------------------------------- */ +static int long_wrap (PJ *P, long n, int dist, double *x) { + long i; + + /* Nothing to do? */ + if (P->is_geocent) + return 0; + if (!P->is_long_wrap_set) + return 0; + if (!P->is_latlong) + return 0; + + for (i = 0; i < n; i++ ) { + double val = x[dist*i]; + if (val == HUGE_VAL) + continue; + + /* Get fast in ] -2 PI, 2 PI [ range */ + val = fmod(val, M_TWOPI); + while( val < P->long_wrap_center - M_PI ) + val += M_TWOPI; + while( val > P->long_wrap_center + M_PI ) + val -= M_TWOPI; + x[dist*i] = val; + } + return 0; +} + + + +/************************************************************************/ +/* pj_transform() */ +/* */ +/* Currently this function doesn't recognise if two projections */ +/* are identical (to short circuit reprojection) because it is */ +/* difficult to compare PJ structures (since there are some */ +/* projection specific components). */ +/************************************************************************/ + +int pj_transform( + PJ *src, PJ *dst, + long point_count, int point_offset, + double *x, double *y, double *z +){ + int err; + + src->ctx->last_errno = 0; + dst->ctx->last_errno = 0; + + if( point_offset == 0 ) + point_offset = 1; + + /* Bring input to "normal form": longitude, latitude, ellipsoidal height */ + + err = adjust_axes (src, PJ_INV, point_count, point_offset, x, y, z); + if (err) + return err; + err = geographic_to_cartesian (src, PJ_INV, point_count, point_offset, x, y, z); + if (err) + return err; + err = projected_to_geographic (src, point_count, point_offset, x, y, z); + if (err) + return err; + err = prime_meridian (src, PJ_INV, point_count, point_offset, x); + if (err) + return err; + err = height_unit (src, PJ_INV, point_count, point_offset, z); + if (err) + return err; + err = geometric_to_orthometric (src, PJ_INV, point_count, point_offset, x, y, z); + if (err) + return err; + + /* At the center of the process we do the datum shift (if needed) */ + + err = datum_transform(src, dst, point_count, point_offset, x, y, z ); + if (err) + return err; + + /* Now get out on the other side: Bring "normal form" to output form */ + + err = geometric_to_orthometric (dst, PJ_FWD, point_count, point_offset, x, y, z); + if (err) + return err; + err = height_unit (dst, PJ_FWD, point_count, point_offset, z); + if (err) + return err; + err = prime_meridian (dst, PJ_FWD, point_count, point_offset, x); + if (err) + return err; + err = geographic_to_cartesian (dst, PJ_FWD, point_count, point_offset, x, y, z); + if (err) + return err; + err = geographic_to_projected (dst, point_count, point_offset, x, y, z); + if (err) + return err; + err = long_wrap (dst, point_count, point_offset, x); + if (err) + return err; + err = adjust_axes (dst, PJ_FWD, point_count, point_offset, x, y, z); + if (err) + return err; + + return 0; +} + + + +/************************************************************************/ +/* pj_geodetic_to_geocentric() */ +/************************************************************************/ + +int pj_geodetic_to_geocentric( double a, double es, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + double b; + int i; + GeocentricInfo gi; + int ret_errno = 0; + + if( es == 0.0 ) + b = a; + else + b = a * sqrt(1-es); + + if( pj_Set_Geocentric_Parameters( &gi, a, b ) != 0 ) + { + return PJD_ERR_GEOCENTRIC; + } + + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + + if( x[io] == HUGE_VAL ) + continue; + + if( pj_Convert_Geodetic_To_Geocentric( &gi, y[io], x[io], z[io], + x+io, y+io, z+io ) != 0 ) + { + ret_errno = PJD_ERR_LAT_OR_LON_EXCEED_LIMIT; + x[io] = y[io] = HUGE_VAL; + /* but keep processing points! */ + } + } + + return ret_errno; +} + +/************************************************************************/ +/* pj_geocentric_to_geodetic() */ +/************************************************************************/ + +int pj_geocentric_to_geodetic( double a, double es, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + double b; + int i; + GeocentricInfo gi; + + if( es == 0.0 ) + b = a; + else + b = a * sqrt(1-es); + + if( pj_Set_Geocentric_Parameters( &gi, a, b ) != 0 ) + { + return PJD_ERR_GEOCENTRIC; + } + + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + + if( x[io] == HUGE_VAL ) + continue; + + pj_Convert_Geocentric_To_Geodetic( &gi, x[io], y[io], z[io], + y+io, x+io, z+io ); + } + + return 0; +} + +/************************************************************************/ +/* pj_compare_datums() */ +/* */ +/* Returns TRUE if the two datums are identical, otherwise */ +/* FALSE. */ +/************************************************************************/ + +int pj_compare_datums( PJ *srcdefn, PJ *dstdefn ) + +{ + if( srcdefn->datum_type != dstdefn->datum_type ) + { + return 0; + } + else if( srcdefn->a_orig != dstdefn->a_orig + || ABS(srcdefn->es_orig - dstdefn->es_orig) > 0.000000000050 ) + { + /* the tolerance for es is to ensure that GRS80 and WGS84 are + considered identical */ + return 0; + } + else if( srcdefn->datum_type == PJD_3PARAM ) + { + return (srcdefn->datum_params[0] == dstdefn->datum_params[0] + && srcdefn->datum_params[1] == dstdefn->datum_params[1] + && srcdefn->datum_params[2] == dstdefn->datum_params[2]); + } + else if( srcdefn->datum_type == PJD_7PARAM ) + { + return (srcdefn->datum_params[0] == dstdefn->datum_params[0] + && srcdefn->datum_params[1] == dstdefn->datum_params[1] + && srcdefn->datum_params[2] == dstdefn->datum_params[2] + && srcdefn->datum_params[3] == dstdefn->datum_params[3] + && srcdefn->datum_params[4] == dstdefn->datum_params[4] + && srcdefn->datum_params[5] == dstdefn->datum_params[5] + && srcdefn->datum_params[6] == dstdefn->datum_params[6]); + } + else if( srcdefn->datum_type == PJD_GRIDSHIFT ) + { + const char* srcnadgrids = + pj_param(srcdefn->ctx, srcdefn->params,"snadgrids").s; + const char* dstnadgrids = + pj_param(dstdefn->ctx, dstdefn->params,"snadgrids").s; + return srcnadgrids != 0 && dstnadgrids != 0 && + strcmp( srcnadgrids, dstnadgrids ) == 0; + } + else + return 1; +} + +/************************************************************************/ +/* pj_geocentic_to_wgs84() */ +/************************************************************************/ + +static +int pj_geocentric_to_wgs84( PJ *defn, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + int i; + + if( defn->datum_type == PJD_3PARAM ) + { + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + + if( x[io] == HUGE_VAL ) + continue; + + x[io] = x[io] + Dx_BF; + y[io] = y[io] + Dy_BF; + z[io] = z[io] + Dz_BF; + } + } + else if( defn->datum_type == PJD_7PARAM ) + { + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + double x_out, y_out, z_out; + + if( x[io] == HUGE_VAL ) + continue; + + x_out = M_BF*( x[io] - Rz_BF*y[io] + Ry_BF*z[io]) + Dx_BF; + y_out = M_BF*( Rz_BF*x[io] + y[io] - Rx_BF*z[io]) + Dy_BF; + z_out = M_BF*(-Ry_BF*x[io] + Rx_BF*y[io] + z[io]) + Dz_BF; + + x[io] = x_out; + y[io] = y_out; + z[io] = z_out; + } + } + + return 0; +} + +/************************************************************************/ +/* pj_geocentic_from_wgs84() */ +/************************************************************************/ + +static +int pj_geocentric_from_wgs84( PJ *defn, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + int i; + + if( defn->datum_type == PJD_3PARAM ) + { + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + + if( x[io] == HUGE_VAL ) + continue; + + x[io] = x[io] - Dx_BF; + y[io] = y[io] - Dy_BF; + z[io] = z[io] - Dz_BF; + } + } + else if( defn->datum_type == PJD_7PARAM ) + { + for( i = 0; i < point_count; i++ ) + { + long io = i * point_offset; + double x_tmp, y_tmp, z_tmp; + + if( x[io] == HUGE_VAL ) + continue; + + x_tmp = (x[io] - Dx_BF) / M_BF; + y_tmp = (y[io] - Dy_BF) / M_BF; + z_tmp = (z[io] - Dz_BF) / M_BF; + + x[io] = x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp; + y[io] = -Rz_BF*x_tmp + y_tmp + Rx_BF*z_tmp; + z[io] = Ry_BF*x_tmp - Rx_BF*y_tmp + z_tmp; + } + } + + return 0; +} + +/************************************************************************/ +/* pj_datum_transform() */ +/* */ +/* The input should be long/lat/z coordinates in radians in the */ +/* source datum, and the output should be long/lat/z */ +/* coordinates in radians in the destination datum. */ +/************************************************************************/ + +int pj_datum_transform( PJ *src, PJ *dst, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + double src_a, src_es, dst_a, dst_es; + int z_is_temp = FALSE; + +/* -------------------------------------------------------------------- */ +/* We cannot do any meaningful datum transformation if either */ +/* the source or destination are of an unknown datum type */ +/* (ie. only a +ellps declaration, no +datum). This is new */ +/* behavior for PROJ 4.6.0. */ +/* -------------------------------------------------------------------- */ + if( src->datum_type == PJD_UNKNOWN + || dst->datum_type == PJD_UNKNOWN ) + return 0; + +/* -------------------------------------------------------------------- */ +/* Short cut if the datums are identical. */ +/* -------------------------------------------------------------------- */ + if( pj_compare_datums( src, dst ) ) + return 0; + + src_a = src->a_orig; + src_es = src->es_orig; + + dst_a = dst->a_orig; + dst_es = dst->es_orig; + +/* -------------------------------------------------------------------- */ +/* Create a temporary Z array if one is not provided. */ +/* -------------------------------------------------------------------- */ + if( z == NULL ) + { + size_t bytes = sizeof(double) * point_count * point_offset; + z = (double *) pj_malloc(bytes); + memset( z, 0, bytes ); + z_is_temp = TRUE; + } + +#define CHECK_RETURN(defn) {if( defn->ctx->last_errno != 0 && (defn->ctx->last_errno > 0 || get_transient_error_value(-defn->ctx->last_errno) == 0) ) { if( z_is_temp ) pj_dalloc(z); return defn->ctx->last_errno; }} + +/* -------------------------------------------------------------------- */ +/* If this datum requires grid shifts, then apply it to geodetic */ +/* coordinates. */ +/* -------------------------------------------------------------------- */ + if( src->datum_type == PJD_GRIDSHIFT ) + { + pj_apply_gridshift_2( src, 0, point_count, point_offset, x, y, z ); + CHECK_RETURN(src); + + src_a = SRS_WGS84_SEMIMAJOR; + src_es = SRS_WGS84_ESQUARED; + } + + if( dst->datum_type == PJD_GRIDSHIFT ) + { + dst_a = SRS_WGS84_SEMIMAJOR; + dst_es = SRS_WGS84_ESQUARED; + } + +/* ==================================================================== */ +/* Do we need to go through geocentric coordinates? */ +/* ==================================================================== */ + if( src_es != dst_es || src_a != dst_a + || src->datum_type == PJD_3PARAM + || src->datum_type == PJD_7PARAM + || dst->datum_type == PJD_3PARAM + || dst->datum_type == PJD_7PARAM) + { +/* -------------------------------------------------------------------- */ +/* Convert to geocentric coordinates. */ +/* -------------------------------------------------------------------- */ + src->ctx->last_errno = + pj_geodetic_to_geocentric( src_a, src_es, + point_count, point_offset, x, y, z ); + CHECK_RETURN(src); + +/* -------------------------------------------------------------------- */ +/* Convert between datums. */ +/* -------------------------------------------------------------------- */ + if( src->datum_type == PJD_3PARAM + || src->datum_type == PJD_7PARAM ) + { + pj_geocentric_to_wgs84( src, point_count, point_offset,x,y,z); + CHECK_RETURN(src); + } + + if( dst->datum_type == PJD_3PARAM + || dst->datum_type == PJD_7PARAM ) + { + pj_geocentric_from_wgs84( dst, point_count,point_offset,x,y,z); + CHECK_RETURN(dst); + } + +/* -------------------------------------------------------------------- */ +/* Convert back to geodetic coordinates. */ +/* -------------------------------------------------------------------- */ + dst->ctx->last_errno = + pj_geocentric_to_geodetic( dst_a, dst_es, + point_count, point_offset, x, y, z ); + CHECK_RETURN(dst); + } + +/* -------------------------------------------------------------------- */ +/* Apply grid shift to destination if required. */ +/* -------------------------------------------------------------------- */ + if( dst->datum_type == PJD_GRIDSHIFT ) + { + pj_apply_gridshift_2( dst, 1, point_count, point_offset, x, y, z ); + CHECK_RETURN(dst); + } + + if( z_is_temp ) + pj_dalloc( z ); + + return 0; +} + +/************************************************************************/ +/* adjust_axis() */ +/* */ +/* Normalize or de-normalized the x/y/z axes. The normal form */ +/* is "enu" (easting, northing, up). */ +/************************************************************************/ +static int adjust_axis( projCtx ctx, + const char *axis, int denormalize_flag, + long point_count, int point_offset, + double *x, double *y, double *z ) + +{ + double x_in, y_in, z_in = 0.0; + int i, i_axis; + + if( !denormalize_flag ) + { + for( i = 0; i < point_count; i++ ) + { + x_in = x[point_offset*i]; + y_in = y[point_offset*i]; + if( z ) + z_in = z[point_offset*i]; + + for( i_axis = 0; i_axis < 3; i_axis++ ) + { + double value; + + if( i_axis == 0 ) + value = x_in; + else if( i_axis == 1 ) + value = y_in; + else + value = z_in; + + switch( axis[i_axis] ) + { + case 'e': + x[point_offset*i] = value; + break; + case 'w': + x[point_offset*i] = -value; + break; + case 'n': + y[point_offset*i] = value; + break; + case 's': + y[point_offset*i] = -value; + break; + case 'u': + if( z ) + z[point_offset*i] = value; + break; + case 'd': + if( z ) + z[point_offset*i] = -value; + break; + default: + pj_ctx_set_errno( ctx, PJD_ERR_AXIS ); + return PJD_ERR_AXIS; + } + } /* i_axis */ + } /* i (point) */ + } + + else /* denormalize */ + { + for( i = 0; i < point_count; i++ ) + { + x_in = x[point_offset*i]; + y_in = y[point_offset*i]; + if( z ) + z_in = z[point_offset*i]; + + for( i_axis = 0; i_axis < 3; i_axis++ ) + { + double *target; + + if( i_axis == 2 && z == NULL ) + continue; + + if( i_axis == 0 ) + target = x; + else if( i_axis == 1 ) + target = y; + else + target = z; + + switch( axis[i_axis] ) + { + case 'e': + target[point_offset*i] = x_in; break; + case 'w': + target[point_offset*i] = -x_in; break; + case 'n': + target[point_offset*i] = y_in; break; + case 's': + target[point_offset*i] = -y_in; break; + case 'u': + target[point_offset*i] = z_in; break; + case 'd': + target[point_offset*i] = -z_in; break; + default: + pj_ctx_set_errno( ctx, PJD_ERR_AXIS ); + return PJD_ERR_AXIS; + } + } /* i_axis */ + } /* i (point) */ + } + + return 0; +} diff --git a/src/pj_tsfn.c b/src/pj_tsfn.c deleted file mode 100644 index ea3b896d..00000000 --- a/src/pj_tsfn.c +++ /dev/null @@ -1,16 +0,0 @@ -/* determine small t */ -#include -#include "projects.h" - -double pj_tsfn(double phi, double sinphi, double e) { - double denominator; - sinphi *= e; - - /* avoid zero division, fail gracefully */ - denominator = 1.0 + sinphi; - if (denominator == 0.0) - return HUGE_VAL; - - return (tan (.5 * (M_HALFPI - phi)) / - pow((1. - sinphi) / (denominator), .5 * e)); -} diff --git a/src/pj_tsfn.cpp b/src/pj_tsfn.cpp new file mode 100644 index 00000000..ea3b896d --- /dev/null +++ b/src/pj_tsfn.cpp @@ -0,0 +1,16 @@ +/* determine small t */ +#include +#include "projects.h" + +double pj_tsfn(double phi, double sinphi, double e) { + double denominator; + sinphi *= e; + + /* avoid zero division, fail gracefully */ + denominator = 1.0 + sinphi; + if (denominator == 0.0) + return HUGE_VAL; + + return (tan (.5 * (M_HALFPI - phi)) / + pow((1. - sinphi) / (denominator), .5 * e)); +} diff --git a/src/pj_units.c b/src/pj_units.c deleted file mode 100644 index 877758a3..00000000 --- a/src/pj_units.c +++ /dev/null @@ -1,58 +0,0 @@ -/* definition of standard cartesian units */ - -#include - -#include "proj.h" - -#define PJ_UNITS__ -#include "projects.h" - -/* Field 2 that contains the multiplier to convert named units to meters -** may be expressed by either a simple floating point constant or a -** numerator/denomenator values (e.g. 1/1000) */ -static const struct PJ_UNITS -pj_units[] = { - {"km", "1000", "Kilometer", 1000.0}, - {"m", "1", "Meter", 1.0}, - {"dm", "1/10", "Decimeter", 0.1}, - {"cm", "1/100", "Centimeter", 0.01}, - {"mm", "1/1000", "Millimeter", 0.001}, - {"kmi", "1852", "International Nautical Mile", 1852.0}, - {"in", "0.0254", "International Inch", 0.0254}, - {"ft", "0.3048", "International Foot", 0.3048}, - {"yd", "0.9144", "International Yard", 0.9144}, - {"mi", "1609.344", "International Statute Mile", 1609.344}, - {"fath", "1.8288", "International Fathom", 1.8288}, - {"ch", "20.1168", "International Chain", 20.1168}, - {"link", "0.201168", "International Link", 0.201168}, - {"us-in", "1/39.37", "U.S. Surveyor's Inch", 100/3937.0}, - {"us-ft", "0.304800609601219", "U.S. Surveyor's Foot", 1200/3937.0}, - {"us-yd", "0.914401828803658", "U.S. Surveyor's Yard", 3600/3937.0}, - {"us-ch", "20.11684023368047", "U.S. Surveyor's Chain", 79200/3937.0}, - {"us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile", 6336000/3937.0}, - {"ind-yd", "0.91439523", "Indian Yard", 0.91439523}, - {"ind-ft", "0.30479841", "Indian Foot", 0.30479841}, - {"ind-ch", "20.11669506", "Indian Chain", 20.11669506}, - {NULL, NULL, NULL, 0.0} -}; - -const PJ_UNITS *proj_list_units() -{ - return pj_units; -} - -/* M_PI / 200 */ -#define GRAD_TO_RAD 0.015707963267948967 - -const struct PJ_UNITS -pj_angular_units[] = { - {"rad", "1.0", "Radian", 1.0}, - {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, - {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, - {NULL, NULL, NULL, 0.0} -}; - -const PJ_UNITS *proj_list_angular_units() -{ - return pj_angular_units; -} diff --git a/src/pj_units.cpp b/src/pj_units.cpp new file mode 100644 index 00000000..877758a3 --- /dev/null +++ b/src/pj_units.cpp @@ -0,0 +1,58 @@ +/* definition of standard cartesian units */ + +#include + +#include "proj.h" + +#define PJ_UNITS__ +#include "projects.h" + +/* Field 2 that contains the multiplier to convert named units to meters +** may be expressed by either a simple floating point constant or a +** numerator/denomenator values (e.g. 1/1000) */ +static const struct PJ_UNITS +pj_units[] = { + {"km", "1000", "Kilometer", 1000.0}, + {"m", "1", "Meter", 1.0}, + {"dm", "1/10", "Decimeter", 0.1}, + {"cm", "1/100", "Centimeter", 0.01}, + {"mm", "1/1000", "Millimeter", 0.001}, + {"kmi", "1852", "International Nautical Mile", 1852.0}, + {"in", "0.0254", "International Inch", 0.0254}, + {"ft", "0.3048", "International Foot", 0.3048}, + {"yd", "0.9144", "International Yard", 0.9144}, + {"mi", "1609.344", "International Statute Mile", 1609.344}, + {"fath", "1.8288", "International Fathom", 1.8288}, + {"ch", "20.1168", "International Chain", 20.1168}, + {"link", "0.201168", "International Link", 0.201168}, + {"us-in", "1/39.37", "U.S. Surveyor's Inch", 100/3937.0}, + {"us-ft", "0.304800609601219", "U.S. Surveyor's Foot", 1200/3937.0}, + {"us-yd", "0.914401828803658", "U.S. Surveyor's Yard", 3600/3937.0}, + {"us-ch", "20.11684023368047", "U.S. Surveyor's Chain", 79200/3937.0}, + {"us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile", 6336000/3937.0}, + {"ind-yd", "0.91439523", "Indian Yard", 0.91439523}, + {"ind-ft", "0.30479841", "Indian Foot", 0.30479841}, + {"ind-ch", "20.11669506", "Indian Chain", 20.11669506}, + {NULL, NULL, NULL, 0.0} +}; + +const PJ_UNITS *proj_list_units() +{ + return pj_units; +} + +/* M_PI / 200 */ +#define GRAD_TO_RAD 0.015707963267948967 + +const struct PJ_UNITS +pj_angular_units[] = { + {"rad", "1.0", "Radian", 1.0}, + {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, + {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, + {NULL, NULL, NULL, 0.0} +}; + +const PJ_UNITS *proj_list_angular_units() +{ + return pj_angular_units; +} diff --git a/src/pj_utils.c b/src/pj_utils.c deleted file mode 100644 index 81a80b45..00000000 --- a/src/pj_utils.c +++ /dev/null @@ -1,181 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Some utility functions we don't want to bother putting in - * their own source files. - * Author: Frank Warmerdam, warmerdam@pobox.com - * - ****************************************************************************** - * Copyright (c) 2001, 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 PJ_LIB__ - -#include -#include - -#include "projects.h" - -/************************************************************************/ -/* pj_is_latlong() */ -/* */ -/* Returns TRUE if this coordinate system object is */ -/* geographic. */ -/************************************************************************/ - -int pj_is_latlong( PJ *pj ) - -{ - return pj == NULL || pj->is_latlong; -} - -/************************************************************************/ -/* pj_is_geocent() */ -/* */ -/* Returns TRUE if this coordinate system object is geocentric. */ -/************************************************************************/ - -int pj_is_geocent( PJ *pj ) - -{ - return pj != NULL && pj->is_geocent; -} - -/************************************************************************/ -/* pj_latlong_from_proj() */ -/* */ -/* Return a PJ* definition defining the lat/long coordinate */ -/* system on which a projection is based. If the coordinate */ -/* system passed in is latlong, a clone of the same will be */ -/* returned. */ -/************************************************************************/ - -PJ *pj_latlong_from_proj( PJ *pj_in ) - -{ - char defn[512]; - int got_datum = FALSE; - - pj_errno = 0; - strcpy( defn, "+proj=latlong" ); - - if( pj_param(pj_in->ctx, pj_in->params, "tdatum").i ) - { - got_datum = TRUE; - sprintf( defn+strlen(defn), " +datum=%s", - pj_param(pj_in->ctx, pj_in->params,"sdatum").s ); - } - else if( pj_param(pj_in->ctx, pj_in->params, "tellps").i ) - { - sprintf( defn+strlen(defn), " +ellps=%s", - pj_param(pj_in->ctx, pj_in->params,"sellps").s ); - } - else if( pj_param(pj_in->ctx,pj_in->params, "ta").i ) - { - sprintf( defn+strlen(defn), " +a=%s", - pj_param(pj_in->ctx,pj_in->params,"sa").s ); - - if( pj_param(pj_in->ctx,pj_in->params, "tb").i ) - sprintf( defn+strlen(defn), " +b=%s", - pj_param(pj_in->ctx,pj_in->params,"sb").s ); - else if( pj_param(pj_in->ctx,pj_in->params, "tes").i ) - sprintf( defn+strlen(defn), " +es=%s", - pj_param(pj_in->ctx,pj_in->params,"ses").s ); - else if( pj_param(pj_in->ctx,pj_in->params, "tf").i ) - sprintf( defn+strlen(defn), " +f=%s", - pj_param(pj_in->ctx,pj_in->params,"sf").s ); - else - { - char* ptr = defn+strlen(defn); - sprintf( ptr, " +es=%.16g", pj_in->es ); - /* TODO later: use C++ ostringstream with imbue(std::locale::classic()) */ - /* to be locale unaware */ - for(; *ptr; ptr++) - { - if( *ptr == ',' ) - *ptr = '.'; - } - } - } - else - { - pj_ctx_set_errno( pj_in->ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN ); - - return NULL; - } - - if( !got_datum ) - { - if( pj_param(pj_in->ctx,pj_in->params, "ttowgs84").i ) - sprintf( defn+strlen(defn), " +towgs84=%s", - pj_param(pj_in->ctx,pj_in->params,"stowgs84").s ); - - if( pj_param(pj_in->ctx,pj_in->params, "tnadgrids").i ) - sprintf( defn+strlen(defn), " +nadgrids=%s", - pj_param(pj_in->ctx,pj_in->params,"snadgrids").s ); - } - - /* copy over some other information related to ellipsoid */ - if( pj_param(pj_in->ctx,pj_in->params, "tR").i ) - sprintf( defn+strlen(defn), " +R=%s", - pj_param(pj_in->ctx,pj_in->params,"sR").s ); - - if( pj_param(pj_in->ctx,pj_in->params, "tR_A").i ) - sprintf( defn+strlen(defn), " +R_A" ); - - if( pj_param(pj_in->ctx,pj_in->params, "tR_V").i ) - sprintf( defn+strlen(defn), " +R_V" ); - - if( pj_param(pj_in->ctx,pj_in->params, "tR_a").i ) - sprintf( defn+strlen(defn), " +R_a" ); - - if( pj_param(pj_in->ctx,pj_in->params, "tR_lat_a").i ) - sprintf( defn+strlen(defn), " +R_lat_a=%s", - pj_param(pj_in->ctx,pj_in->params,"sR_lat_a").s ); - - if( pj_param(pj_in->ctx,pj_in->params, "tR_lat_g").i ) - sprintf( defn+strlen(defn), " +R_lat_g=%s", - pj_param(pj_in->ctx,pj_in->params,"sR_lat_g").s ); - - /* copy over prime meridian */ - if( pj_param(pj_in->ctx,pj_in->params, "tpm").i ) - sprintf( defn+strlen(defn), " +pm=%s", - pj_param(pj_in->ctx,pj_in->params,"spm").s ); - - return pj_init_plus_ctx( pj_in->ctx, defn ); -} - -/************************************************************************/ -/* pj_get_spheroid_defn() */ -/* */ -/* Fetch the internal definition of the spheroid. Note that */ -/* you can compute "b" from eccentricity_squared as: */ -/* */ -/* b = a * sqrt(1 - es) */ -/************************************************************************/ - -void pj_get_spheroid_defn(projPJ defn, double *major_axis, double *eccentricity_squared) -{ - if ( major_axis ) - *major_axis = defn->a; - - if ( eccentricity_squared ) - *eccentricity_squared = defn->es; -} diff --git a/src/pj_utils.cpp b/src/pj_utils.cpp new file mode 100644 index 00000000..81a80b45 --- /dev/null +++ b/src/pj_utils.cpp @@ -0,0 +1,181 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Some utility functions we don't want to bother putting in + * their own source files. + * Author: Frank Warmerdam, warmerdam@pobox.com + * + ****************************************************************************** + * Copyright (c) 2001, 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 PJ_LIB__ + +#include +#include + +#include "projects.h" + +/************************************************************************/ +/* pj_is_latlong() */ +/* */ +/* Returns TRUE if this coordinate system object is */ +/* geographic. */ +/************************************************************************/ + +int pj_is_latlong( PJ *pj ) + +{ + return pj == NULL || pj->is_latlong; +} + +/************************************************************************/ +/* pj_is_geocent() */ +/* */ +/* Returns TRUE if this coordinate system object is geocentric. */ +/************************************************************************/ + +int pj_is_geocent( PJ *pj ) + +{ + return pj != NULL && pj->is_geocent; +} + +/************************************************************************/ +/* pj_latlong_from_proj() */ +/* */ +/* Return a PJ* definition defining the lat/long coordinate */ +/* system on which a projection is based. If the coordinate */ +/* system passed in is latlong, a clone of the same will be */ +/* returned. */ +/************************************************************************/ + +PJ *pj_latlong_from_proj( PJ *pj_in ) + +{ + char defn[512]; + int got_datum = FALSE; + + pj_errno = 0; + strcpy( defn, "+proj=latlong" ); + + if( pj_param(pj_in->ctx, pj_in->params, "tdatum").i ) + { + got_datum = TRUE; + sprintf( defn+strlen(defn), " +datum=%s", + pj_param(pj_in->ctx, pj_in->params,"sdatum").s ); + } + else if( pj_param(pj_in->ctx, pj_in->params, "tellps").i ) + { + sprintf( defn+strlen(defn), " +ellps=%s", + pj_param(pj_in->ctx, pj_in->params,"sellps").s ); + } + else if( pj_param(pj_in->ctx,pj_in->params, "ta").i ) + { + sprintf( defn+strlen(defn), " +a=%s", + pj_param(pj_in->ctx,pj_in->params,"sa").s ); + + if( pj_param(pj_in->ctx,pj_in->params, "tb").i ) + sprintf( defn+strlen(defn), " +b=%s", + pj_param(pj_in->ctx,pj_in->params,"sb").s ); + else if( pj_param(pj_in->ctx,pj_in->params, "tes").i ) + sprintf( defn+strlen(defn), " +es=%s", + pj_param(pj_in->ctx,pj_in->params,"ses").s ); + else if( pj_param(pj_in->ctx,pj_in->params, "tf").i ) + sprintf( defn+strlen(defn), " +f=%s", + pj_param(pj_in->ctx,pj_in->params,"sf").s ); + else + { + char* ptr = defn+strlen(defn); + sprintf( ptr, " +es=%.16g", pj_in->es ); + /* TODO later: use C++ ostringstream with imbue(std::locale::classic()) */ + /* to be locale unaware */ + for(; *ptr; ptr++) + { + if( *ptr == ',' ) + *ptr = '.'; + } + } + } + else + { + pj_ctx_set_errno( pj_in->ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN ); + + return NULL; + } + + if( !got_datum ) + { + if( pj_param(pj_in->ctx,pj_in->params, "ttowgs84").i ) + sprintf( defn+strlen(defn), " +towgs84=%s", + pj_param(pj_in->ctx,pj_in->params,"stowgs84").s ); + + if( pj_param(pj_in->ctx,pj_in->params, "tnadgrids").i ) + sprintf( defn+strlen(defn), " +nadgrids=%s", + pj_param(pj_in->ctx,pj_in->params,"snadgrids").s ); + } + + /* copy over some other information related to ellipsoid */ + if( pj_param(pj_in->ctx,pj_in->params, "tR").i ) + sprintf( defn+strlen(defn), " +R=%s", + pj_param(pj_in->ctx,pj_in->params,"sR").s ); + + if( pj_param(pj_in->ctx,pj_in->params, "tR_A").i ) + sprintf( defn+strlen(defn), " +R_A" ); + + if( pj_param(pj_in->ctx,pj_in->params, "tR_V").i ) + sprintf( defn+strlen(defn), " +R_V" ); + + if( pj_param(pj_in->ctx,pj_in->params, "tR_a").i ) + sprintf( defn+strlen(defn), " +R_a" ); + + if( pj_param(pj_in->ctx,pj_in->params, "tR_lat_a").i ) + sprintf( defn+strlen(defn), " +R_lat_a=%s", + pj_param(pj_in->ctx,pj_in->params,"sR_lat_a").s ); + + if( pj_param(pj_in->ctx,pj_in->params, "tR_lat_g").i ) + sprintf( defn+strlen(defn), " +R_lat_g=%s", + pj_param(pj_in->ctx,pj_in->params,"sR_lat_g").s ); + + /* copy over prime meridian */ + if( pj_param(pj_in->ctx,pj_in->params, "tpm").i ) + sprintf( defn+strlen(defn), " +pm=%s", + pj_param(pj_in->ctx,pj_in->params,"spm").s ); + + return pj_init_plus_ctx( pj_in->ctx, defn ); +} + +/************************************************************************/ +/* pj_get_spheroid_defn() */ +/* */ +/* Fetch the internal definition of the spheroid. Note that */ +/* you can compute "b" from eccentricity_squared as: */ +/* */ +/* b = a * sqrt(1 - es) */ +/************************************************************************/ + +void pj_get_spheroid_defn(projPJ defn, double *major_axis, double *eccentricity_squared) +{ + if ( major_axis ) + *major_axis = defn->a; + + if ( eccentricity_squared ) + *eccentricity_squared = defn->es; +} diff --git a/src/pj_zpoly1.c b/src/pj_zpoly1.c deleted file mode 100644 index bacb62ce..00000000 --- a/src/pj_zpoly1.c +++ /dev/null @@ -1,46 +0,0 @@ -/* evaluate complex polynomial */ -#include "projects.h" -/* note: coefficients are always from C_1 to C_n -** i.e. C_0 == (0., 0) -** n should always be >= 1 though no checks are made -*/ - COMPLEX -pj_zpoly1(COMPLEX z, const COMPLEX *C, int n) { - COMPLEX a; - double t; - - a = *(C += n); - while (n-- > 0) { - a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; - a.i = C->i + z.r * a.i + z.i * t; - } - a.r = z.r * (t = a.r) - z.i * a.i; - a.i = z.r * a.i + z.i * t; - return a; -} -/* evaluate complex polynomial and derivative */ - COMPLEX -pj_zpolyd1(COMPLEX z, const COMPLEX *C, int n, COMPLEX *der) { - COMPLEX a, b; - double t; - int first = 1; - - a = *(C += n); - b = a; - while (n-- > 0) { - if (first) { - first = 0; - } else { - b.r = a.r + z.r * (t = b.r) - z.i * b.i; - b.i = a.i + z.r * b.i + z.i * t; - } - a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; - a.i = C->i + z.r * a.i + z.i * t; - } - b.r = a.r + z.r * (t = b.r) - z.i * b.i; - b.i = a.i + z.r * b.i + z.i * t; - a.r = z.r * (t = a.r) - z.i * a.i; - a.i = z.r * a.i + z.i * t; - *der = b; - return a; -} diff --git a/src/pj_zpoly1.cpp b/src/pj_zpoly1.cpp new file mode 100644 index 00000000..bacb62ce --- /dev/null +++ b/src/pj_zpoly1.cpp @@ -0,0 +1,46 @@ +/* evaluate complex polynomial */ +#include "projects.h" +/* note: coefficients are always from C_1 to C_n +** i.e. C_0 == (0., 0) +** n should always be >= 1 though no checks are made +*/ + COMPLEX +pj_zpoly1(COMPLEX z, const COMPLEX *C, int n) { + COMPLEX a; + double t; + + a = *(C += n); + while (n-- > 0) { + a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; + a.i = C->i + z.r * a.i + z.i * t; + } + a.r = z.r * (t = a.r) - z.i * a.i; + a.i = z.r * a.i + z.i * t; + return a; +} +/* evaluate complex polynomial and derivative */ + COMPLEX +pj_zpolyd1(COMPLEX z, const COMPLEX *C, int n, COMPLEX *der) { + COMPLEX a, b; + double t; + int first = 1; + + a = *(C += n); + b = a; + while (n-- > 0) { + if (first) { + first = 0; + } else { + b.r = a.r + z.r * (t = b.r) - z.i * b.i; + b.i = a.i + z.r * b.i + z.i * t; + } + a.r = (--C)->r + z.r * (t = a.r) - z.i * a.i; + a.i = C->i + z.r * a.i + z.i * t; + } + b.r = a.r + z.r * (t = b.r) - z.i * b.i; + b.i = a.i + z.r * b.i + z.i * t; + a.r = z.r * (t = a.r) - z.i * a.i; + a.i = z.r * a.i + z.i * t; + *der = b; + return a; +} diff --git a/src/proj.c b/src/proj.c deleted file mode 100644 index 2405781c..00000000 --- a/src/proj.c +++ /dev/null @@ -1,581 +0,0 @@ -/* <<<< Cartographic projection filter program >>>> */ -#include "proj.h" -#include "projects.h" -#include -#include -#include -#include -#include -#include "emess.h" - -#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__WIN32__) -# include -# include -# define SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY) -#else -# define SET_BINARY_MODE(file) -#endif - -#define MAX_LINE 1000 -#define MAX_PARGS 100 -#define PJ_INVERS(P) (P->inv ? 1 : 0) - -extern void gen_cheb(int, projUV(*)(projUV), char *, PJ *, int, char **); - -static PJ *Proj; -static union { - projUV (*generic)(projUV, PJ *); - projXY (*fwd)(projLP, PJ *); - projLP (*inv)(projXY, PJ *); -} proj; - -static int - reversein = 0, /* != 0 reverse input arguments */ - reverseout = 0, /* != 0 reverse output arguments */ - bin_in = 0, /* != 0 then binary input */ - bin_out = 0, /* != 0 then binary output */ - echoin = 0, /* echo input data to output line */ - tag = '#', /* beginning of line tag character */ - inverse = 0, /* != 0 then inverse projection */ - prescale = 0, /* != 0 apply cartesian scale factor */ - dofactors = 0, /* determine scale factors */ - very_verby = 0, /* very verbose mode */ - postscale = 0; - -static char - *cheby_str, /* string controlling Chebychev evaluation */ - *oform = (char *)0, /* output format for x-y or decimal degrees */ - oform_buffer[16]; /* Buffer for oform when using -d */ - -static const char - *oterr = "*\t*", /* output line for unprojectable input */ - *usage = "%s\nusage: %s [ -bdeEfiIlmorsStTvVwW [args] ] [ +opts[=arg] ] [ files ]\n"; - -static PJ_FACTORS facs; - -static double (*informat)(const char *, char **), /* input data deformatter function */ - fscale = 0.; /* cartesian scale factor */ - -static projUV int_proj(projUV data) { - if (prescale) { - data.u *= fscale; - data.v *= fscale; - } - - data = (*proj.generic)(data, Proj); - - if (postscale && data.u != HUGE_VAL) { - data.u *= fscale; - data.v *= fscale; - } - - return data; -} - -/* file processing function */ -static void process(FILE *fid) { - char line[MAX_LINE+3], *s = 0, pline[40]; - PJ_COORD data; - - for (;;) { - int facs_bad = 0; - ++emess_dat.File_line; - - if (bin_in) { /* binary input */ - if (fread(&data, sizeof(projUV), 1, fid) != 1) - break; - } else { /* ascii input */ - 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) { - if (!bin_out) - (void)fputs(line, stdout); - continue; - } - - if (reversein) { - data.uv.v = (*informat)(s, &s); - data.uv.u = (*informat)(s, &s); - } else { - data.uv.u = (*informat)(s, &s); - data.uv.v = (*informat)(s, &s); - } - - if (data.uv.v == HUGE_VAL) - data.uv.u = HUGE_VAL; - - if (!*s && (s > line)) --s; /* assumed we gobbled \n */ - if (!bin_out && echoin) { - char t; - t = *s; - *s = '\0'; - (void)fputs(line, stdout); - *s = t; - putchar('\t'); - } - } - - if (data.uv.u != HUGE_VAL) { - PJ_COORD coord; - coord.lp = data.lp; - if (prescale) { data.uv.u *= fscale; data.uv.v *= fscale; } - if (dofactors && !inverse) { - facs = proj_factors(Proj, coord); - facs_bad = proj_errno(Proj); - } - - data.xy = (*proj.fwd)(data.lp, Proj); - - if (dofactors && inverse) { - facs = proj_factors(Proj, coord); - facs_bad = proj_errno(Proj); - } - - if (postscale && data.uv.u != HUGE_VAL) - { data.uv.u *= fscale; data.uv.v *= fscale; } - } - - if (bin_out) { /* binary output */ - (void)fwrite(&data, sizeof(projUV), 1, stdout); - continue; - } else if (data.uv.u == HUGE_VAL) /* error output */ - (void)fputs(oterr, stdout); - else if (inverse && !oform) { /*ascii DMS output */ - if (reverseout) { - (void)fputs(rtodms(pline, data.uv.v, 'N', 'S'), stdout); - putchar('\t'); - (void)fputs(rtodms(pline, data.uv.u, 'E', 'W'), stdout); - } else { - (void)fputs(rtodms(pline, data.uv.u, 'E', 'W'), stdout); - putchar('\t'); - (void)fputs(rtodms(pline, data.uv.v, 'N', 'S'), stdout); - } - } else { /* x-y or decimal degree ascii output, scale if warranted by output units */ - if (inverse) { - if (proj_angular_input(Proj, PJ_FWD)) { - data.uv.v *= RAD_TO_DEG; - data.uv.u *= RAD_TO_DEG; - } - } else { - if (proj_angular_output(Proj, PJ_FWD)) { - data.uv.v *= RAD_TO_DEG; - data.uv.u *= RAD_TO_DEG; - } - } - - if (reverseout) { - (void)printf(oform, data.uv.v); putchar('\t'); - (void)printf(oform, data.uv.u); - } else { - (void)printf(oform, data.uv.u); putchar('\t'); - (void)printf(oform, data.uv.v); - } - } - - /* print scale factor data */ - if (dofactors) { - if (!facs_bad) - (void)printf("\t<%g %g %g %g %g %g>", - facs.meridional_scale, facs.parallel_scale, facs.areal_scale, - facs.angular_distortion * RAD_TO_DEG, facs.tissot_semimajor, facs.tissot_semiminor); - else - (void)fputs("\t<* * * * * *>", stdout); - } - (void)fputs(bin_in ? "\n" : s, stdout); - } -} - -/* file processing function --- verbosely */ -static void vprocess(FILE *fid) { - char line[MAX_LINE+3], *s, pline[40]; - LP dat_ll; - projXY dat_xy; - int linvers; - PJ_COORD coord; - - - if (!oform) - oform = "%.3f"; - - if (bin_in || bin_out) - emess(1,"binary I/O not available in -V option"); - - for (;;) { - proj_errno_reset(Proj); - ++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) { /* pass on data */ - (void)fputs(s, stdout); - continue; - } - - /* check to override default input mode */ - if (*s == 'I' || *s == 'i') { - linvers = 1; - ++s; - } else - linvers = inverse; - - if (linvers) { - if (!PJ_INVERS(Proj)) { - emess(-1,"inverse for this projection not avail.\n"); - continue; - } - dat_xy.x = strtod(s, &s); - dat_xy.y = strtod(s, &s); - if (dat_xy.x == HUGE_VAL || dat_xy.y == HUGE_VAL) { - emess(-1,"lon-lat input conversion failure\n"); - continue; - } - if (prescale) { dat_xy.x *= fscale; dat_xy.y *= fscale; } - if (reversein) { - projXY temp = dat_xy; - dat_xy.x = temp.y; - dat_xy.y = temp.x; - } - dat_ll = pj_inv(dat_xy, Proj); - } else { - dat_ll.lam = proj_dmstor(s, &s); - dat_ll.phi = proj_dmstor(s, &s); - if (dat_ll.lam == HUGE_VAL || dat_ll.phi == HUGE_VAL) { - emess(-1,"lon-lat input conversion failure\n"); - continue; - } - if (reversein) { - LP temp = dat_ll; - dat_ll.lam = temp.phi; - dat_ll.phi = temp.lam; - } - dat_xy = pj_fwd(dat_ll, Proj); - if (postscale) { dat_xy.x *= fscale; dat_xy.y *= fscale; } - } - - /* For some reason pj_errno does not work as expected in some */ - /* versions of Visual Studio, so using pj_get_errno_ref instead */ - if (*pj_get_errno_ref()) { - emess(-1, pj_strerrno(*pj_get_errno_ref())); - continue; - } - - if (!*s && (s > line)) --s; /* assumed we gobbled \n */ - coord.lp = dat_ll; - facs = proj_factors(Proj, coord); - if (proj_errno(Proj)) { - emess(-1,"failed to compute factors\n\n"); - continue; - } - - if (*s != '\n') - (void)fputs(s, stdout); - - (void)fputs("Longitude: ", stdout); - (void)fputs(proj_rtodms(pline, dat_ll.lam, 'E', 'W'), stdout); - (void)printf(" [ %.11g ]\n", dat_ll.lam * RAD_TO_DEG); - (void)fputs("Latitude: ", stdout); - (void)fputs(proj_rtodms(pline, dat_ll.phi, 'N', 'S'), stdout); - (void)printf(" [ %.11g ]\n", dat_ll.phi * RAD_TO_DEG); - (void)fputs("Easting (x): ", stdout); - (void)printf(oform, dat_xy.x); putchar('\n'); - (void)fputs("Northing (y): ", stdout); - (void)printf(oform, dat_xy.y); putchar('\n'); - (void)printf("Meridian scale (h) : %.8f ( %.4g %% error )\n", facs.meridional_scale, (facs.meridional_scale-1.)*100.); - (void)printf("Parallel scale (k) : %.8f ( %.4g %% error )\n", facs.parallel_scale, (facs.parallel_scale-1.)*100.); - (void)printf("Areal scale (s): %.8f ( %.4g %% error )\n", facs.areal_scale, (facs.areal_scale-1.)*100.); - (void)printf("Angular distortion (w): %.3f\n", facs.angular_distortion * RAD_TO_DEG); - (void)printf("Meridian/Parallel angle: %.5f\n", facs.meridian_parallel_angle * RAD_TO_DEG); - (void)printf("Convergence : "); - (void)fputs(proj_rtodms(pline, facs.meridian_convergence, 0, 0), stdout); - (void)printf(" [ %.8f ]\n", facs.meridian_convergence * RAD_TO_DEG); - (void)printf("Max-min (Tissot axis a-b) scale error: %.5f %.5f\n\n", facs.tissot_semimajor, facs.tissot_semiminor); - } -} - -int main(int argc, char **argv) { - char *arg, **eargv = argv, *pargv[MAX_PARGS], **iargv = argv; - FILE *fid; - int pargc = 0, iargc = argc, eargc = 0, mon = 0; - - if ( (emess_dat.Prog_name = strrchr(*argv,DIR_CHAR)) != NULL) - ++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++] = "-"; - break; - case 'b': /* binary I/O */ - bin_in = bin_out = 1; - continue; - case 'v': /* monitor dump of initialization */ - mon = 1; - continue; - case 'i': /* input binary */ - bin_in = 1; - continue; - case 'o': /* output binary */ - bin_out = 1; - continue; - case 'I': /* alt. method to spec inverse */ - inverse = 1; - continue; - case 'E': /* echo ascii input to ascii output */ - echoin = 1; - continue; - case 'V': /* very verbose processing mode */ - very_verby = 1; - mon = 1; - continue; - case 'S': /* compute scale factors */ - dofactors = 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) { - if( strcmp(lp->id,"latlong") == 0 - || strcmp(lp->id,"longlat") == 0 - || strcmp(lp->id,"geocent") == 0 ) - continue; - - (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 != NULL && strlen(ld->comments) > 0 ) - printf( "%25s %s\n", " ", ld->comments ); - } - } 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 'T': /* generate Chebyshev coefficients */ - if (--argc <= 0) goto noargument; - cheby_str = *++argv; - continue; - case 'm': /* cartesian multiplier */ - if (--argc <= 0) goto noargument; - postscale = 1; - if (!strncmp("1/",*++argv,2) || - !strncmp("1:",*argv,2)) { - if((fscale = atof((*argv)+2)) == 0.) - goto badscale; - fscale = 1. / fscale; - } else - if ((fscale = atof(*argv)) == 0.) { - badscale: - emess(1,"invalid scale argument"); - } - continue; - case 'W': /* specify seconds precision */ - case 'w': /* -W for constant field width */ - { - int 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 'd': - if (--argc <= 0) goto noargument; - sprintf(oform_buffer, "%%.%df", atoi(*++argv)); - oform = oform_buffer; - break; - case 'r': /* reverse input */ - reversein = 1; - continue; - case 's': /* reverse output */ - reverseout = 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; - } - if (eargc == 0 && !cheby_str) /* if no specific files force sysin */ - eargv[eargc++] = "-"; - else if (eargc > 0 && cheby_str) /* warning */ - emess(4, "data files when generating Chebychev prohibited"); - /* done with parameter and control input */ - if (inverse && postscale) { - prescale = 1; - postscale = 0; - fscale = 1./fscale; - } - if (!(Proj = pj_init(pargc, pargv))) - emess(3,"projection initialization failure\ncause: %s", - pj_strerrno(pj_errno)); - - if (!proj_angular_input(Proj, PJ_FWD)) { - emess(3, "can't initialize operations that take non-angular input coordinates"); - exit(0); - } - - if (proj_angular_output(Proj, PJ_FWD)) { - emess(3, "can't initialize operations that produce angular output coordinates"); - exit(0); - } - - if (inverse) { - if (!Proj->inv) - emess(3,"inverse projection not available"); - proj.inv = pj_inv; - } else - proj.fwd = pj_fwd; - if (cheby_str) { - gen_cheb(inverse, int_proj, cheby_str, Proj, iargc, iargv); - exit(0); - } - - /* set input formatting control */ - if (mon) { - pj_pr_list(Proj); - if (very_verby) { - (void)printf("#Final Earth figure: "); - if (Proj->es != 0.0) { - (void)printf("ellipsoid\n# Major axis (a): "); - (void)printf(oform ? oform : "%.3f", Proj->a); - (void)printf("\n# 1/flattening: %.6f\n", - 1./(1. - sqrt(1. - Proj->es))); - (void)printf("# squared eccentricity: %.12f\n", Proj->es); - } else { - (void)printf("sphere\n# Radius: "); - (void)printf(oform ? oform : "%.3f", Proj->a); - (void)putchar('\n'); - } - } - } - - if (inverse) - informat = strtod; - else { - informat = proj_dmstor; - if (!oform) - oform = "%.2f"; - } - - if (bin_out) { - SET_BINARY_MODE(stdout); - } - - /* process input file list */ - for ( ; eargc-- ; ++eargv) { - if (**eargv == '-') { - fid = stdin; - emess_dat.File_name = ""; - - if (bin_in) - { - SET_BINARY_MODE(stdin); - } - - } else { - if ((fid = fopen(*eargv, "rb")) == NULL) { - emess(-2, *eargv, "input file"); - continue; - } - emess_dat.File_name = *eargv; - } - emess_dat.File_line = 0; - if (very_verby) - vprocess(fid); - else - process(fid); - (void)fclose(fid); - emess_dat.File_name = 0; - } - - if( Proj ) - pj_free(Proj); - - exit(0); /* normal completion */ -} diff --git a/src/proj.cpp b/src/proj.cpp new file mode 100644 index 00000000..2405781c --- /dev/null +++ b/src/proj.cpp @@ -0,0 +1,581 @@ +/* <<<< Cartographic projection filter program >>>> */ +#include "proj.h" +#include "projects.h" +#include +#include +#include +#include +#include +#include "emess.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__WIN32__) +# include +# include +# define SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define MAX_LINE 1000 +#define MAX_PARGS 100 +#define PJ_INVERS(P) (P->inv ? 1 : 0) + +extern void gen_cheb(int, projUV(*)(projUV), char *, PJ *, int, char **); + +static PJ *Proj; +static union { + projUV (*generic)(projUV, PJ *); + projXY (*fwd)(projLP, PJ *); + projLP (*inv)(projXY, PJ *); +} proj; + +static int + reversein = 0, /* != 0 reverse input arguments */ + reverseout = 0, /* != 0 reverse output arguments */ + bin_in = 0, /* != 0 then binary input */ + bin_out = 0, /* != 0 then binary output */ + echoin = 0, /* echo input data to output line */ + tag = '#', /* beginning of line tag character */ + inverse = 0, /* != 0 then inverse projection */ + prescale = 0, /* != 0 apply cartesian scale factor */ + dofactors = 0, /* determine scale factors */ + very_verby = 0, /* very verbose mode */ + postscale = 0; + +static char + *cheby_str, /* string controlling Chebychev evaluation */ + *oform = (char *)0, /* output format for x-y or decimal degrees */ + oform_buffer[16]; /* Buffer for oform when using -d */ + +static const char + *oterr = "*\t*", /* output line for unprojectable input */ + *usage = "%s\nusage: %s [ -bdeEfiIlmorsStTvVwW [args] ] [ +opts[=arg] ] [ files ]\n"; + +static PJ_FACTORS facs; + +static double (*informat)(const char *, char **), /* input data deformatter function */ + fscale = 0.; /* cartesian scale factor */ + +static projUV int_proj(projUV data) { + if (prescale) { + data.u *= fscale; + data.v *= fscale; + } + + data = (*proj.generic)(data, Proj); + + if (postscale && data.u != HUGE_VAL) { + data.u *= fscale; + data.v *= fscale; + } + + return data; +} + +/* file processing function */ +static void process(FILE *fid) { + char line[MAX_LINE+3], *s = 0, pline[40]; + PJ_COORD data; + + for (;;) { + int facs_bad = 0; + ++emess_dat.File_line; + + if (bin_in) { /* binary input */ + if (fread(&data, sizeof(projUV), 1, fid) != 1) + break; + } else { /* ascii input */ + 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) { + if (!bin_out) + (void)fputs(line, stdout); + continue; + } + + if (reversein) { + data.uv.v = (*informat)(s, &s); + data.uv.u = (*informat)(s, &s); + } else { + data.uv.u = (*informat)(s, &s); + data.uv.v = (*informat)(s, &s); + } + + if (data.uv.v == HUGE_VAL) + data.uv.u = HUGE_VAL; + + if (!*s && (s > line)) --s; /* assumed we gobbled \n */ + if (!bin_out && echoin) { + char t; + t = *s; + *s = '\0'; + (void)fputs(line, stdout); + *s = t; + putchar('\t'); + } + } + + if (data.uv.u != HUGE_VAL) { + PJ_COORD coord; + coord.lp = data.lp; + if (prescale) { data.uv.u *= fscale; data.uv.v *= fscale; } + if (dofactors && !inverse) { + facs = proj_factors(Proj, coord); + facs_bad = proj_errno(Proj); + } + + data.xy = (*proj.fwd)(data.lp, Proj); + + if (dofactors && inverse) { + facs = proj_factors(Proj, coord); + facs_bad = proj_errno(Proj); + } + + if (postscale && data.uv.u != HUGE_VAL) + { data.uv.u *= fscale; data.uv.v *= fscale; } + } + + if (bin_out) { /* binary output */ + (void)fwrite(&data, sizeof(projUV), 1, stdout); + continue; + } else if (data.uv.u == HUGE_VAL) /* error output */ + (void)fputs(oterr, stdout); + else if (inverse && !oform) { /*ascii DMS output */ + if (reverseout) { + (void)fputs(rtodms(pline, data.uv.v, 'N', 'S'), stdout); + putchar('\t'); + (void)fputs(rtodms(pline, data.uv.u, 'E', 'W'), stdout); + } else { + (void)fputs(rtodms(pline, data.uv.u, 'E', 'W'), stdout); + putchar('\t'); + (void)fputs(rtodms(pline, data.uv.v, 'N', 'S'), stdout); + } + } else { /* x-y or decimal degree ascii output, scale if warranted by output units */ + if (inverse) { + if (proj_angular_input(Proj, PJ_FWD)) { + data.uv.v *= RAD_TO_DEG; + data.uv.u *= RAD_TO_DEG; + } + } else { + if (proj_angular_output(Proj, PJ_FWD)) { + data.uv.v *= RAD_TO_DEG; + data.uv.u *= RAD_TO_DEG; + } + } + + if (reverseout) { + (void)printf(oform, data.uv.v); putchar('\t'); + (void)printf(oform, data.uv.u); + } else { + (void)printf(oform, data.uv.u); putchar('\t'); + (void)printf(oform, data.uv.v); + } + } + + /* print scale factor data */ + if (dofactors) { + if (!facs_bad) + (void)printf("\t<%g %g %g %g %g %g>", + facs.meridional_scale, facs.parallel_scale, facs.areal_scale, + facs.angular_distortion * RAD_TO_DEG, facs.tissot_semimajor, facs.tissot_semiminor); + else + (void)fputs("\t<* * * * * *>", stdout); + } + (void)fputs(bin_in ? "\n" : s, stdout); + } +} + +/* file processing function --- verbosely */ +static void vprocess(FILE *fid) { + char line[MAX_LINE+3], *s, pline[40]; + LP dat_ll; + projXY dat_xy; + int linvers; + PJ_COORD coord; + + + if (!oform) + oform = "%.3f"; + + if (bin_in || bin_out) + emess(1,"binary I/O not available in -V option"); + + for (;;) { + proj_errno_reset(Proj); + ++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) { /* pass on data */ + (void)fputs(s, stdout); + continue; + } + + /* check to override default input mode */ + if (*s == 'I' || *s == 'i') { + linvers = 1; + ++s; + } else + linvers = inverse; + + if (linvers) { + if (!PJ_INVERS(Proj)) { + emess(-1,"inverse for this projection not avail.\n"); + continue; + } + dat_xy.x = strtod(s, &s); + dat_xy.y = strtod(s, &s); + if (dat_xy.x == HUGE_VAL || dat_xy.y == HUGE_VAL) { + emess(-1,"lon-lat input conversion failure\n"); + continue; + } + if (prescale) { dat_xy.x *= fscale; dat_xy.y *= fscale; } + if (reversein) { + projXY temp = dat_xy; + dat_xy.x = temp.y; + dat_xy.y = temp.x; + } + dat_ll = pj_inv(dat_xy, Proj); + } else { + dat_ll.lam = proj_dmstor(s, &s); + dat_ll.phi = proj_dmstor(s, &s); + if (dat_ll.lam == HUGE_VAL || dat_ll.phi == HUGE_VAL) { + emess(-1,"lon-lat input conversion failure\n"); + continue; + } + if (reversein) { + LP temp = dat_ll; + dat_ll.lam = temp.phi; + dat_ll.phi = temp.lam; + } + dat_xy = pj_fwd(dat_ll, Proj); + if (postscale) { dat_xy.x *= fscale; dat_xy.y *= fscale; } + } + + /* For some reason pj_errno does not work as expected in some */ + /* versions of Visual Studio, so using pj_get_errno_ref instead */ + if (*pj_get_errno_ref()) { + emess(-1, pj_strerrno(*pj_get_errno_ref())); + continue; + } + + if (!*s && (s > line)) --s; /* assumed we gobbled \n */ + coord.lp = dat_ll; + facs = proj_factors(Proj, coord); + if (proj_errno(Proj)) { + emess(-1,"failed to compute factors\n\n"); + continue; + } + + if (*s != '\n') + (void)fputs(s, stdout); + + (void)fputs("Longitude: ", stdout); + (void)fputs(proj_rtodms(pline, dat_ll.lam, 'E', 'W'), stdout); + (void)printf(" [ %.11g ]\n", dat_ll.lam * RAD_TO_DEG); + (void)fputs("Latitude: ", stdout); + (void)fputs(proj_rtodms(pline, dat_ll.phi, 'N', 'S'), stdout); + (void)printf(" [ %.11g ]\n", dat_ll.phi * RAD_TO_DEG); + (void)fputs("Easting (x): ", stdout); + (void)printf(oform, dat_xy.x); putchar('\n'); + (void)fputs("Northing (y): ", stdout); + (void)printf(oform, dat_xy.y); putchar('\n'); + (void)printf("Meridian scale (h) : %.8f ( %.4g %% error )\n", facs.meridional_scale, (facs.meridional_scale-1.)*100.); + (void)printf("Parallel scale (k) : %.8f ( %.4g %% error )\n", facs.parallel_scale, (facs.parallel_scale-1.)*100.); + (void)printf("Areal scale (s): %.8f ( %.4g %% error )\n", facs.areal_scale, (facs.areal_scale-1.)*100.); + (void)printf("Angular distortion (w): %.3f\n", facs.angular_distortion * RAD_TO_DEG); + (void)printf("Meridian/Parallel angle: %.5f\n", facs.meridian_parallel_angle * RAD_TO_DEG); + (void)printf("Convergence : "); + (void)fputs(proj_rtodms(pline, facs.meridian_convergence, 0, 0), stdout); + (void)printf(" [ %.8f ]\n", facs.meridian_convergence * RAD_TO_DEG); + (void)printf("Max-min (Tissot axis a-b) scale error: %.5f %.5f\n\n", facs.tissot_semimajor, facs.tissot_semiminor); + } +} + +int main(int argc, char **argv) { + char *arg, **eargv = argv, *pargv[MAX_PARGS], **iargv = argv; + FILE *fid; + int pargc = 0, iargc = argc, eargc = 0, mon = 0; + + if ( (emess_dat.Prog_name = strrchr(*argv,DIR_CHAR)) != NULL) + ++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++] = "-"; + break; + case 'b': /* binary I/O */ + bin_in = bin_out = 1; + continue; + case 'v': /* monitor dump of initialization */ + mon = 1; + continue; + case 'i': /* input binary */ + bin_in = 1; + continue; + case 'o': /* output binary */ + bin_out = 1; + continue; + case 'I': /* alt. method to spec inverse */ + inverse = 1; + continue; + case 'E': /* echo ascii input to ascii output */ + echoin = 1; + continue; + case 'V': /* very verbose processing mode */ + very_verby = 1; + mon = 1; + continue; + case 'S': /* compute scale factors */ + dofactors = 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) { + if( strcmp(lp->id,"latlong") == 0 + || strcmp(lp->id,"longlat") == 0 + || strcmp(lp->id,"geocent") == 0 ) + continue; + + (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 != NULL && strlen(ld->comments) > 0 ) + printf( "%25s %s\n", " ", ld->comments ); + } + } 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 'T': /* generate Chebyshev coefficients */ + if (--argc <= 0) goto noargument; + cheby_str = *++argv; + continue; + case 'm': /* cartesian multiplier */ + if (--argc <= 0) goto noargument; + postscale = 1; + if (!strncmp("1/",*++argv,2) || + !strncmp("1:",*argv,2)) { + if((fscale = atof((*argv)+2)) == 0.) + goto badscale; + fscale = 1. / fscale; + } else + if ((fscale = atof(*argv)) == 0.) { + badscale: + emess(1,"invalid scale argument"); + } + continue; + case 'W': /* specify seconds precision */ + case 'w': /* -W for constant field width */ + { + int 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 'd': + if (--argc <= 0) goto noargument; + sprintf(oform_buffer, "%%.%df", atoi(*++argv)); + oform = oform_buffer; + break; + case 'r': /* reverse input */ + reversein = 1; + continue; + case 's': /* reverse output */ + reverseout = 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; + } + if (eargc == 0 && !cheby_str) /* if no specific files force sysin */ + eargv[eargc++] = "-"; + else if (eargc > 0 && cheby_str) /* warning */ + emess(4, "data files when generating Chebychev prohibited"); + /* done with parameter and control input */ + if (inverse && postscale) { + prescale = 1; + postscale = 0; + fscale = 1./fscale; + } + if (!(Proj = pj_init(pargc, pargv))) + emess(3,"projection initialization failure\ncause: %s", + pj_strerrno(pj_errno)); + + if (!proj_angular_input(Proj, PJ_FWD)) { + emess(3, "can't initialize operations that take non-angular input coordinates"); + exit(0); + } + + if (proj_angular_output(Proj, PJ_FWD)) { + emess(3, "can't initialize operations that produce angular output coordinates"); + exit(0); + } + + if (inverse) { + if (!Proj->inv) + emess(3,"inverse projection not available"); + proj.inv = pj_inv; + } else + proj.fwd = pj_fwd; + if (cheby_str) { + gen_cheb(inverse, int_proj, cheby_str, Proj, iargc, iargv); + exit(0); + } + + /* set input formatting control */ + if (mon) { + pj_pr_list(Proj); + if (very_verby) { + (void)printf("#Final Earth figure: "); + if (Proj->es != 0.0) { + (void)printf("ellipsoid\n# Major axis (a): "); + (void)printf(oform ? oform : "%.3f", Proj->a); + (void)printf("\n# 1/flattening: %.6f\n", + 1./(1. - sqrt(1. - Proj->es))); + (void)printf("# squared eccentricity: %.12f\n", Proj->es); + } else { + (void)printf("sphere\n# Radius: "); + (void)printf(oform ? oform : "%.3f", Proj->a); + (void)putchar('\n'); + } + } + } + + if (inverse) + informat = strtod; + else { + informat = proj_dmstor; + if (!oform) + oform = "%.2f"; + } + + if (bin_out) { + SET_BINARY_MODE(stdout); + } + + /* process input file list */ + for ( ; eargc-- ; ++eargv) { + if (**eargv == '-') { + fid = stdin; + emess_dat.File_name = ""; + + if (bin_in) + { + SET_BINARY_MODE(stdin); + } + + } else { + if ((fid = fopen(*eargv, "rb")) == NULL) { + emess(-2, *eargv, "input file"); + continue; + } + emess_dat.File_name = *eargv; + } + emess_dat.File_line = 0; + if (very_verby) + vprocess(fid); + else + process(fid); + (void)fclose(fid); + emess_dat.File_name = 0; + } + + if( Proj ) + pj_free(Proj); + + exit(0); /* normal completion */ +} diff --git a/src/proj_4D_api.c b/src/proj_4D_api.c deleted file mode 100644 index 75a061a0..00000000 --- a/src/proj_4D_api.c +++ /dev/null @@ -1,1281 +0,0 @@ -/****************************************************************************** - * Project: PROJ.4 - * Purpose: Implement a (currently minimalistic) proj API based primarily - * on the PJ_COORD 4D geodetic spatiotemporal data type. - * - * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 - * - ****************************************************************************** - * Copyright (c) 2016, 2017 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. - *****************************************************************************/ - -#include -#include -#include -#include -#include -#ifndef _MSC_VER -#include -#endif - -#include "proj.h" -#include "proj_internal.h" -#include "proj_math.h" -#include "projects.h" -#include "geodesic.h" - - -/* Initialize PJ_COORD struct */ -PJ_COORD proj_coord (double x, double y, double z, double t) { - PJ_COORD res; - res.v[0] = x; - res.v[1] = y; - res.v[2] = z; - res.v[3] = t; - return res; -} - -/*****************************************************************************/ -int proj_angular_input (PJ *P, enum PJ_DIRECTION dir) { -/****************************************************************************** - Returns 1 if the operator P expects angular input coordinates when - operating in direction dir, 0 otherwise. - dir: {PJ_FWD, PJ_INV} -******************************************************************************/ - if (PJ_FWD==dir) - return pj_left (P)==PJ_IO_UNITS_ANGULAR; - return pj_right (P)==PJ_IO_UNITS_ANGULAR; -} - -/*****************************************************************************/ -int proj_angular_output (PJ *P, enum PJ_DIRECTION dir) { -/****************************************************************************** - Returns 1 if the operator P provides angular output coordinates when - operating in direction dir, 0 otherwise. - dir: {PJ_FWD, PJ_INV} -******************************************************************************/ - return proj_angular_input (P, -dir); -} - - -/* Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid */ -PJ_COORD proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b) { - PJ_COORD c; - /* Note: the geodesic code takes arguments in degrees */ - geod_inverse (P->geod, - PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), - PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), - c.v, c.v+1, c.v+2 - ); - - return c; -} - - -/* Geodesic distance (in meter) between two points with angular 2D coordinates */ -double proj_lp_dist (const PJ *P, PJ_COORD a, PJ_COORD b) { - double s12, azi1, azi2; - /* Note: the geodesic code takes arguments in degrees */ - geod_inverse (P->geod, - PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), - PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), - &s12, &azi1, &azi2 - ); - return s12; -} - -/* The geodesic distance AND the vertical offset */ -double proj_lpz_dist (const PJ *P, PJ_COORD a, PJ_COORD b) { - if (HUGE_VAL==a.lpz.lam || HUGE_VAL==b.lpz.lam) - return HUGE_VAL; - return hypot (proj_lp_dist (P, a, b), a.lpz.z - b.lpz.z); -} - -/* Euclidean distance between two points with linear 2D coordinates */ -double proj_xy_dist (PJ_COORD a, PJ_COORD b) { - return hypot (a.xy.x - b.xy.x, a.xy.y - b.xy.y); -} - -/* Euclidean distance between two points with linear 3D coordinates */ -double proj_xyz_dist (PJ_COORD a, PJ_COORD b) { - return hypot (proj_xy_dist (a, b), a.xyz.z - b.xyz.z); -} - - - -/* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */ -double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { - int i; - PJ_COORD t, org; - - if (0==P) - return HUGE_VAL; - - if (n < 1) { - proj_errno_set (P, EINVAL); - return HUGE_VAL; - } - - /* in the first half-step, we generate the output value */ - org = *coord; - *coord = proj_trans (P, direction, org); - t = *coord; - - /* now we take n-1 full steps in inverse direction: We are */ - /* out of phase due to the half step already taken */ - for (i = 0; i < n - 1; i++) - t = proj_trans (P, direction, proj_trans (P, -direction, t) ); - - /* finally, we take the last half-step */ - t = proj_trans (P, -direction, t); - - /* checking for angular *input* since we do a roundtrip, and end where we begin */ - if (proj_angular_input (P, direction)) - return proj_lpz_dist (P, org, t); - - return proj_xyz_dist (org, t); -} - - - -/**************************************************************************************/ -PJ_COORD proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { -/*************************************************************************************** -Apply the transformation P to the coordinate coord, preferring the 4D interfaces if -available. - -See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which work -similarly, but prefers the 2D resp. 3D interfaces if available. -***************************************************************************************/ - if (0==P) - return coord; - if (P->inverted) - direction = -direction; - - switch (direction) { - case PJ_FWD: - return pj_fwd4d (coord, P); - case PJ_INV: - return pj_inv4d (coord, P); - case PJ_IDENT: - return coord; - default: - break; - } - - proj_errno_set (P, EINVAL); - return proj_coord_error (); -} - - - -/*****************************************************************************/ -int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) { -/****************************************************************************** - Batch transform an array of PJ_COORD. - - Returns 0 if all coordinates are transformed without error, otherwise - returns error number. -******************************************************************************/ - size_t i; - - for (i = 0; i < n; i++) { - coord[i] = proj_trans (P, direction, coord[i]); - if (proj_errno(P)) - return proj_errno (P); - } - - return 0; -} - - - -/*************************************************************************************/ -size_t proj_trans_generic ( - PJ *P, - PJ_DIRECTION direction, - double *x, size_t sx, size_t nx, - double *y, size_t sy, size_t ny, - double *z, size_t sz, size_t nz, - double *t, size_t st, size_t nt -) { -/************************************************************************************** - - Transform a series of coordinates, where the individual coordinate dimension - may be represented by an array that is either - - 1. fully populated - 2. a null pointer and/or a length of zero, which will be treated as a - fully populated array of zeroes - 3. of length one, i.e. a constant, which will be treated as a fully - populated array of that constant value - - The strides, sx, sy, sz, st, represent the step length, in bytes, between - consecutive elements of the corresponding array. This makes it possible for - proj_transform to handle transformation of a large class of application - specific data structures, without necessarily understanding the data structure - format, as in: - - typedef struct {double x, y; int quality_level; char surveyor_name[134];} XYQS; - XYQS survey[345]; - double height = 23.45; - PJ *P = {...}; - size_t stride = sizeof (XYQS); - ... - proj_transform ( - P, PJ_INV, sizeof(XYQS), - &(survey[0].x), stride, 345, (* We have 345 eastings *) - &(survey[0].y), stride, 345, (* ...and 345 northings. *) - &height, 1, (* The height is the constant 23.45 m *) - 0, 0 (* and the time is the constant 0.00 s *) - ); - - This is similar to the inner workings of the pj_transform function, but the - stride functionality has been generalized to work for any size of basic unit, - not just a fixed number of doubles. - - In most cases, the stride will be identical for x, y,z, and t, since they will - typically be either individual arrays (stride = sizeof(double)), or strided - views into an array of application specific data structures (stride = sizeof (...)). - - But in order to support cases where x, y, z, and t come from heterogeneous - sources, individual strides, sx, sy, sz, st, are used. - - Caveat: Since proj_transform does its work *in place*, this means that even the - supposedly constants (i.e. length 1 arrays) will return from the call in altered - state. Hence, remember to reinitialize between repeated calls. - - Return value: Number of transformations completed. - -**************************************************************************************/ - PJ_COORD coord = {{0,0,0,0}}; - size_t i, nmin; - double null_broadcast = 0; - - if (0==P) - return 0; - - if (P->inverted) - direction = -direction; - - /* ignore lengths of null arrays */ - if (0==x) nx = 0; - if (0==y) ny = 0; - if (0==z) nz = 0; - if (0==t) nt = 0; - - /* and make the nullities point to some real world memory for broadcasting nulls */ - if (0==nx) x = &null_broadcast; - if (0==ny) y = &null_broadcast; - if (0==nz) z = &null_broadcast; - if (0==nt) t = &null_broadcast; - - /* nothing to do? */ - if (0==nx+ny+nz+nt) - return 0; - - /* arrays of length 1 are constants, which we broadcast along the longer arrays */ - /* so we need to find the length of the shortest non-unity array to figure out */ - /* how many coordinate pairs we must transform */ - nmin = (nx > 1)? nx: (ny > 1)? ny: (nz > 1)? nz: (nt > 1)? nt: 1; - if ((nx > 1) && (nx < nmin)) nmin = nx; - if ((ny > 1) && (ny < nmin)) nmin = ny; - if ((nz > 1) && (nz < nmin)) nmin = nz; - if ((nt > 1) && (nt < nmin)) nmin = nt; - - /* Check validity of direction flag */ - switch (direction) { - case PJ_FWD: - case PJ_INV: - break; - case PJ_IDENT: - return nmin; - default: - proj_errno_set (P, EINVAL); - return 0; - } - - /* Arrays of length==0 are broadcast as the constant 0 */ - /* Arrays of length==1 are broadcast as their single value */ - /* Arrays of length >1 are iterated over (for the first nmin values) */ - /* The slightly convolved incremental indexing is used due */ - /* to the stride, which may be any size supported by the platform */ - for (i = 0; i < nmin; i++) { - coord.xyzt.x = *x; - coord.xyzt.y = *y; - coord.xyzt.z = *z; - coord.xyzt.t = *t; - - if (PJ_FWD==direction) - coord = pj_fwd4d (coord, P); - else - coord = pj_inv4d (coord, P); - - /* in all full length cases, we overwrite the input with the output, */ - /* and step on to the next element. */ - /* The casts are somewhat funky, but they compile down to no-ops and */ - /* they tell compilers and static analyzers that we know what we do */ - if (nx > 1) { - *x = coord.xyzt.x; - x = (double *) ((void *) ( ((char *) x) + sx)); - } - if (ny > 1) { - *y = coord.xyzt.y; - y = (double *) ((void *) ( ((char *) y) + sy)); - } - if (nz > 1) { - *z = coord.xyzt.z; - z = (double *) ((void *) ( ((char *) z) + sz)); - } - if (nt > 1) { - *t = coord.xyzt.t; - t = (double *) ((void *) ( ((char *) t) + st)); - } - } - - /* Last time around, we update the length 1 cases with their transformed alter egos */ - if (nx==1) - *x = coord.xyzt.x; - if (ny==1) - *y = coord.xyzt.y; - if (nz==1) - *z = coord.xyzt.z; - if (nt==1) - *t = coord.xyzt.t; - - return i; -} - - -/*************************************************************************************/ -PJ_COORD pj_geocentric_latitude (const PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { -/************************************************************************************** - Convert geographical latitude to geocentric (or the other way round if - direction = PJ_INV) - - The conversion involves a call to the tangent function, which goes through the - roof at the poles, so very close (the last centimeter) to the poles no - conversion takes place and the input latitude is copied directly to the output. - - Fortunately, the geocentric latitude converges to the geographical at the - poles, so the difference is negligible. - - For the spherical case, the geographical latitude equals the geocentric, and - consequently, the input is copied directly to the output. -**************************************************************************************/ - const double limit = M_HALFPI - 1e-9; - PJ_COORD res = coord; - if ((coord.lp.phi > limit) || (coord.lp.phi < -limit) || (P->es==0)) - return res; - if (direction==PJ_FWD) - res.lp.phi = atan (P->one_es * tan (coord.lp.phi) ); - else - res.lp.phi = atan (P->rone_es * tan (coord.lp.phi) ); - - return res; -} - -double proj_torad (double angle_in_degrees) { return PJ_TORAD (angle_in_degrees);} -double proj_todeg (double angle_in_radians) { return PJ_TODEG (angle_in_radians);} - -double proj_dmstor(const char *is, char **rs) { - return dmstor(is, rs); -} - -char* proj_rtodms(char *s, double r, int pos, int neg) { - return rtodms(s, r, pos, neg); -} - -/*************************************************************************************/ -static PJ* skip_prep_fin(PJ *P) { -/************************************************************************************** -Skip prepare and finalize function for the various "helper operations" added to P when -in cs2cs compatibility mode. -**************************************************************************************/ - P->skip_fwd_prepare = 1; - P->skip_fwd_finalize = 1; - P->skip_inv_prepare = 1; - P->skip_inv_finalize = 1; - return P; -} - -/*************************************************************************************/ -static int cs2cs_emulation_setup (PJ *P) { -/************************************************************************************** -If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the 4D API -equivalent operations, so the preparation and finalization steps in the pj_inv/pj_fwd -invocators can emulate the behaviour of pj_transform and the cs2cs app. - -Returns 1 on success, 0 on failure -**************************************************************************************/ - PJ *Q; - paralist *p; - int do_cart = 0; - if (0==P) - return 0; - - /* Don't recurse when calling proj_create (which calls us back) */ - if (pj_param_exists (P->params, "break_cs2cs_recursion")) - return 1; - - /* Swap axes? */ - p = pj_param_exists (P->params, "axis"); - - /* Don't axisswap if data are already in "enu" order */ - if (p && (0!=strcmp ("enu", p->param))) { - char *def = malloc (100+strlen(P->axis)); - if (0==def) - return 0; - sprintf (def, "break_cs2cs_recursion proj=axisswap axis=%s", P->axis); - Q = proj_create (P->ctx, def); - free (def); - if (0==Q) - return 0; - P->axisswap = skip_prep_fin(Q); - } - - /* Geoid grid(s) given? */ - p = pj_param_exists (P->params, "geoidgrids"); - if (p && strlen (p->param) > strlen ("geoidgrids=")) { - char *gridnames = p->param + strlen ("geoidgrids="); - char *def = malloc (100+strlen(gridnames)); - if (0==def) - return 0; - sprintf (def, "break_cs2cs_recursion proj=vgridshift grids=%s", gridnames); - Q = proj_create (P->ctx, def); - free (def); - if (0==Q) - return 0; - P->vgridshift = skip_prep_fin(Q); - } - - /* Datum shift grid(s) given? */ - p = pj_param_exists (P->params, "nadgrids"); - if (p && strlen (p->param) > strlen ("nadgrids=")) { - char *gridnames = p->param + strlen ("nadgrids="); - char *def = malloc (100+strlen(gridnames)); - if (0==def) - return 0; - sprintf (def, "break_cs2cs_recursion proj=hgridshift grids=%s", gridnames); - Q = proj_create (P->ctx, def); - free (def); - if (0==Q) - return 0; - P->hgridshift = skip_prep_fin(Q); - } - - /* We ignore helmert if we have grid shift */ - p = P->hgridshift ? 0 : pj_param_exists (P->params, "towgs84"); - while (p) { - char *def; - char *s = p->param; - double *d = P->datum_params; - size_t n = strlen (s); - - /* We ignore null helmert shifts (common in auto-translated resource files, e.g. epsg) */ - if (0==d[0] && 0==d[1] && 0==d[2] && 0==d[3] && 0==d[4] && 0==d[5] && 0==d[6]) { - /* If the current ellipsoid is not WGS84, then make sure the */ - /* change in ellipsoid is still done. */ - if (!(fabs(P->a_orig - 6378137.0) < 1e-8 && fabs(P->es_orig - 0.0066943799901413) < 1e-15)) { - do_cart = 1; - } - break; - } - - if (n <= 8) /* 8==strlen ("towgs84=") */ - return 0; - - def = malloc (100+n); - if (0==def) - return 0; - sprintf (def, "break_cs2cs_recursion proj=helmert exact %s convention=position_vector", s); - Q = proj_create (P->ctx, def); - free(def); - if (0==Q) - return 0; - pj_inherit_ellipsoid_def (P, Q); - P->helmert = skip_prep_fin (Q); - - break; - } - - /* We also need cartesian/geographical transformations if we are working in */ - /* geocentric/cartesian space or we need to do a Helmert transform. */ - if (P->is_geocent || P->helmert || do_cart) { - char def[150]; - sprintf (def, "break_cs2cs_recursion proj=cart a=%40.20g es=%40.20g", P->a_orig, P->es_orig); - { - /* In case the current locale does not use dot but comma as decimal */ - /* separator, replace it with dot, so that proj_atof() behaves */ - /* correctly. */ - /* TODO later: use C++ ostringstream with imbue(std::locale::classic()) */ - /* to be locale unaware */ - char* next_pos; - for (next_pos = def; (next_pos = strchr (next_pos, ',')) != NULL; next_pos++) { - *next_pos = '.'; - } - } - Q = proj_create (P->ctx, def); - if (0==Q) - return 0; - P->cart = skip_prep_fin (Q); - - if (!P->is_geocent) { - sprintf (def, "break_cs2cs_recursion proj=cart ellps=WGS84"); - Q = proj_create (P->ctx, def); - if (0==Q) - return 0; - P->cart_wgs84 = skip_prep_fin (Q); - } - } - - return 1; -} - - - -/*************************************************************************************/ -PJ *proj_create (PJ_CONTEXT *ctx, const char *definition) { -/************************************************************************************** - Create a new PJ object in the context ctx, using the given definition. If ctx==0, - the default context is used, if definition==0, or invalid, a null-pointer is - returned. The definition may use '+' as argument start indicator, as in - "+proj=utm +zone=32", or leave it out, as in "proj=utm zone=32". - - It may even use free formatting "proj = utm; zone =32 ellps= GRS80". - Note that the semicolon separator is allowed, but not required. -**************************************************************************************/ - PJ *P; - char *args, **argv; - size_t argc, n; - int ret; - int allow_init_epsg; - - if (0==ctx) - ctx = pj_get_default_ctx (); - - /* Make a copy that we can manipulate */ - n = strlen (definition); - args = (char *) malloc (n + 1); - if (0==args) { - proj_context_errno_set(ctx, ENOMEM); - return 0; - } - strcpy (args, definition); - - argc = pj_trim_argc (args); - if (argc==0) { - pj_dealloc (args); - proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); - return 0; - } - - argv = pj_trim_argv (argc, args); - - /* ...and let pj_init_ctx do the hard work */ - /* New interface: forbid init=epsg:XXXX syntax by default */ - allow_init_epsg = proj_context_get_use_proj4_init_rules(ctx, FALSE); - P = pj_init_ctx_with_allow_init_epsg (ctx, (int) argc, argv, allow_init_epsg); - - pj_dealloc (argv); - pj_dealloc (args); - - /* Support cs2cs-style modifiers */ - ret = cs2cs_emulation_setup (P); - if (0==ret) - return proj_destroy (P); - - return P; -} - - - -/*************************************************************************************/ -PJ *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv) { -/************************************************************************************** -Create a new PJ object in the context ctx, using the given definition argument -array argv. If ctx==0, the default context is used, if definition==0, or invalid, -a null-pointer is returned. The definition arguments may use '+' as argument start -indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm", -"zone=32"}. -**************************************************************************************/ - PJ *P; - const char *c; - - if (0==ctx) - ctx = pj_get_default_ctx (); - if (0==argv) { - proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); - return 0; - } - - /* We assume that free format is used, and build a full proj_create compatible string */ - c = pj_make_args (argc, argv); - if (0==c) { - proj_context_errno_set(ctx, ENOMEM); - return 0; - } - - P = proj_create (ctx, c); - - pj_dealloc ((char *) c); - return P; -} - -/** Create an area of use */ -PJ_AREA * proj_area_create(void) { - return pj_calloc(1, sizeof(PJ_AREA)); -} - -/** Assign a bounding box to an area of use. */ -void proj_area_set_bbox(PJ_AREA *area, - double west_lon_degree, - double south_lat_degree, - double east_lon_degree, - double north_lat_degree) { - area->bbox_set = TRUE; - area->west_lon_degree = west_lon_degree; - area->south_lat_degree = south_lat_degree; - area->east_lon_degree = east_lon_degree; - area->north_lat_degree = north_lat_degree; -} - -/** Free an area of use */ -void proj_area_destroy(PJ_AREA* area) { - pj_dealloc(area); -} - -/************************************************************************/ -/* proj_context_use_proj4_init_rules() */ -/************************************************************************/ - -void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) { - if( ctx == NULL ) { - ctx = pj_get_default_ctx(); - } - ctx->use_proj4_init_rules = enable; -} - -/************************************************************************/ -/* EQUAL() */ -/************************************************************************/ - -static int EQUAL(const char* a, const char* b) { -#ifdef _MSC_VER - return _stricmp(a, b) == 0; -#else - return strcasecmp(a, b) == 0; -#endif -} - -/************************************************************************/ -/* proj_context_get_use_proj4_init_rules() */ -/************************************************************************/ - -int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path) { - const char* val = getenv("PROJ_USE_PROJ4_INIT_RULES"); - - if( ctx == NULL ) { - ctx = pj_get_default_ctx(); - } - - if( val ) { - if( EQUAL(val, "yes") || EQUAL(val, "on") || EQUAL(val, "true") ) { - return TRUE; - } - if( EQUAL(val, "no") || EQUAL(val, "off") || EQUAL(val, "false") ) { - return FALSE; - } - pj_log(ctx, PJ_LOG_ERROR, "Invalid value for PROJ_USE_PROJ4_INIT_RULES"); - } - - if( ctx->use_proj4_init_rules >= 0 ) { - return ctx->use_proj4_init_rules; - } - return from_legacy_code_path; -} - - -/*****************************************************************************/ -PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char *target_crs, PJ_AREA *area) { -/****************************************************************************** - Create a transformation pipeline between two known coordinate reference - systems. - - source_crs and target_crs can be : - - a "AUTHORITY:CODE", like EPSG:25832. When using that syntax for a source - CRS, the created pipeline will expect that the values passed to proj_trans() - respect the axis order and axis unit of the official definition ( - so for example, for EPSG:4326, with latitude first and longitude next, - in degrees). Similarly, when using that syntax for a target CRS, output - values will be emitted according to the official definition of this CRS. - - a PROJ string, like "+proj=longlat +datum=WGS84". - When using that syntax, the axis order and unit for geographic CRS will - be longitude, latitude, and the unit degrees. - - more generally any string accepted by proj_obj_create_from_user_input() - - An "area of use" can be specified in area. When it is supplied, the more - accurate transformation between two given systems can be chosen. - - Example call: - - PJ *P = proj_create_crs_to_crs(0, "EPSG:25832", "EPSG:25833", NULL); - -******************************************************************************/ - PJ *P; - PJ_OBJ* src; - PJ_OBJ* dst; - PJ_OPERATION_FACTORY_CONTEXT* operation_ctx; - PJ_OBJ_LIST* op_list; - PJ_OBJ* op; - const char* proj_string; - const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; - const char* const* optionsImportCRS = - proj_context_get_use_proj4_init_rules(ctx, FALSE) ? optionsProj4Mode : NULL; - - src = proj_obj_create_from_user_input(ctx, source_crs, optionsImportCRS); - if( !src ) { - return NULL; - } - - dst = proj_obj_create_from_user_input(ctx, target_crs, optionsImportCRS); - if( !dst ) { - proj_obj_destroy(src); - return NULL; - } - - operation_ctx = proj_create_operation_factory_context(ctx, NULL); - if( !operation_ctx ) { - proj_obj_destroy(src); - proj_obj_destroy(dst); - return NULL; - } - - if( area && area->bbox_set ) { - proj_operation_factory_context_set_area_of_interest( - ctx, - operation_ctx, - area->west_lon_degree, - area->south_lat_degree, - area->east_lon_degree, - area->north_lat_degree); - } - - proj_operation_factory_context_set_grid_availability_use( - ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); - - op_list = proj_obj_create_operations(ctx, src, dst, operation_ctx); - - proj_operation_factory_context_destroy(operation_ctx); - proj_obj_destroy(src); - proj_obj_destroy(dst); - - if( !op_list ) { - return NULL; - } - - if( proj_obj_list_get_count(op_list) == 0 ) { - proj_obj_list_destroy(op_list); - return NULL; - } - - op = proj_obj_list_get(ctx, op_list, 0); - proj_obj_list_destroy(op_list); - if( !op ) { - return NULL; - } - - proj_string = proj_obj_as_proj_string(ctx, op, PJ_PROJ_5, NULL); - if( !proj_string) { - proj_obj_destroy(op); - return NULL; - } - - if( proj_string[0] == '\0' ) { - /* Null transform ? */ - P = proj_create(ctx, "proj=affine"); - } else { - P = proj_create(ctx, proj_string); - } - - proj_obj_destroy(op); - - return P; -} - -PJ *proj_destroy (PJ *P) { - pj_free (P); - return 0; -} - -/*****************************************************************************/ -int proj_errno (const PJ *P) { -/****************************************************************************** - Read an error level from the context of a PJ. -******************************************************************************/ - return pj_ctx_get_errno (pj_get_ctx ((PJ *) P)); -} - -/*****************************************************************************/ -int proj_context_errno (PJ_CONTEXT *ctx) { -/****************************************************************************** - Read an error directly from a context, without going through a PJ - belonging to that context. -******************************************************************************/ - if (0==ctx) - ctx = pj_get_default_ctx(); - return pj_ctx_get_errno (ctx); -} - -/*****************************************************************************/ -int proj_errno_set (const PJ *P, int err) { -/****************************************************************************** - Set context-errno, bubble it up to the thread local errno, return err -******************************************************************************/ - /* Use proj_errno_reset to explicitly clear the error status */ - if (0==err) - return 0; - - /* For P==0 err goes to the default context */ - proj_context_errno_set (pj_get_ctx ((PJ *) P), err); - errno = err; - return err; -} - -/*****************************************************************************/ -int proj_errno_restore (const PJ *P, int err) { -/****************************************************************************** - Use proj_errno_restore when the current function succeeds, but the - error flag was set on entry, and stored/reset using proj_errno_reset - in order to monitor for new errors. - - See usage example under proj_errno_reset () -******************************************************************************/ - if (0==err) - return 0; - proj_errno_set (P, err); - return 0; -} - -/*****************************************************************************/ -int proj_errno_reset (const PJ *P) { -/****************************************************************************** - Clears errno in the context and thread local levels - through the low level pj_ctx interface. - - Returns the previous value of the errno, for convenient reset/restore - operations: - - int foo (PJ *P) { - // errno may be set on entry, but we need to reset it to be able to - // check for errors from "do_something_with_P(P)" - int last_errno = proj_errno_reset (P); - - // local failure - if (0==P) - return proj_errno_set (P, 42); - - // call to function that may fail - do_something_with_P (P); - - // failure in do_something_with_P? - keep latest error status - if (proj_errno(P)) - return proj_errno (P); - - // success - restore previous error status, return 0 - return proj_errno_restore (P, last_errno); - } -******************************************************************************/ - int last_errno; - last_errno = proj_errno (P); - - pj_ctx_set_errno (pj_get_ctx ((PJ *) P), 0); - errno = 0; - pj_errno = 0; - return last_errno; -} - - -/* Create a new context */ -PJ_CONTEXT *proj_context_create (void) { - return pj_ctx_alloc (); -} - - -PJ_CONTEXT *proj_context_destroy (PJ_CONTEXT *ctx) { - if (0==ctx) - return 0; - - /* Trying to free the default context is a no-op (since it is statically allocated) */ - if (pj_get_default_ctx ()==ctx) - return 0; - - pj_ctx_free (ctx); - return 0; -} - - - - - - -/*****************************************************************************/ -static char *path_append (char *buf, const char *app, size_t *buf_size) { -/****************************************************************************** - Helper for proj_info() below. Append app to buf, separated by a - semicolon. Also handle allocation of longer buffer if needed. - - Returns buffer and adjusts *buf_size through provided pointer arg. -******************************************************************************/ - char *p; - size_t len, applen = 0, buflen = 0; -#ifdef _WIN32 - char *delim = ";"; -#else - char *delim = ":"; -#endif - - /* Nothing to do? */ - if (0 == app) - return buf; - applen = strlen (app); - if (0 == applen) - return buf; - - /* Start checking whether buf is long enough */ - if (0 != buf) - buflen = strlen (buf); - len = buflen+applen+strlen (delim) + 1; - - /* "pj_realloc", so to speak */ - if (*buf_size < len) { - p = pj_calloc (2 * len, sizeof (char)); - if (0==p) { - pj_dealloc (buf); - return 0; - } - *buf_size = 2 * len; - if (buf != 0) - strcpy (p, buf); - pj_dealloc (buf); - buf = p; - } - - /* Only append a semicolon if something's already there */ - if (0 != buflen) - strcat (buf, ";"); - strcat (buf, app); - return buf; -} - -static const char *empty = {""}; -static char version[64] = {""}; -static PJ_INFO info = {0, 0, 0, 0, 0, 0, 0, 0}; -static volatile int info_initialized = 0; - -/*****************************************************************************/ -PJ_INFO proj_info (void) { -/****************************************************************************** - Basic info about the current instance of the PROJ.4 library. - - Returns PJ_INFO struct. -******************************************************************************/ - const char * const *paths; - size_t i, n; - - size_t buf_size = 0; - char *buf = 0; - - pj_acquire_lock (); - - if (0!=info_initialized) { - pj_release_lock (); - return info; - } - - info.major = PROJ_VERSION_MAJOR; - info.minor = PROJ_VERSION_MINOR; - info.patch = PROJ_VERSION_PATCH; - - /* This is a controlled environment, so no risk of sprintf buffer - overflow. A normal version string is xx.yy.zz which is 8 characters - long and there is room for 64 bytes in the version string. */ - sprintf (version, "%d.%d.%d", info.major, info.minor, info.patch); - - info.searchpath = empty; - info.version = version; - info.release = pj_get_release (); - - /* build search path string */ - buf = path_append (buf, getenv ("HOME"), &buf_size); - buf = path_append (buf, getenv ("PROJ_LIB"), &buf_size); - - paths = proj_get_searchpath (); - n = (size_t) proj_get_path_count (); - - for (i = 0; i < n; i++) - buf = path_append (buf, paths[i], &buf_size); - info.searchpath = buf ? buf : empty; - - info.paths = paths; - info.path_count = n; - - info_initialized = 1; - pj_release_lock (); - return info; -} - - -/*****************************************************************************/ -PJ_PROJ_INFO proj_pj_info(PJ *P) { -/****************************************************************************** - Basic info about a particular instance of a projection object. - - Returns PJ_PROJ_INFO struct. -******************************************************************************/ - PJ_PROJ_INFO pjinfo; - char *def; - - memset(&pjinfo, 0, sizeof(PJ_PROJ_INFO)); - - /* Expected accuracy of the transformation. Hardcoded for now, will be improved */ - /* later. Most likely to be used when a transformation is set up with */ - /* proj_create_crs_to_crs in a future version that leverages the EPSG database. */ - pjinfo.accuracy = -1.0; - - if (0==P) - return pjinfo; - - /* projection id */ - if (pj_param(P->ctx, P->params, "tproj").i) - pjinfo.id = pj_param(P->ctx, P->params, "sproj").s; - - /* projection description */ - pjinfo.description = P->descr; - - /* projection definition */ - if (P->def_full) - def = P->def_full; - else - def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */ - if (0==def) - pjinfo.definition = empty; - else - pjinfo.definition = pj_shrink (def); - /* Make pj_free clean this up eventually */ - P->def_full = def; - - pjinfo.has_inverse = pj_has_inverse(P); - return pjinfo; -} - - -/*****************************************************************************/ -PJ_GRID_INFO proj_grid_info(const char *gridname) { -/****************************************************************************** - Information about a named datum grid. - - Returns PJ_GRID_INFO struct. -******************************************************************************/ - PJ_GRID_INFO grinfo; - - /*PJ_CONTEXT *ctx = proj_context_create(); */ - PJ_CONTEXT *ctx = pj_get_default_ctx(); - PJ_GRIDINFO *gridinfo = pj_gridinfo_init(ctx, gridname); - memset(&grinfo, 0, sizeof(PJ_GRID_INFO)); - - /* in case the grid wasn't found */ - if (gridinfo->filename == NULL) { - pj_gridinfo_free(ctx, gridinfo); - strcpy(grinfo.format, "missing"); - return grinfo; - } - - /* The string copies below are automatically null-terminated due to */ - /* the memset above, so strncpy is safe */ - - /* name of grid */ - strncpy (grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1); - - /* full path of grid */ - pj_find_file(ctx, gridname, grinfo.filename, sizeof(grinfo.filename) - 1); - - /* grid format */ - strncpy (grinfo.format, gridinfo->format, sizeof(grinfo.format) - 1); - - /* grid size */ - grinfo.n_lon = gridinfo->ct->lim.lam; - grinfo.n_lat = gridinfo->ct->lim.phi; - - /* cell size */ - grinfo.cs_lon = gridinfo->ct->del.lam; - grinfo.cs_lat = gridinfo->ct->del.phi; - - /* bounds of grid */ - grinfo.lowerleft = gridinfo->ct->ll; - grinfo.upperright.lam = grinfo.lowerleft.lam + grinfo.n_lon*grinfo.cs_lon; - grinfo.upperright.phi = grinfo.lowerleft.phi + grinfo.n_lat*grinfo.cs_lat; - - pj_gridinfo_free(ctx, gridinfo); - - return grinfo; -} - - - -/*****************************************************************************/ -PJ_INIT_INFO proj_init_info(const char *initname){ -/****************************************************************************** - Information about a named init file. - - Maximum length of initname is 64. - - Returns PJ_INIT_INFO struct. - - If the init file is not found all members of the return struct are set - to the empty string. - - If the init file is found, but the metadata is missing, the value is - set to "Unknown". -******************************************************************************/ - int file_found; - char param[80], key[74]; - paralist *start, *next; - PJ_INIT_INFO ininfo; - PJ_CONTEXT *ctx = pj_get_default_ctx(); - - memset(&ininfo, 0, sizeof(PJ_INIT_INFO)); - - file_found = pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename)); - if (!file_found || strlen(initname) > 64) { - if( strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0 ) { - const char* val; - - pj_ctx_set_errno( ctx, 0 ); - - strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); - strcpy(ininfo.origin, "EPSG"); - val = proj_context_get_database_metadata(ctx, "EPSG.VERSION"); - if( val ) { - strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); - } - val = proj_context_get_database_metadata(ctx, "EPSG.DATE"); - if( val ) { - strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); - } - return ininfo; - } - - if( strcmp(initname, "IGNF") == 0 ) { - const char* val; - - pj_ctx_set_errno( ctx, 0 ); - - strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); - strcpy(ininfo.origin, "IGNF"); - val = proj_context_get_database_metadata(ctx, "IGNF.VERSION"); - if( val ) { - strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); - } - val = proj_context_get_database_metadata(ctx, "IGNF.DATE"); - if( val ) { - strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); - } - return ininfo; - } - - return ininfo; - } - - /* The initial memset (0) makes strncpy safe here */ - strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); - strcpy(ininfo.origin, "Unknown"); - strcpy(ininfo.version, "Unknown"); - strcpy(ininfo.lastupdate, "Unknown"); - - strncpy (key, initname, 64); /* make room for ":metadata\0" at the end */ - key[64] = 0; - memcpy(key + strlen(key), ":metadata", 9 + 1); - strcpy(param, "+init="); - /* The +strlen(param) avoids a cppcheck false positive warning */ - strncat(param + strlen(param), key, sizeof(param)-1-strlen(param)); - - start = pj_mkparam(param); - pj_expand_init(ctx, start); - - if (pj_param(ctx, start, "tversion").i) - strncpy(ininfo.version, pj_param(ctx, start, "sversion").s, sizeof(ininfo.version) - 1); - - if (pj_param(ctx, start, "torigin").i) - strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s, sizeof(ininfo.origin) - 1); - - if (pj_param(ctx, start, "tlastupdate").i) - strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s, sizeof(ininfo.lastupdate) - 1); - - for ( ; start; start = next) { - next = start->next; - pj_dalloc(start); - } - - return ininfo; -} - - - -/*****************************************************************************/ -PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { -/****************************************************************************** - Cartographic characteristics at point lp. - - Characteristics include meridian, parallel and areal scales, angular - distortion, meridian/parallel, meridian convergence and scale error. - - returns PJ_FACTORS. If unsuccessful, error number is set and the - struct returned contains NULL data. -******************************************************************************/ - PJ_FACTORS factors = {0,0,0, 0,0,0, 0,0, 0,0,0,0}; - struct FACTORS f; - - if (0==P) - return factors; - - if (pj_factors(lp.lp, P, 0.0, &f)) - return factors; - - factors.meridional_scale = f.h; - factors.parallel_scale = f.k; - factors.areal_scale = f.s; - - factors.angular_distortion = f.omega; - factors.meridian_parallel_angle = f.thetap; - factors.meridian_convergence = f.conv; - - factors.tissot_semimajor = f.a; - factors.tissot_semiminor = f.b; - - /* Raw derivatives, for completeness's sake */ - factors.dx_dlam = f.der.x_l; - factors.dx_dphi = f.der.x_p; - factors.dy_dlam = f.der.y_l; - factors.dy_dphi = f.der.y_p; - - return factors; -} diff --git a/src/proj_4D_api.cpp b/src/proj_4D_api.cpp new file mode 100644 index 00000000..c2a37a49 --- /dev/null +++ b/src/proj_4D_api.cpp @@ -0,0 +1,1285 @@ +/****************************************************************************** + * Project: PROJ.4 + * Purpose: Implement a (currently minimalistic) proj API based primarily + * on the PJ_COORD 4D geodetic spatiotemporal data type. + * + * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 + * + ****************************************************************************** + * Copyright (c) 2016, 2017 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif + +#include "proj.h" +#include "proj_internal.h" +#include "proj_math.h" +#include "projects.h" +#include "geodesic.h" + + +/* Initialize PJ_COORD struct */ +PJ_COORD proj_coord (double x, double y, double z, double t) { + PJ_COORD res; + res.v[0] = x; + res.v[1] = y; + res.v[2] = z; + res.v[3] = t; + return res; +} + +static PJ_DIRECTION opposite_direction(PJ_DIRECTION dir) { + return static_cast(-dir); +} + +/*****************************************************************************/ +int proj_angular_input (PJ *P, enum PJ_DIRECTION dir) { +/****************************************************************************** + Returns 1 if the operator P expects angular input coordinates when + operating in direction dir, 0 otherwise. + dir: {PJ_FWD, PJ_INV} +******************************************************************************/ + if (PJ_FWD==dir) + return pj_left (P)==PJ_IO_UNITS_ANGULAR; + return pj_right (P)==PJ_IO_UNITS_ANGULAR; +} + +/*****************************************************************************/ +int proj_angular_output (PJ *P, enum PJ_DIRECTION dir) { +/****************************************************************************** + Returns 1 if the operator P provides angular output coordinates when + operating in direction dir, 0 otherwise. + dir: {PJ_FWD, PJ_INV} +******************************************************************************/ + return proj_angular_input (P, opposite_direction(dir)); +} + + +/* Geodesic distance (in meter) + fwd and rev azimuth between two points on the ellipsoid */ +PJ_COORD proj_geod (const PJ *P, PJ_COORD a, PJ_COORD b) { + PJ_COORD c; + /* Note: the geodesic code takes arguments in degrees */ + geod_inverse (P->geod, + PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), + PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), + c.v, c.v+1, c.v+2 + ); + + return c; +} + + +/* Geodesic distance (in meter) between two points with angular 2D coordinates */ +double proj_lp_dist (const PJ *P, PJ_COORD a, PJ_COORD b) { + double s12, azi1, azi2; + /* Note: the geodesic code takes arguments in degrees */ + geod_inverse (P->geod, + PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), + PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), + &s12, &azi1, &azi2 + ); + return s12; +} + +/* The geodesic distance AND the vertical offset */ +double proj_lpz_dist (const PJ *P, PJ_COORD a, PJ_COORD b) { + if (HUGE_VAL==a.lpz.lam || HUGE_VAL==b.lpz.lam) + return HUGE_VAL; + return hypot (proj_lp_dist (P, a, b), a.lpz.z - b.lpz.z); +} + +/* Euclidean distance between two points with linear 2D coordinates */ +double proj_xy_dist (PJ_COORD a, PJ_COORD b) { + return hypot (a.xy.x - b.xy.x, a.xy.y - b.xy.y); +} + +/* Euclidean distance between two points with linear 3D coordinates */ +double proj_xyz_dist (PJ_COORD a, PJ_COORD b) { + return hypot (proj_xy_dist (a, b), a.xyz.z - b.xyz.z); +} + + + +/* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */ +double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { + int i; + PJ_COORD t, org; + + if (0==P) + return HUGE_VAL; + + if (n < 1) { + proj_errno_set (P, EINVAL); + return HUGE_VAL; + } + + /* in the first half-step, we generate the output value */ + org = *coord; + *coord = proj_trans (P, direction, org); + t = *coord; + + /* now we take n-1 full steps in inverse direction: We are */ + /* out of phase due to the half step already taken */ + for (i = 0; i < n - 1; i++) + t = proj_trans (P, direction, proj_trans (P, opposite_direction(direction), t) ); + + /* finally, we take the last half-step */ + t = proj_trans (P, opposite_direction(direction), t); + + /* checking for angular *input* since we do a roundtrip, and end where we begin */ + if (proj_angular_input (P, direction)) + return proj_lpz_dist (P, org, t); + + return proj_xyz_dist (org, t); +} + + + +/**************************************************************************************/ +PJ_COORD proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { +/*************************************************************************************** +Apply the transformation P to the coordinate coord, preferring the 4D interfaces if +available. + +See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which work +similarly, but prefers the 2D resp. 3D interfaces if available. +***************************************************************************************/ + if (0==P) + return coord; + if (P->inverted) + direction = opposite_direction(direction); + + switch (direction) { + case PJ_FWD: + return pj_fwd4d (coord, P); + case PJ_INV: + return pj_inv4d (coord, P); + case PJ_IDENT: + return coord; + default: + break; + } + + proj_errno_set (P, EINVAL); + return proj_coord_error (); +} + + + +/*****************************************************************************/ +int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) { +/****************************************************************************** + Batch transform an array of PJ_COORD. + + Returns 0 if all coordinates are transformed without error, otherwise + returns error number. +******************************************************************************/ + size_t i; + + for (i = 0; i < n; i++) { + coord[i] = proj_trans (P, direction, coord[i]); + if (proj_errno(P)) + return proj_errno (P); + } + + return 0; +} + + + +/*************************************************************************************/ +size_t proj_trans_generic ( + PJ *P, + PJ_DIRECTION direction, + double *x, size_t sx, size_t nx, + double *y, size_t sy, size_t ny, + double *z, size_t sz, size_t nz, + double *t, size_t st, size_t nt +) { +/************************************************************************************** + + Transform a series of coordinates, where the individual coordinate dimension + may be represented by an array that is either + + 1. fully populated + 2. a null pointer and/or a length of zero, which will be treated as a + fully populated array of zeroes + 3. of length one, i.e. a constant, which will be treated as a fully + populated array of that constant value + + The strides, sx, sy, sz, st, represent the step length, in bytes, between + consecutive elements of the corresponding array. This makes it possible for + proj_transform to handle transformation of a large class of application + specific data structures, without necessarily understanding the data structure + format, as in: + + typedef struct {double x, y; int quality_level; char surveyor_name[134];} XYQS; + XYQS survey[345]; + double height = 23.45; + PJ *P = {...}; + size_t stride = sizeof (XYQS); + ... + proj_transform ( + P, PJ_INV, sizeof(XYQS), + &(survey[0].x), stride, 345, (* We have 345 eastings *) + &(survey[0].y), stride, 345, (* ...and 345 northings. *) + &height, 1, (* The height is the constant 23.45 m *) + 0, 0 (* and the time is the constant 0.00 s *) + ); + + This is similar to the inner workings of the pj_transform function, but the + stride functionality has been generalized to work for any size of basic unit, + not just a fixed number of doubles. + + In most cases, the stride will be identical for x, y,z, and t, since they will + typically be either individual arrays (stride = sizeof(double)), or strided + views into an array of application specific data structures (stride = sizeof (...)). + + But in order to support cases where x, y, z, and t come from heterogeneous + sources, individual strides, sx, sy, sz, st, are used. + + Caveat: Since proj_transform does its work *in place*, this means that even the + supposedly constants (i.e. length 1 arrays) will return from the call in altered + state. Hence, remember to reinitialize between repeated calls. + + Return value: Number of transformations completed. + +**************************************************************************************/ + PJ_COORD coord = {{0,0,0,0}}; + size_t i, nmin; + double null_broadcast = 0; + + if (0==P) + return 0; + + if (P->inverted) + direction = opposite_direction(direction); + + /* ignore lengths of null arrays */ + if (0==x) nx = 0; + if (0==y) ny = 0; + if (0==z) nz = 0; + if (0==t) nt = 0; + + /* and make the nullities point to some real world memory for broadcasting nulls */ + if (0==nx) x = &null_broadcast; + if (0==ny) y = &null_broadcast; + if (0==nz) z = &null_broadcast; + if (0==nt) t = &null_broadcast; + + /* nothing to do? */ + if (0==nx+ny+nz+nt) + return 0; + + /* arrays of length 1 are constants, which we broadcast along the longer arrays */ + /* so we need to find the length of the shortest non-unity array to figure out */ + /* how many coordinate pairs we must transform */ + nmin = (nx > 1)? nx: (ny > 1)? ny: (nz > 1)? nz: (nt > 1)? nt: 1; + if ((nx > 1) && (nx < nmin)) nmin = nx; + if ((ny > 1) && (ny < nmin)) nmin = ny; + if ((nz > 1) && (nz < nmin)) nmin = nz; + if ((nt > 1) && (nt < nmin)) nmin = nt; + + /* Check validity of direction flag */ + switch (direction) { + case PJ_FWD: + case PJ_INV: + break; + case PJ_IDENT: + return nmin; + default: + proj_errno_set (P, EINVAL); + return 0; + } + + /* Arrays of length==0 are broadcast as the constant 0 */ + /* Arrays of length==1 are broadcast as their single value */ + /* Arrays of length >1 are iterated over (for the first nmin values) */ + /* The slightly convolved incremental indexing is used due */ + /* to the stride, which may be any size supported by the platform */ + for (i = 0; i < nmin; i++) { + coord.xyzt.x = *x; + coord.xyzt.y = *y; + coord.xyzt.z = *z; + coord.xyzt.t = *t; + + if (PJ_FWD==direction) + coord = pj_fwd4d (coord, P); + else + coord = pj_inv4d (coord, P); + + /* in all full length cases, we overwrite the input with the output, */ + /* and step on to the next element. */ + /* The casts are somewhat funky, but they compile down to no-ops and */ + /* they tell compilers and static analyzers that we know what we do */ + if (nx > 1) { + *x = coord.xyzt.x; + x = (double *) ((void *) ( ((char *) x) + sx)); + } + if (ny > 1) { + *y = coord.xyzt.y; + y = (double *) ((void *) ( ((char *) y) + sy)); + } + if (nz > 1) { + *z = coord.xyzt.z; + z = (double *) ((void *) ( ((char *) z) + sz)); + } + if (nt > 1) { + *t = coord.xyzt.t; + t = (double *) ((void *) ( ((char *) t) + st)); + } + } + + /* Last time around, we update the length 1 cases with their transformed alter egos */ + if (nx==1) + *x = coord.xyzt.x; + if (ny==1) + *y = coord.xyzt.y; + if (nz==1) + *z = coord.xyzt.z; + if (nt==1) + *t = coord.xyzt.t; + + return i; +} + + +/*************************************************************************************/ +PJ_COORD pj_geocentric_latitude (const PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { +/************************************************************************************** + Convert geographical latitude to geocentric (or the other way round if + direction = PJ_INV) + + The conversion involves a call to the tangent function, which goes through the + roof at the poles, so very close (the last centimeter) to the poles no + conversion takes place and the input latitude is copied directly to the output. + + Fortunately, the geocentric latitude converges to the geographical at the + poles, so the difference is negligible. + + For the spherical case, the geographical latitude equals the geocentric, and + consequently, the input is copied directly to the output. +**************************************************************************************/ + const double limit = M_HALFPI - 1e-9; + PJ_COORD res = coord; + if ((coord.lp.phi > limit) || (coord.lp.phi < -limit) || (P->es==0)) + return res; + if (direction==PJ_FWD) + res.lp.phi = atan (P->one_es * tan (coord.lp.phi) ); + else + res.lp.phi = atan (P->rone_es * tan (coord.lp.phi) ); + + return res; +} + +double proj_torad (double angle_in_degrees) { return PJ_TORAD (angle_in_degrees);} +double proj_todeg (double angle_in_radians) { return PJ_TODEG (angle_in_radians);} + +double proj_dmstor(const char *is, char **rs) { + return dmstor(is, rs); +} + +char* proj_rtodms(char *s, double r, int pos, int neg) { + return rtodms(s, r, pos, neg); +} + +/*************************************************************************************/ +static PJ* skip_prep_fin(PJ *P) { +/************************************************************************************** +Skip prepare and finalize function for the various "helper operations" added to P when +in cs2cs compatibility mode. +**************************************************************************************/ + P->skip_fwd_prepare = 1; + P->skip_fwd_finalize = 1; + P->skip_inv_prepare = 1; + P->skip_inv_finalize = 1; + return P; +} + +/*************************************************************************************/ +static int cs2cs_emulation_setup (PJ *P) { +/************************************************************************************** +If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the 4D API +equivalent operations, so the preparation and finalization steps in the pj_inv/pj_fwd +invocators can emulate the behaviour of pj_transform and the cs2cs app. + +Returns 1 on success, 0 on failure +**************************************************************************************/ + PJ *Q; + paralist *p; + int do_cart = 0; + if (0==P) + return 0; + + /* Don't recurse when calling proj_create (which calls us back) */ + if (pj_param_exists (P->params, "break_cs2cs_recursion")) + return 1; + + /* Swap axes? */ + p = pj_param_exists (P->params, "axis"); + + /* Don't axisswap if data are already in "enu" order */ + if (p && (0!=strcmp ("enu", p->param))) { + char *def = static_cast(malloc (100+strlen(P->axis))); + if (0==def) + return 0; + sprintf (def, "break_cs2cs_recursion proj=axisswap axis=%s", P->axis); + Q = proj_create (P->ctx, def); + free (def); + if (0==Q) + return 0; + P->axisswap = skip_prep_fin(Q); + } + + /* Geoid grid(s) given? */ + p = pj_param_exists (P->params, "geoidgrids"); + if (p && strlen (p->param) > strlen ("geoidgrids=")) { + char *gridnames = p->param + strlen ("geoidgrids="); + char *def = static_cast(malloc (100+strlen(gridnames))); + if (0==def) + return 0; + sprintf (def, "break_cs2cs_recursion proj=vgridshift grids=%s", gridnames); + Q = proj_create (P->ctx, def); + free (def); + if (0==Q) + return 0; + P->vgridshift = skip_prep_fin(Q); + } + + /* Datum shift grid(s) given? */ + p = pj_param_exists (P->params, "nadgrids"); + if (p && strlen (p->param) > strlen ("nadgrids=")) { + char *gridnames = p->param + strlen ("nadgrids="); + char *def = static_cast(malloc (100+strlen(gridnames))); + if (0==def) + return 0; + sprintf (def, "break_cs2cs_recursion proj=hgridshift grids=%s", gridnames); + Q = proj_create (P->ctx, def); + free (def); + if (0==Q) + return 0; + P->hgridshift = skip_prep_fin(Q); + } + + /* We ignore helmert if we have grid shift */ + p = P->hgridshift ? 0 : pj_param_exists (P->params, "towgs84"); + while (p) { + char *def; + char *s = p->param; + double *d = P->datum_params; + size_t n = strlen (s); + + /* We ignore null helmert shifts (common in auto-translated resource files, e.g. epsg) */ + if (0==d[0] && 0==d[1] && 0==d[2] && 0==d[3] && 0==d[4] && 0==d[5] && 0==d[6]) { + /* If the current ellipsoid is not WGS84, then make sure the */ + /* change in ellipsoid is still done. */ + if (!(fabs(P->a_orig - 6378137.0) < 1e-8 && fabs(P->es_orig - 0.0066943799901413) < 1e-15)) { + do_cart = 1; + } + break; + } + + if (n <= 8) /* 8==strlen ("towgs84=") */ + return 0; + + def = static_cast(malloc (100+n)); + if (0==def) + return 0; + sprintf (def, "break_cs2cs_recursion proj=helmert exact %s convention=position_vector", s); + Q = proj_create (P->ctx, def); + free(def); + if (0==Q) + return 0; + pj_inherit_ellipsoid_def (P, Q); + P->helmert = skip_prep_fin (Q); + + break; + } + + /* We also need cartesian/geographical transformations if we are working in */ + /* geocentric/cartesian space or we need to do a Helmert transform. */ + if (P->is_geocent || P->helmert || do_cart) { + char def[150]; + sprintf (def, "break_cs2cs_recursion proj=cart a=%40.20g es=%40.20g", P->a_orig, P->es_orig); + { + /* In case the current locale does not use dot but comma as decimal */ + /* separator, replace it with dot, so that proj_atof() behaves */ + /* correctly. */ + /* TODO later: use C++ ostringstream with imbue(std::locale::classic()) */ + /* to be locale unaware */ + char* next_pos; + for (next_pos = def; (next_pos = strchr (next_pos, ',')) != NULL; next_pos++) { + *next_pos = '.'; + } + } + Q = proj_create (P->ctx, def); + if (0==Q) + return 0; + P->cart = skip_prep_fin (Q); + + if (!P->is_geocent) { + sprintf (def, "break_cs2cs_recursion proj=cart ellps=WGS84"); + Q = proj_create (P->ctx, def); + if (0==Q) + return 0; + P->cart_wgs84 = skip_prep_fin (Q); + } + } + + return 1; +} + + + +/*************************************************************************************/ +PJ *proj_create (PJ_CONTEXT *ctx, const char *definition) { +/************************************************************************************** + Create a new PJ object in the context ctx, using the given definition. If ctx==0, + the default context is used, if definition==0, or invalid, a null-pointer is + returned. The definition may use '+' as argument start indicator, as in + "+proj=utm +zone=32", or leave it out, as in "proj=utm zone=32". + + It may even use free formatting "proj = utm; zone =32 ellps= GRS80". + Note that the semicolon separator is allowed, but not required. +**************************************************************************************/ + PJ *P; + char *args, **argv; + size_t argc, n; + int ret; + int allow_init_epsg; + + if (0==ctx) + ctx = pj_get_default_ctx (); + + /* Make a copy that we can manipulate */ + n = strlen (definition); + args = (char *) malloc (n + 1); + if (0==args) { + proj_context_errno_set(ctx, ENOMEM); + return 0; + } + strcpy (args, definition); + + argc = pj_trim_argc (args); + if (argc==0) { + pj_dealloc (args); + proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); + return 0; + } + + argv = pj_trim_argv (argc, args); + + /* ...and let pj_init_ctx do the hard work */ + /* New interface: forbid init=epsg:XXXX syntax by default */ + allow_init_epsg = proj_context_get_use_proj4_init_rules(ctx, FALSE); + P = pj_init_ctx_with_allow_init_epsg (ctx, (int) argc, argv, allow_init_epsg); + + pj_dealloc (argv); + pj_dealloc (args); + + /* Support cs2cs-style modifiers */ + ret = cs2cs_emulation_setup (P); + if (0==ret) + return proj_destroy (P); + + return P; +} + + + +/*************************************************************************************/ +PJ *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv) { +/************************************************************************************** +Create a new PJ object in the context ctx, using the given definition argument +array argv. If ctx==0, the default context is used, if definition==0, or invalid, +a null-pointer is returned. The definition arguments may use '+' as argument start +indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm", +"zone=32"}. +**************************************************************************************/ + PJ *P; + const char *c; + + if (0==ctx) + ctx = pj_get_default_ctx (); + if (0==argv) { + proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); + return 0; + } + + /* We assume that free format is used, and build a full proj_create compatible string */ + c = pj_make_args (argc, argv); + if (0==c) { + proj_context_errno_set(ctx, ENOMEM); + return 0; + } + + P = proj_create (ctx, c); + + pj_dealloc ((char *) c); + return P; +} + +/** Create an area of use */ +PJ_AREA * proj_area_create(void) { + return static_cast(pj_calloc(1, sizeof(PJ_AREA))); +} + +/** Assign a bounding box to an area of use. */ +void proj_area_set_bbox(PJ_AREA *area, + double west_lon_degree, + double south_lat_degree, + double east_lon_degree, + double north_lat_degree) { + area->bbox_set = TRUE; + area->west_lon_degree = west_lon_degree; + area->south_lat_degree = south_lat_degree; + area->east_lon_degree = east_lon_degree; + area->north_lat_degree = north_lat_degree; +} + +/** Free an area of use */ +void proj_area_destroy(PJ_AREA* area) { + pj_dealloc(area); +} + +/************************************************************************/ +/* proj_context_use_proj4_init_rules() */ +/************************************************************************/ + +void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) { + if( ctx == NULL ) { + ctx = pj_get_default_ctx(); + } + ctx->use_proj4_init_rules = enable; +} + +/************************************************************************/ +/* EQUAL() */ +/************************************************************************/ + +static int EQUAL(const char* a, const char* b) { +#ifdef _MSC_VER + return _stricmp(a, b) == 0; +#else + return strcasecmp(a, b) == 0; +#endif +} + +/************************************************************************/ +/* proj_context_get_use_proj4_init_rules() */ +/************************************************************************/ + +int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path) { + const char* val = getenv("PROJ_USE_PROJ4_INIT_RULES"); + + if( ctx == NULL ) { + ctx = pj_get_default_ctx(); + } + + if( val ) { + if( EQUAL(val, "yes") || EQUAL(val, "on") || EQUAL(val, "true") ) { + return TRUE; + } + if( EQUAL(val, "no") || EQUAL(val, "off") || EQUAL(val, "false") ) { + return FALSE; + } + pj_log(ctx, PJ_LOG_ERROR, "Invalid value for PROJ_USE_PROJ4_INIT_RULES"); + } + + if( ctx->use_proj4_init_rules >= 0 ) { + return ctx->use_proj4_init_rules; + } + return from_legacy_code_path; +} + + +/*****************************************************************************/ +PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char *target_crs, PJ_AREA *area) { +/****************************************************************************** + Create a transformation pipeline between two known coordinate reference + systems. + + source_crs and target_crs can be : + - a "AUTHORITY:CODE", like EPSG:25832. When using that syntax for a source + CRS, the created pipeline will expect that the values passed to proj_trans() + respect the axis order and axis unit of the official definition ( + so for example, for EPSG:4326, with latitude first and longitude next, + in degrees). Similarly, when using that syntax for a target CRS, output + values will be emitted according to the official definition of this CRS. + - a PROJ string, like "+proj=longlat +datum=WGS84". + When using that syntax, the axis order and unit for geographic CRS will + be longitude, latitude, and the unit degrees. + - more generally any string accepted by proj_obj_create_from_user_input() + + An "area of use" can be specified in area. When it is supplied, the more + accurate transformation between two given systems can be chosen. + + Example call: + + PJ *P = proj_create_crs_to_crs(0, "EPSG:25832", "EPSG:25833", NULL); + +******************************************************************************/ + PJ *P; + PJ_OBJ* src; + PJ_OBJ* dst; + PJ_OPERATION_FACTORY_CONTEXT* operation_ctx; + PJ_OBJ_LIST* op_list; + PJ_OBJ* op; + const char* proj_string; + const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; + const char* const* optionsImportCRS = + proj_context_get_use_proj4_init_rules(ctx, FALSE) ? optionsProj4Mode : NULL; + + src = proj_obj_create_from_user_input(ctx, source_crs, optionsImportCRS); + if( !src ) { + return NULL; + } + + dst = proj_obj_create_from_user_input(ctx, target_crs, optionsImportCRS); + if( !dst ) { + proj_obj_destroy(src); + return NULL; + } + + operation_ctx = proj_create_operation_factory_context(ctx, NULL); + if( !operation_ctx ) { + proj_obj_destroy(src); + proj_obj_destroy(dst); + return NULL; + } + + if( area && area->bbox_set ) { + proj_operation_factory_context_set_area_of_interest( + ctx, + operation_ctx, + area->west_lon_degree, + area->south_lat_degree, + area->east_lon_degree, + area->north_lat_degree); + } + + proj_operation_factory_context_set_grid_availability_use( + ctx, operation_ctx, PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); + + op_list = proj_obj_create_operations(ctx, src, dst, operation_ctx); + + proj_operation_factory_context_destroy(operation_ctx); + proj_obj_destroy(src); + proj_obj_destroy(dst); + + if( !op_list ) { + return NULL; + } + + if( proj_obj_list_get_count(op_list) == 0 ) { + proj_obj_list_destroy(op_list); + return NULL; + } + + op = proj_obj_list_get(ctx, op_list, 0); + proj_obj_list_destroy(op_list); + if( !op ) { + return NULL; + } + + proj_string = proj_obj_as_proj_string(ctx, op, PJ_PROJ_5, NULL); + if( !proj_string) { + proj_obj_destroy(op); + return NULL; + } + + if( proj_string[0] == '\0' ) { + /* Null transform ? */ + P = proj_create(ctx, "proj=affine"); + } else { + P = proj_create(ctx, proj_string); + } + + proj_obj_destroy(op); + + return P; +} + +PJ *proj_destroy (PJ *P) { + pj_free (P); + return 0; +} + +/*****************************************************************************/ +int proj_errno (const PJ *P) { +/****************************************************************************** + Read an error level from the context of a PJ. +******************************************************************************/ + return pj_ctx_get_errno (pj_get_ctx ((PJ *) P)); +} + +/*****************************************************************************/ +int proj_context_errno (PJ_CONTEXT *ctx) { +/****************************************************************************** + Read an error directly from a context, without going through a PJ + belonging to that context. +******************************************************************************/ + if (0==ctx) + ctx = pj_get_default_ctx(); + return pj_ctx_get_errno (ctx); +} + +/*****************************************************************************/ +int proj_errno_set (const PJ *P, int err) { +/****************************************************************************** + Set context-errno, bubble it up to the thread local errno, return err +******************************************************************************/ + /* Use proj_errno_reset to explicitly clear the error status */ + if (0==err) + return 0; + + /* For P==0 err goes to the default context */ + proj_context_errno_set (pj_get_ctx ((PJ *) P), err); + errno = err; + return err; +} + +/*****************************************************************************/ +int proj_errno_restore (const PJ *P, int err) { +/****************************************************************************** + Use proj_errno_restore when the current function succeeds, but the + error flag was set on entry, and stored/reset using proj_errno_reset + in order to monitor for new errors. + + See usage example under proj_errno_reset () +******************************************************************************/ + if (0==err) + return 0; + proj_errno_set (P, err); + return 0; +} + +/*****************************************************************************/ +int proj_errno_reset (const PJ *P) { +/****************************************************************************** + Clears errno in the context and thread local levels + through the low level pj_ctx interface. + + Returns the previous value of the errno, for convenient reset/restore + operations: + + int foo (PJ *P) { + // errno may be set on entry, but we need to reset it to be able to + // check for errors from "do_something_with_P(P)" + int last_errno = proj_errno_reset (P); + + // local failure + if (0==P) + return proj_errno_set (P, 42); + + // call to function that may fail + do_something_with_P (P); + + // failure in do_something_with_P? - keep latest error status + if (proj_errno(P)) + return proj_errno (P); + + // success - restore previous error status, return 0 + return proj_errno_restore (P, last_errno); + } +******************************************************************************/ + int last_errno; + last_errno = proj_errno (P); + + pj_ctx_set_errno (pj_get_ctx ((PJ *) P), 0); + errno = 0; + pj_errno = 0; + return last_errno; +} + + +/* Create a new context */ +PJ_CONTEXT *proj_context_create (void) { + return pj_ctx_alloc (); +} + + +PJ_CONTEXT *proj_context_destroy (PJ_CONTEXT *ctx) { + if (0==ctx) + return 0; + + /* Trying to free the default context is a no-op (since it is statically allocated) */ + if (pj_get_default_ctx ()==ctx) + return 0; + + pj_ctx_free (ctx); + return 0; +} + + + + + + +/*****************************************************************************/ +static char *path_append (char *buf, const char *app, size_t *buf_size) { +/****************************************************************************** + Helper for proj_info() below. Append app to buf, separated by a + semicolon. Also handle allocation of longer buffer if needed. + + Returns buffer and adjusts *buf_size through provided pointer arg. +******************************************************************************/ + char *p; + size_t len, applen = 0, buflen = 0; +#ifdef _WIN32 + char *delim = ";"; +#else + char *delim = ":"; +#endif + + /* Nothing to do? */ + if (0 == app) + return buf; + applen = strlen (app); + if (0 == applen) + return buf; + + /* Start checking whether buf is long enough */ + if (0 != buf) + buflen = strlen (buf); + len = buflen+applen+strlen (delim) + 1; + + /* "pj_realloc", so to speak */ + if (*buf_size < len) { + p = static_cast(pj_calloc (2 * len, sizeof (char))); + if (0==p) { + pj_dealloc (buf); + return 0; + } + *buf_size = 2 * len; + if (buf != 0) + strcpy (p, buf); + pj_dealloc (buf); + buf = p; + } + + /* Only append a semicolon if something's already there */ + if (0 != buflen) + strcat (buf, ";"); + strcat (buf, app); + return buf; +} + +static const char *empty = {""}; +static char version[64] = {""}; +static PJ_INFO info = {0, 0, 0, 0, 0, 0, 0, 0}; +static volatile int info_initialized = 0; + +/*****************************************************************************/ +PJ_INFO proj_info (void) { +/****************************************************************************** + Basic info about the current instance of the PROJ.4 library. + + Returns PJ_INFO struct. +******************************************************************************/ + const char * const *paths; + size_t i, n; + + size_t buf_size = 0; + char *buf = 0; + + pj_acquire_lock (); + + if (0!=info_initialized) { + pj_release_lock (); + return info; + } + + info.major = PROJ_VERSION_MAJOR; + info.minor = PROJ_VERSION_MINOR; + info.patch = PROJ_VERSION_PATCH; + + /* This is a controlled environment, so no risk of sprintf buffer + overflow. A normal version string is xx.yy.zz which is 8 characters + long and there is room for 64 bytes in the version string. */ + sprintf (version, "%d.%d.%d", info.major, info.minor, info.patch); + + info.searchpath = empty; + info.version = version; + info.release = pj_get_release (); + + /* build search path string */ + buf = path_append (buf, getenv ("HOME"), &buf_size); + buf = path_append (buf, getenv ("PROJ_LIB"), &buf_size); + + paths = proj_get_searchpath (); + n = (size_t) proj_get_path_count (); + + for (i = 0; i < n; i++) + buf = path_append (buf, paths[i], &buf_size); + info.searchpath = buf ? buf : empty; + + info.paths = paths; + info.path_count = n; + + info_initialized = 1; + pj_release_lock (); + return info; +} + + +/*****************************************************************************/ +PJ_PROJ_INFO proj_pj_info(PJ *P) { +/****************************************************************************** + Basic info about a particular instance of a projection object. + + Returns PJ_PROJ_INFO struct. +******************************************************************************/ + PJ_PROJ_INFO pjinfo; + char *def; + + memset(&pjinfo, 0, sizeof(PJ_PROJ_INFO)); + + /* Expected accuracy of the transformation. Hardcoded for now, will be improved */ + /* later. Most likely to be used when a transformation is set up with */ + /* proj_create_crs_to_crs in a future version that leverages the EPSG database. */ + pjinfo.accuracy = -1.0; + + if (0==P) + return pjinfo; + + /* projection id */ + if (pj_param(P->ctx, P->params, "tproj").i) + pjinfo.id = pj_param(P->ctx, P->params, "sproj").s; + + /* projection description */ + pjinfo.description = P->descr; + + /* projection definition */ + if (P->def_full) + def = P->def_full; + else + def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */ + if (0==def) + pjinfo.definition = empty; + else + pjinfo.definition = pj_shrink (def); + /* Make pj_free clean this up eventually */ + P->def_full = def; + + pjinfo.has_inverse = pj_has_inverse(P); + return pjinfo; +} + + +/*****************************************************************************/ +PJ_GRID_INFO proj_grid_info(const char *gridname) { +/****************************************************************************** + Information about a named datum grid. + + Returns PJ_GRID_INFO struct. +******************************************************************************/ + PJ_GRID_INFO grinfo; + + /*PJ_CONTEXT *ctx = proj_context_create(); */ + PJ_CONTEXT *ctx = pj_get_default_ctx(); + PJ_GRIDINFO *gridinfo = pj_gridinfo_init(ctx, gridname); + memset(&grinfo, 0, sizeof(PJ_GRID_INFO)); + + /* in case the grid wasn't found */ + if (gridinfo->filename == NULL) { + pj_gridinfo_free(ctx, gridinfo); + strcpy(grinfo.format, "missing"); + return grinfo; + } + + /* The string copies below are automatically null-terminated due to */ + /* the memset above, so strncpy is safe */ + + /* name of grid */ + strncpy (grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1); + + /* full path of grid */ + pj_find_file(ctx, gridname, grinfo.filename, sizeof(grinfo.filename) - 1); + + /* grid format */ + strncpy (grinfo.format, gridinfo->format, sizeof(grinfo.format) - 1); + + /* grid size */ + grinfo.n_lon = gridinfo->ct->lim.lam; + grinfo.n_lat = gridinfo->ct->lim.phi; + + /* cell size */ + grinfo.cs_lon = gridinfo->ct->del.lam; + grinfo.cs_lat = gridinfo->ct->del.phi; + + /* bounds of grid */ + grinfo.lowerleft = gridinfo->ct->ll; + grinfo.upperright.lam = grinfo.lowerleft.lam + grinfo.n_lon*grinfo.cs_lon; + grinfo.upperright.phi = grinfo.lowerleft.phi + grinfo.n_lat*grinfo.cs_lat; + + pj_gridinfo_free(ctx, gridinfo); + + return grinfo; +} + + + +/*****************************************************************************/ +PJ_INIT_INFO proj_init_info(const char *initname){ +/****************************************************************************** + Information about a named init file. + + Maximum length of initname is 64. + + Returns PJ_INIT_INFO struct. + + If the init file is not found all members of the return struct are set + to the empty string. + + If the init file is found, but the metadata is missing, the value is + set to "Unknown". +******************************************************************************/ + int file_found; + char param[80], key[74]; + paralist *start, *next; + PJ_INIT_INFO ininfo; + PJ_CONTEXT *ctx = pj_get_default_ctx(); + + memset(&ininfo, 0, sizeof(PJ_INIT_INFO)); + + file_found = pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename)); + if (!file_found || strlen(initname) > 64) { + if( strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0 ) { + const char* val; + + pj_ctx_set_errno( ctx, 0 ); + + strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); + strcpy(ininfo.origin, "EPSG"); + val = proj_context_get_database_metadata(ctx, "EPSG.VERSION"); + if( val ) { + strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); + } + val = proj_context_get_database_metadata(ctx, "EPSG.DATE"); + if( val ) { + strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); + } + return ininfo; + } + + if( strcmp(initname, "IGNF") == 0 ) { + const char* val; + + pj_ctx_set_errno( ctx, 0 ); + + strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); + strcpy(ininfo.origin, "IGNF"); + val = proj_context_get_database_metadata(ctx, "IGNF.VERSION"); + if( val ) { + strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); + } + val = proj_context_get_database_metadata(ctx, "IGNF.DATE"); + if( val ) { + strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); + } + return ininfo; + } + + return ininfo; + } + + /* The initial memset (0) makes strncpy safe here */ + strncpy (ininfo.name, initname, sizeof(ininfo.name) - 1); + strcpy(ininfo.origin, "Unknown"); + strcpy(ininfo.version, "Unknown"); + strcpy(ininfo.lastupdate, "Unknown"); + + strncpy (key, initname, 64); /* make room for ":metadata\0" at the end */ + key[64] = 0; + memcpy(key + strlen(key), ":metadata", 9 + 1); + strcpy(param, "+init="); + /* The +strlen(param) avoids a cppcheck false positive warning */ + strncat(param + strlen(param), key, sizeof(param)-1-strlen(param)); + + start = pj_mkparam(param); + pj_expand_init(ctx, start); + + if (pj_param(ctx, start, "tversion").i) + strncpy(ininfo.version, pj_param(ctx, start, "sversion").s, sizeof(ininfo.version) - 1); + + if (pj_param(ctx, start, "torigin").i) + strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s, sizeof(ininfo.origin) - 1); + + if (pj_param(ctx, start, "tlastupdate").i) + strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s, sizeof(ininfo.lastupdate) - 1); + + for ( ; start; start = next) { + next = start->next; + pj_dalloc(start); + } + + return ininfo; +} + + + +/*****************************************************************************/ +PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { +/****************************************************************************** + Cartographic characteristics at point lp. + + Characteristics include meridian, parallel and areal scales, angular + distortion, meridian/parallel, meridian convergence and scale error. + + returns PJ_FACTORS. If unsuccessful, error number is set and the + struct returned contains NULL data. +******************************************************************************/ + PJ_FACTORS factors = {0,0,0, 0,0,0, 0,0, 0,0,0,0}; + struct FACTORS f; + + if (0==P) + return factors; + + if (pj_factors(lp.lp, P, 0.0, &f)) + return factors; + + factors.meridional_scale = f.h; + factors.parallel_scale = f.k; + factors.areal_scale = f.s; + + factors.angular_distortion = f.omega; + factors.meridian_parallel_angle = f.thetap; + factors.meridian_convergence = f.conv; + + factors.tissot_semimajor = f.a; + factors.tissot_semiminor = f.b; + + /* Raw derivatives, for completeness's sake */ + factors.dx_dlam = f.der.x_l; + factors.dx_dphi = f.der.x_p; + factors.dy_dlam = f.der.y_l; + factors.dy_dphi = f.der.y_p; + + return factors; +} diff --git a/src/proj_etmerc.c b/src/proj_etmerc.c deleted file mode 100644 index 4d7187e6..00000000 --- a/src/proj_etmerc.c +++ /dev/null @@ -1,360 +0,0 @@ -/* -** libproj -- library of cartographic projections -** -** Copyright (c) 2008 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. -*/ - -/* The code in this file is largly based upon procedures: - * - * Written by: Knud Poder and Karsten Engsager - * - * Based on math from: R.Koenig and K.H. Weise, "Mathematische - * Grundlagen der hoeheren Geodaesie und Kartographie, - * Springer-Verlag, Berlin/Goettingen" Heidelberg, 1951. - * - * Modified and used here by permission of Reference Networks - * Division, Kort og Matrikelstyrelsen (KMS), Copenhagen, Denmark - * -*/ - -#define PJ_LIB__ - -#include - -#include "proj.h" -#include "projects.h" -#include "proj_math.h" - - -struct pj_opaque { - double Qn; /* Merid. quad., scaled to the projection */ \ - double Zb; /* Radius vector in polar coord. systems */ \ - double cgb[6]; /* Constants for Gauss -> Geo lat */ \ - double cbg[6]; /* Constants for Geo lat -> Gauss */ \ - double utg[6]; /* Constants for transv. merc. -> geo */ \ - double gtu[6]; /* Constants for geo -> transv. merc. */ -}; - -PROJ_HEAD(etmerc, "Extended Transverse Mercator") - "\n\tCyl, Sph\n\tlat_ts=(0)\nlat_0=(0)"; -PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") - "\n\tCyl, Sph\n\tzone= south"; - -#define PROJ_ETMERC_ORDER 6 - -#ifdef _GNU_SOURCE - inline -#endif -static double gatg(double *p1, int len_p1, double B) { - double *p; - double h = 0, h1, h2 = 0, cos_2B; - - cos_2B = 2*cos(2*B); - p = p1 + len_p1; - h1 = *--p; - while (p - p1) { - h = -h2 + cos_2B*h1 + *--p; - h2 = h1; - h1 = h; - } - return (B + h*sin(2*B)); -} - -/* Complex Clenshaw summation */ -#ifdef _GNU_SOURCE - inline -#endif -static double clenS(double *a, int size, double arg_r, double arg_i, double *R, double *I) { - double *p, r, i, hr, hr1, hr2, hi, hi1, hi2; - double sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i; - - /* arguments */ - p = a + size; -#ifdef _GNU_SOURCE - sincos(arg_r, &sin_arg_r, &cos_arg_r); -#else - sin_arg_r = sin(arg_r); - cos_arg_r = cos(arg_r); -#endif - sinh_arg_i = sinh(arg_i); - cosh_arg_i = cosh(arg_i); - r = 2*cos_arg_r*cosh_arg_i; - i = -2*sin_arg_r*sinh_arg_i; - - /* summation loop */ - hi1 = hr1 = hi = 0; - hr = *--p; - for (; a - p;) { - hr2 = hr1; - hi2 = hi1; - hr1 = hr; - hi1 = hi; - hr = -hr2 + r*hr1 - i*hi1 + *--p; - hi = -hi2 + i*hr1 + r*hi1; - } - - r = sin_arg_r*cosh_arg_i; - i = cos_arg_r*sinh_arg_i; - *R = r*hr - i*hi; - *I = r*hi + i*hr; - return *R; -} - - -/* Real Clenshaw summation */ -static double clens(double *a, int size, double arg_r) { - double *p, r, hr, hr1, hr2, cos_arg_r; - - p = a + size; - cos_arg_r = cos(arg_r); - r = 2*cos_arg_r; - - /* summation loop */ - hr1 = 0; - hr = *--p; - for (; a - p;) { - hr2 = hr1; - hr1 = hr; - hr = -hr2 + r*hr1 + *--p; - } - return sin (arg_r)*hr; -} - - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = P->opaque; - double sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; - double Cn = lp.phi, Ce = lp.lam; - - /* ell. LAT, LNG -> Gaussian LAT, LNG */ - Cn = gatg (Q->cbg, PROJ_ETMERC_ORDER, Cn); - /* Gaussian LAT, LNG -> compl. sph. LAT */ -#ifdef _GNU_SOURCE - sincos (Cn, &sin_Cn, &cos_Cn); - sincos (Ce, &sin_Ce, &cos_Ce); -#else - sin_Cn = sin (Cn); - cos_Cn = cos (Cn); - sin_Ce = sin (Ce); - cos_Ce = cos (Ce); -#endif - - Cn = atan2 (sin_Cn, cos_Ce*cos_Cn); - Ce = atan2 (sin_Ce*cos_Cn, hypot (sin_Cn, cos_Cn*cos_Ce)); - - /* compl. sph. N, E -> ell. norm. N, E */ - Ce = asinh ( tan (Ce) ); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */ - Cn += clenS (Q->gtu, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); - Ce += dCe; - if (fabs (Ce) <= 2.623395162778) { - xy.y = Q->Qn * Cn + Q->Zb; /* Northing */ - xy.x = Q->Qn * Ce; /* Easting */ - } 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 = P->opaque; - double sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; - double Cn = xy.y, Ce = xy.x; - - /* normalize N, E */ - Cn = (Cn - Q->Zb)/Q->Qn; - Ce = Ce/Q->Qn; - - if (fabs(Ce) <= 2.623395162778) { /* 150 degrees */ - /* norm. N, E -> compl. sph. LAT, LNG */ - Cn += clenS(Q->utg, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); - Ce += dCe; - Ce = atan (sinh (Ce)); /* Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI); */ - /* compl. sph. LAT -> Gaussian LAT, LNG */ -#ifdef _GNU_SOURCE - sincos (Cn, &sin_Cn, &cos_Cn); - sincos (Ce, &sin_Ce, &cos_Ce); -#else - sin_Cn = sin (Cn); - cos_Cn = cos (Cn); - sin_Ce = sin (Ce); - cos_Ce = cos (Ce); -#endif - Ce = atan2 (sin_Ce, cos_Ce*cos_Cn); - Cn = atan2 (sin_Cn*cos_Ce, hypot (sin_Ce, cos_Ce*cos_Cn)); - /* Gaussian LAT, LNG -> ell. LAT, LNG */ - lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn); - lp.lam = Ce; - } - else - lp.phi = lp.lam = HUGE_VAL; - return lp; -} - - -static PJ *setup(PJ *P) { /* general initialization */ - double f, n, np, Z; - struct pj_opaque *Q = P->opaque; - - if (P->es <= 0) { - return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - } - - /* flattening */ - f = P->es / (1 + sqrt (1 - P->es)); /* Replaces: f = 1 - sqrt(1-P->es); */ - - /* third flattening */ - np = n = f/(2 - f); - - /* COEF. OF TRIG SERIES GEO <-> GAUSS */ - /* cgb := Gaussian -> Geodetic, KW p190 - 191 (61) - (62) */ - /* cbg := Geodetic -> Gaussian, KW p186 - 187 (51) - (52) */ - /* PROJ_ETMERC_ORDER = 6th degree : Engsager and Poder: ICC2007 */ - - Q->cgb[0] = n*( 2 + n*(-2/3.0 + n*(-2 + n*(116/45.0 + n*(26/45.0 + - n*(-2854/675.0 )))))); - Q->cbg[0] = n*(-2 + n*( 2/3.0 + n*( 4/3.0 + n*(-82/45.0 + n*(32/45.0 + - n*( 4642/4725.0)))))); - np *= n; - Q->cgb[1] = np*(7/3.0 + n*( -8/5.0 + n*(-227/45.0 + n*(2704/315.0 + - n*( 2323/945.0))))); - Q->cbg[1] = np*(5/3.0 + n*(-16/15.0 + n*( -13/9.0 + n*( 904/315.0 + - n*(-1522/945.0))))); - np *= n; - /* n^5 coeff corrected from 1262/105 -> -1262/105 */ - Q->cgb[2] = np*( 56/15.0 + n*(-136/35.0 + n*(-1262/105.0 + - n*( 73814/2835.0)))); - Q->cbg[2] = np*(-26/15.0 + n*( 34/21.0 + n*( 8/5.0 + - n*(-12686/2835.0)))); - np *= n; - /* n^5 coeff corrected from 322/35 -> 332/35 */ - Q->cgb[3] = np*(4279/630.0 + n*(-332/35.0 + n*(-399572/14175.0))); - Q->cbg[3] = np*(1237/630.0 + n*( -12/5.0 + n*( -24832/14175.0))); - np *= n; - Q->cgb[4] = np*(4174/315.0 + n*(-144838/6237.0 )); - Q->cbg[4] = np*(-734/315.0 + n*( 109598/31185.0)); - np *= n; - Q->cgb[5] = np*(601676/22275.0 ); - Q->cbg[5] = np*(444337/155925.0); - - /* Constants of the projections */ - /* Transverse Mercator (UTM, ITM, etc) */ - np = n*n; - /* Norm. mer. quad, K&W p.50 (96), p.19 (38b), p.5 (2) */ - Q->Qn = P->k0/(1 + n) * (1 + np*(1/4.0 + np*(1/64.0 + np/256.0))); - /* coef of trig series */ - /* utg := ell. N, E -> sph. N, E, KW p194 (65) */ - /* gtu := sph. N, E -> ell. N, E, KW p196 (69) */ - Q->utg[0] = n*(-0.5 + n*( 2/3.0 + n*(-37/96.0 + n*( 1/360.0 + - n*( 81/512.0 + n*(-96199/604800.0)))))); - Q->gtu[0] = n*( 0.5 + n*(-2/3.0 + n*( 5/16.0 + n*(41/180.0 + - n*(-127/288.0 + n*( 7891/37800.0 )))))); - Q->utg[1] = np*(-1/48.0 + n*(-1/15.0 + n*(437/1440.0 + n*(-46/105.0 + - n*( 1118711/3870720.0))))); - Q->gtu[1] = np*(13/48.0 + n*(-3/5.0 + n*(557/1440.0 + n*(281/630.0 + - n*(-1983433/1935360.0))))); - np *= n; - Q->utg[2] = np*(-17/480.0 + n*( 37/840.0 + n*( 209/4480.0 + - n*( -5569/90720.0 )))); - Q->gtu[2] = np*( 61/240.0 + n*(-103/140.0 + n*(15061/26880.0 + - n*(167603/181440.0)))); - np *= n; - Q->utg[3] = np*(-4397/161280.0 + n*( 11/504.0 + n*( 830251/7257600.0))); - Q->gtu[3] = np*(49561/161280.0 + n*(-179/168.0 + n*(6601661/7257600.0))); - np *= n; - Q->utg[4] = np*(-4583/161280.0 + n*( 108847/3991680.0)); - Q->gtu[4] = np*(34729/80640.0 + n*(-3418889/1995840.0)); - np *= n; - Q->utg[5] = np*(-20648693/638668800.0); - Q->gtu[5] = np*(212378941/319334400.0); - - /* Gaussian latitude value of the origin latitude */ - Z = gatg (Q->cbg, PROJ_ETMERC_ORDER, P->phi0); - - /* Origin northing minus true northing at the origin latitude */ - /* i.e. true northing = N - P->Zb */ - Q->Zb = - Q->Qn*(Z + clens(Q->gtu, PROJ_ETMERC_ORDER, 2*Z)); - P->inv = e_inverse; - P->fwd = e_forward; - return P; -} - - - -PJ *PROJECTION(etmerc) { - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - return setup (P); -} - - - -/* utm uses etmerc for the underlying projection */ - - -PJ *PROJECTION(utm) { - long zone; - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor (P, ENOMEM); - P->opaque = Q; - - if (P->es == 0.0) { - proj_errno_set(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); - return pj_default_destructor(P, ENOMEM); - } - if (P->lam0 < -1000.0 || P->lam0 > 1000.0) { - return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); - } - - P->y0 = pj_param (P->ctx, P->params, "bsouth").i ? 10000000. : 0.; - P->x0 = 500000.; - if (pj_param (P->ctx, P->params, "tzone").i) /* zone input ? */ - { - zone = pj_param(P->ctx, P->params, "izone").i; - if (zone > 0 && zone <= 60) - --zone; - else { - return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); - } - } - else /* nearest central meridian input */ - { - zone = lround((floor ((adjlon (P->lam0) + M_PI) * 30. / M_PI))); - if (zone < 0) - zone = 0; - else if (zone >= 60) - zone = 59; - } - P->lam0 = (zone + .5) * M_PI / 30. - M_PI; - P->k0 = 0.9996; - P->phi0 = 0.; - - return setup (P); -} diff --git a/src/proj_etmerc.cpp b/src/proj_etmerc.cpp new file mode 100644 index 00000000..0ba710d7 --- /dev/null +++ b/src/proj_etmerc.cpp @@ -0,0 +1,360 @@ +/* +** libproj -- library of cartographic projections +** +** Copyright (c) 2008 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. +*/ + +/* The code in this file is largly based upon procedures: + * + * Written by: Knud Poder and Karsten Engsager + * + * Based on math from: R.Koenig and K.H. Weise, "Mathematische + * Grundlagen der hoeheren Geodaesie und Kartographie, + * Springer-Verlag, Berlin/Goettingen" Heidelberg, 1951. + * + * Modified and used here by permission of Reference Networks + * Division, Kort og Matrikelstyrelsen (KMS), Copenhagen, Denmark + * +*/ + +#define PJ_LIB__ + +#include + +#include "proj.h" +#include "projects.h" +#include "proj_math.h" + + +struct pj_opaque { + double Qn; /* Merid. quad., scaled to the projection */ \ + double Zb; /* Radius vector in polar coord. systems */ \ + double cgb[6]; /* Constants for Gauss -> Geo lat */ \ + double cbg[6]; /* Constants for Geo lat -> Gauss */ \ + double utg[6]; /* Constants for transv. merc. -> geo */ \ + double gtu[6]; /* Constants for geo -> transv. merc. */ +}; + +PROJ_HEAD(etmerc, "Extended Transverse Mercator") + "\n\tCyl, Sph\n\tlat_ts=(0)\nlat_0=(0)"; +PROJ_HEAD(utm, "Universal Transverse Mercator (UTM)") + "\n\tCyl, Sph\n\tzone= south"; + +#define PROJ_ETMERC_ORDER 6 + +#ifdef _GNU_SOURCE + inline +#endif +static double gatg(double *p1, int len_p1, double B) { + double *p; + double h = 0, h1, h2 = 0, cos_2B; + + cos_2B = 2*cos(2*B); + p = p1 + len_p1; + h1 = *--p; + while (p - p1) { + h = -h2 + cos_2B*h1 + *--p; + h2 = h1; + h1 = h; + } + return (B + h*sin(2*B)); +} + +/* Complex Clenshaw summation */ +#ifdef _GNU_SOURCE + inline +#endif +static double clenS(double *a, int size, double arg_r, double arg_i, double *R, double *I) { + double *p, r, i, hr, hr1, hr2, hi, hi1, hi2; + double sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i; + + /* arguments */ + p = a + size; +#ifdef _GNU_SOURCE + sincos(arg_r, &sin_arg_r, &cos_arg_r); +#else + sin_arg_r = sin(arg_r); + cos_arg_r = cos(arg_r); +#endif + sinh_arg_i = sinh(arg_i); + cosh_arg_i = cosh(arg_i); + r = 2*cos_arg_r*cosh_arg_i; + i = -2*sin_arg_r*sinh_arg_i; + + /* summation loop */ + hi1 = hr1 = hi = 0; + hr = *--p; + for (; a - p;) { + hr2 = hr1; + hi2 = hi1; + hr1 = hr; + hi1 = hi; + hr = -hr2 + r*hr1 - i*hi1 + *--p; + hi = -hi2 + i*hr1 + r*hi1; + } + + r = sin_arg_r*cosh_arg_i; + i = cos_arg_r*sinh_arg_i; + *R = r*hr - i*hi; + *I = r*hi + i*hr; + return *R; +} + + +/* Real Clenshaw summation */ +static double clens(double *a, int size, double arg_r) { + double *p, r, hr, hr1, hr2, cos_arg_r; + + p = a + size; + cos_arg_r = cos(arg_r); + r = 2*cos_arg_r; + + /* summation loop */ + hr1 = 0; + hr = *--p; + for (; a - p;) { + hr2 = hr1; + hr1 = hr; + hr = -hr2 + r*hr1 + *--p; + } + return sin (arg_r)*hr; +} + + + +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 sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; + double Cn = lp.phi, Ce = lp.lam; + + /* ell. LAT, LNG -> Gaussian LAT, LNG */ + Cn = gatg (Q->cbg, PROJ_ETMERC_ORDER, Cn); + /* Gaussian LAT, LNG -> compl. sph. LAT */ +#ifdef _GNU_SOURCE + sincos (Cn, &sin_Cn, &cos_Cn); + sincos (Ce, &sin_Ce, &cos_Ce); +#else + sin_Cn = sin (Cn); + cos_Cn = cos (Cn); + sin_Ce = sin (Ce); + cos_Ce = cos (Ce); +#endif + + Cn = atan2 (sin_Cn, cos_Ce*cos_Cn); + Ce = atan2 (sin_Ce*cos_Cn, hypot (sin_Cn, cos_Cn*cos_Ce)); + + /* compl. sph. N, E -> ell. norm. N, E */ + Ce = asinh ( tan (Ce) ); /* Replaces: Ce = log(tan(FORTPI + Ce*0.5)); */ + Cn += clenS (Q->gtu, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); + Ce += dCe; + if (fabs (Ce) <= 2.623395162778) { + xy.y = Q->Qn * Cn + Q->Zb; /* Northing */ + xy.x = Q->Qn * Ce; /* Easting */ + } 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); + double sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe; + double Cn = xy.y, Ce = xy.x; + + /* normalize N, E */ + Cn = (Cn - Q->Zb)/Q->Qn; + Ce = Ce/Q->Qn; + + if (fabs(Ce) <= 2.623395162778) { /* 150 degrees */ + /* norm. N, E -> compl. sph. LAT, LNG */ + Cn += clenS(Q->utg, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe); + Ce += dCe; + Ce = atan (sinh (Ce)); /* Replaces: Ce = 2*(atan(exp(Ce)) - FORTPI); */ + /* compl. sph. LAT -> Gaussian LAT, LNG */ +#ifdef _GNU_SOURCE + sincos (Cn, &sin_Cn, &cos_Cn); + sincos (Ce, &sin_Ce, &cos_Ce); +#else + sin_Cn = sin (Cn); + cos_Cn = cos (Cn); + sin_Ce = sin (Ce); + cos_Ce = cos (Ce); +#endif + Ce = atan2 (sin_Ce, cos_Ce*cos_Cn); + Cn = atan2 (sin_Cn*cos_Ce, hypot (sin_Ce, cos_Ce*cos_Cn)); + /* Gaussian LAT, LNG -> ell. LAT, LNG */ + lp.phi = gatg (Q->cgb, PROJ_ETMERC_ORDER, Cn); + lp.lam = Ce; + } + else + lp.phi = lp.lam = HUGE_VAL; + return lp; +} + + +static PJ *setup(PJ *P) { /* general initialization */ + double f, n, np, Z; + struct pj_opaque *Q = static_cast(P->opaque); + + if (P->es <= 0) { + return pj_default_destructor(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + } + + /* flattening */ + f = P->es / (1 + sqrt (1 - P->es)); /* Replaces: f = 1 - sqrt(1-P->es); */ + + /* third flattening */ + np = n = f/(2 - f); + + /* COEF. OF TRIG SERIES GEO <-> GAUSS */ + /* cgb := Gaussian -> Geodetic, KW p190 - 191 (61) - (62) */ + /* cbg := Geodetic -> Gaussian, KW p186 - 187 (51) - (52) */ + /* PROJ_ETMERC_ORDER = 6th degree : Engsager and Poder: ICC2007 */ + + Q->cgb[0] = n*( 2 + n*(-2/3.0 + n*(-2 + n*(116/45.0 + n*(26/45.0 + + n*(-2854/675.0 )))))); + Q->cbg[0] = n*(-2 + n*( 2/3.0 + n*( 4/3.0 + n*(-82/45.0 + n*(32/45.0 + + n*( 4642/4725.0)))))); + np *= n; + Q->cgb[1] = np*(7/3.0 + n*( -8/5.0 + n*(-227/45.0 + n*(2704/315.0 + + n*( 2323/945.0))))); + Q->cbg[1] = np*(5/3.0 + n*(-16/15.0 + n*( -13/9.0 + n*( 904/315.0 + + n*(-1522/945.0))))); + np *= n; + /* n^5 coeff corrected from 1262/105 -> -1262/105 */ + Q->cgb[2] = np*( 56/15.0 + n*(-136/35.0 + n*(-1262/105.0 + + n*( 73814/2835.0)))); + Q->cbg[2] = np*(-26/15.0 + n*( 34/21.0 + n*( 8/5.0 + + n*(-12686/2835.0)))); + np *= n; + /* n^5 coeff corrected from 322/35 -> 332/35 */ + Q->cgb[3] = np*(4279/630.0 + n*(-332/35.0 + n*(-399572/14175.0))); + Q->cbg[3] = np*(1237/630.0 + n*( -12/5.0 + n*( -24832/14175.0))); + np *= n; + Q->cgb[4] = np*(4174/315.0 + n*(-144838/6237.0 )); + Q->cbg[4] = np*(-734/315.0 + n*( 109598/31185.0)); + np *= n; + Q->cgb[5] = np*(601676/22275.0 ); + Q->cbg[5] = np*(444337/155925.0); + + /* Constants of the projections */ + /* Transverse Mercator (UTM, ITM, etc) */ + np = n*n; + /* Norm. mer. quad, K&W p.50 (96), p.19 (38b), p.5 (2) */ + Q->Qn = P->k0/(1 + n) * (1 + np*(1/4.0 + np*(1/64.0 + np/256.0))); + /* coef of trig series */ + /* utg := ell. N, E -> sph. N, E, KW p194 (65) */ + /* gtu := sph. N, E -> ell. N, E, KW p196 (69) */ + Q->utg[0] = n*(-0.5 + n*( 2/3.0 + n*(-37/96.0 + n*( 1/360.0 + + n*( 81/512.0 + n*(-96199/604800.0)))))); + Q->gtu[0] = n*( 0.5 + n*(-2/3.0 + n*( 5/16.0 + n*(41/180.0 + + n*(-127/288.0 + n*( 7891/37800.0 )))))); + Q->utg[1] = np*(-1/48.0 + n*(-1/15.0 + n*(437/1440.0 + n*(-46/105.0 + + n*( 1118711/3870720.0))))); + Q->gtu[1] = np*(13/48.0 + n*(-3/5.0 + n*(557/1440.0 + n*(281/630.0 + + n*(-1983433/1935360.0))))); + np *= n; + Q->utg[2] = np*(-17/480.0 + n*( 37/840.0 + n*( 209/4480.0 + + n*( -5569/90720.0 )))); + Q->gtu[2] = np*( 61/240.0 + n*(-103/140.0 + n*(15061/26880.0 + + n*(167603/181440.0)))); + np *= n; + Q->utg[3] = np*(-4397/161280.0 + n*( 11/504.0 + n*( 830251/7257600.0))); + Q->gtu[3] = np*(49561/161280.0 + n*(-179/168.0 + n*(6601661/7257600.0))); + np *= n; + Q->utg[4] = np*(-4583/161280.0 + n*( 108847/3991680.0)); + Q->gtu[4] = np*(34729/80640.0 + n*(-3418889/1995840.0)); + np *= n; + Q->utg[5] = np*(-20648693/638668800.0); + Q->gtu[5] = np*(212378941/319334400.0); + + /* Gaussian latitude value of the origin latitude */ + Z = gatg (Q->cbg, PROJ_ETMERC_ORDER, P->phi0); + + /* Origin northing minus true northing at the origin latitude */ + /* i.e. true northing = N - P->Zb */ + Q->Zb = - Q->Qn*(Z + clens(Q->gtu, PROJ_ETMERC_ORDER, 2*Z)); + P->inv = e_inverse; + P->fwd = e_forward; + return P; +} + + + +PJ *PROJECTION(etmerc) { + struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); + if (0==Q) + return pj_default_destructor (P, ENOMEM); + P->opaque = Q; + return setup (P); +} + + + +/* utm uses etmerc for the underlying projection */ + + +PJ *PROJECTION(utm) { + long zone; + struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); + if (0==Q) + return pj_default_destructor (P, ENOMEM); + P->opaque = Q; + + if (P->es == 0.0) { + proj_errno_set(P, PJD_ERR_ELLIPSOID_USE_REQUIRED); + return pj_default_destructor(P, ENOMEM); + } + if (P->lam0 < -1000.0 || P->lam0 > 1000.0) { + return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); + } + + P->y0 = pj_param (P->ctx, P->params, "bsouth").i ? 10000000. : 0.; + P->x0 = 500000.; + if (pj_param (P->ctx, P->params, "tzone").i) /* zone input ? */ + { + zone = pj_param(P->ctx, P->params, "izone").i; + if (zone > 0 && zone <= 60) + --zone; + else { + return pj_default_destructor(P, PJD_ERR_INVALID_UTM_ZONE); + } + } + else /* nearest central meridian input */ + { + zone = lround((floor ((adjlon (P->lam0) + M_PI) * 30. / M_PI))); + if (zone < 0) + zone = 0; + else if (zone >= 60) + zone = 59; + } + P->lam0 = (zone + .5) * M_PI / 30. - M_PI; + P->k0 = 0.9996; + P->phi0 = 0.; + + return setup (P); +} diff --git a/src/proj_mdist.c b/src/proj_mdist.c deleted file mode 100644 index 777f704d..00000000 --- a/src/proj_mdist.c +++ /dev/null @@ -1,127 +0,0 @@ -/* -** libproj -- library of cartographic projections -** -** 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. -*/ -/* Computes distance from equator along the meridian to latitude phi -** and inverse on unit ellipsoid. -** Precision commensurate with double precision. -*/ -#define PJ_LIB__ - -#include -#include - -#include "projects.h" - -#define MAX_ITER 20 -#define TOL 1e-14 - -struct MDIST { - int nb; - double es; - double E; - double b[1]; -}; - void * -proj_mdist_ini(double es) { - double numf, numfi, twon1, denf, denfi, ens, T, twon; - double den, El, Es; - double E[MAX_ITER]; - struct MDIST *b; - int i, j; - -/* generate E(e^2) and its terms E[] */ - ens = es; - numf = twon1 = denfi = 1.; - denf = 1.; - twon = 4.; - Es = El = E[0] = 1.; - for (i = 1; i < MAX_ITER ; ++i) { - numf *= (twon1 * twon1); - den = twon * denf * denf * twon1; - T = numf/den; - Es -= (E[i] = T * ens); - ens *= es; - twon *= 4.; - denf *= ++denfi; - twon1 += 2.; - if (Es == El) /* jump out if no change */ - break; - El = Es; - } - if ((b = (struct MDIST *)malloc(sizeof(struct MDIST)+ - (i*sizeof(double)))) == NULL) - return(NULL); - b->nb = i - 1; - b->es = es; - b->E = Es; - /* generate b_n coefficients--note: collapse with prefix ratios */ - b->b[0] = Es = 1. - Es; - numf = denf = 1.; - numfi = 2.; - denfi = 3.; - for (j = 1; j < i; ++j) { - Es -= E[j]; - numf *= numfi; - denf *= denfi; - b->b[j] = Es * numf / denf; - numfi += 2.; - denfi += 2.; - } - return (b); -} - double -proj_mdist(double phi, double sphi, double cphi, const void *data) { - const struct MDIST *b = (const struct MDIST *)data; - double sc, sum, sphi2, D; - int i; - - sc = sphi * cphi; - sphi2 = sphi * sphi; - D = phi * b->E - b->es * sc / sqrt(1. - b->es * sphi2); - sum = b->b[i = b->nb]; - while (i) sum = b->b[--i] + sphi2 * sum; - return(D + sc * sum); -} - double -proj_inv_mdist(projCtx ctx, double dist, const void *data) { - const struct MDIST *b = (const struct MDIST *)data; - double s, t, phi, k; - int i; - - k = 1./(1.- b->es); - i = MAX_ITER; - phi = dist; - while ( i-- ) { - s = sin(phi); - t = 1. - b->es * s * s; - phi -= t = (proj_mdist(phi, s, cos(phi), b) - dist) * - (t * sqrt(t)) * k; - if (fabs(t) < TOL) /* that is no change */ - return phi; - } - /* convergence failed */ - pj_ctx_set_errno(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); - return phi; -} diff --git a/src/proj_mdist.cpp b/src/proj_mdist.cpp new file mode 100644 index 00000000..777f704d --- /dev/null +++ b/src/proj_mdist.cpp @@ -0,0 +1,127 @@ +/* +** libproj -- library of cartographic projections +** +** 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. +*/ +/* Computes distance from equator along the meridian to latitude phi +** and inverse on unit ellipsoid. +** Precision commensurate with double precision. +*/ +#define PJ_LIB__ + +#include +#include + +#include "projects.h" + +#define MAX_ITER 20 +#define TOL 1e-14 + +struct MDIST { + int nb; + double es; + double E; + double b[1]; +}; + void * +proj_mdist_ini(double es) { + double numf, numfi, twon1, denf, denfi, ens, T, twon; + double den, El, Es; + double E[MAX_ITER]; + struct MDIST *b; + int i, j; + +/* generate E(e^2) and its terms E[] */ + ens = es; + numf = twon1 = denfi = 1.; + denf = 1.; + twon = 4.; + Es = El = E[0] = 1.; + for (i = 1; i < MAX_ITER ; ++i) { + numf *= (twon1 * twon1); + den = twon * denf * denf * twon1; + T = numf/den; + Es -= (E[i] = T * ens); + ens *= es; + twon *= 4.; + denf *= ++denfi; + twon1 += 2.; + if (Es == El) /* jump out if no change */ + break; + El = Es; + } + if ((b = (struct MDIST *)malloc(sizeof(struct MDIST)+ + (i*sizeof(double)))) == NULL) + return(NULL); + b->nb = i - 1; + b->es = es; + b->E = Es; + /* generate b_n coefficients--note: collapse with prefix ratios */ + b->b[0] = Es = 1. - Es; + numf = denf = 1.; + numfi = 2.; + denfi = 3.; + for (j = 1; j < i; ++j) { + Es -= E[j]; + numf *= numfi; + denf *= denfi; + b->b[j] = Es * numf / denf; + numfi += 2.; + denfi += 2.; + } + return (b); +} + double +proj_mdist(double phi, double sphi, double cphi, const void *data) { + const struct MDIST *b = (const struct MDIST *)data; + double sc, sum, sphi2, D; + int i; + + sc = sphi * cphi; + sphi2 = sphi * sphi; + D = phi * b->E - b->es * sc / sqrt(1. - b->es * sphi2); + sum = b->b[i = b->nb]; + while (i) sum = b->b[--i] + sphi2 * sum; + return(D + sc * sum); +} + double +proj_inv_mdist(projCtx ctx, double dist, const void *data) { + const struct MDIST *b = (const struct MDIST *)data; + double s, t, phi, k; + int i; + + k = 1./(1.- b->es); + i = MAX_ITER; + phi = dist; + while ( i-- ) { + s = sin(phi); + t = 1. - b->es * s * s; + phi -= t = (proj_mdist(phi, s, cos(phi), b) - dist) * + (t * sqrt(t)) * k; + if (fabs(t) < TOL) /* that is no change */ + return phi; + } + /* convergence failed */ + pj_ctx_set_errno(ctx, PJD_ERR_NON_CONV_INV_MERI_DIST); + return phi; +} diff --git a/src/proj_rouss.c b/src/proj_rouss.c deleted file mode 100644 index 0e0f9982..00000000 --- a/src/proj_rouss.c +++ /dev/null @@ -1,156 +0,0 @@ -/* -** libproj -- library of cartographic projections -** -** 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" - -struct pj_opaque { - double s0; - double A1, A2, A3, A4, A5, A6; - double B1, B2, B3, B4, B5, B6, B7, B8; - double C1, C2, C3, C4, C5, C6, C7, C8; - double D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11; - void *en; -}; -PROJ_HEAD(rouss, "Roussilhe Stereographic") "\n\tAzi, Ell"; - - -static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ - XY xy = {0.0,0.0}; - struct pj_opaque *Q = P->opaque; - double s, al, cp, sp, al2, s2; - - cp = cos(lp.phi); - sp = sin(lp.phi); - s = proj_mdist(lp.phi, sp, cp, Q->en) - Q->s0; - s2 = s * s; - al = lp.lam * cp / sqrt(1. - P->es * sp * sp); - al2 = al * al; - xy.x = P->k0 * al*(1.+s2*(Q->A1+s2*Q->A4)-al2*(Q->A2+s*Q->A3+s2*Q->A5 - +al2*Q->A6)); - xy.y = P->k0 * (al2*(Q->B1+al2*Q->B4)+ - s*(1.+al2*(Q->B3-al2*Q->B6)+s2*(Q->B2+s2*Q->B8)+ - s*al2*(Q->B5+s*Q->B7))); - - return xy; -} - - -static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ - LP lp = {0.0,0.0}; - struct pj_opaque *Q = P->opaque; - double s, al, x = xy.x / P->k0, y = xy.y / P->k0, x2, y2;; - - x2 = x * x; - y2 = y * y; - al = x*(1.-Q->C1*y2+x2*(Q->C2+Q->C3*y-Q->C4*x2+Q->C5*y2-Q->C7*x2*y) - +y2*(Q->C6*y2-Q->C8*x2*y)); - s = Q->s0 + y*(1.+y2*(-Q->D2+Q->D8*y2))+ - x2*(-Q->D1+y*(-Q->D3+y*(-Q->D5+y*(-Q->D7+y*Q->D11)))+ - x2*(Q->D4+y*(Q->D6+y*Q->D10)-x2*Q->D9)); - lp.phi=proj_inv_mdist(P->ctx, s, Q->en); - s = sin(lp.phi); - lp.lam=al * sqrt(1. - P->es * s * s)/cos(lp.phi); - - return lp; -} - - -static void *destructor (PJ *P, int errlev) { - if (0==P) - return 0; - - if (0==P->opaque) - return pj_default_destructor (P, errlev); - - if (P->opaque->en) - pj_dealloc (P->opaque->en); - - return pj_default_destructor (P, ENOMEM); -} - - -PJ *PROJECTION(rouss) { - double N0, es2, t, t2, R_R0_2, R_R0_4; - - struct pj_opaque *Q = pj_calloc (1, sizeof (struct pj_opaque)); - if (0==Q) - return pj_default_destructor(P, ENOMEM); - P->opaque = Q; - - if (!((Q->en = proj_mdist_ini(P->es)))) - return pj_default_destructor (P, ENOMEM); - - es2 = sin(P->phi0); - Q->s0 = proj_mdist(P->phi0, es2, cos(P->phi0), Q->en); - t = 1. - (es2 = P->es * es2 * es2); - N0 = 1./sqrt(t); - R_R0_2 = t * t / P->one_es; - R_R0_4 = R_R0_2 * R_R0_2; - t = tan(P->phi0); - t2 = t * t; - Q->C1 = Q->A1 = R_R0_2 / 4.; - Q->C2 = Q->A2 = R_R0_2 * (2 * t2 - 1. - 2. * es2) / 12.; - Q->A3 = R_R0_2 * t * (1. + 4. * t2)/ ( 12. * N0); - Q->A4 = R_R0_4 / 24.; - Q->A5 = R_R0_4 * ( -1. + t2 * (11. + 12. * t2))/24.; - Q->A6 = R_R0_4 * ( -2. + t2 * (11. - 2. * t2))/240.; - Q->B1 = t / (2. * N0); - Q->B2 = R_R0_2 / 12.; - Q->B3 = R_R0_2 * (1. + 2. * t2 - 2. * es2)/4.; - Q->B4 = R_R0_2 * t * (2. - t2)/(24. * N0); - Q->B5 = R_R0_2 * t * (5. + 4.* t2)/(8. * N0); - Q->B6 = R_R0_4 * (-2. + t2 * (-5. + 6. * t2))/48.; - Q->B7 = R_R0_4 * (5. + t2 * (19. + 12. * t2))/24.; - Q->B8 = R_R0_4 / 120.; - Q->C3 = R_R0_2 * t * (1. + t2)/(3. * N0); - Q->C4 = R_R0_4 * (-3. + t2 * (34. + 22. * t2))/240.; - Q->C5 = R_R0_4 * (4. + t2 * (13. + 12. * t2))/24.; - Q->C6 = R_R0_4 / 16.; - Q->C7 = R_R0_4 * t * (11. + t2 * (33. + t2 * 16.))/(48. * N0); - Q->C8 = R_R0_4 * t * (1. + t2 * 4.)/(36. * N0); - Q->D1 = t / (2. * N0); - Q->D2 = R_R0_2 / 12.; - Q->D3 = R_R0_2 * (2 * t2 + 1. - 2. * es2) / 4.; - Q->D4 = R_R0_2 * t * (1. + t2)/(8. * N0); - Q->D5 = R_R0_2 * t * (1. + t2 * 2.)/(4. * N0); - Q->D6 = R_R0_4 * (1. + t2 * (6. + t2 * 6.))/16.; - Q->D7 = R_R0_4 * t2 * (3. + t2 * 4.)/8.; - Q->D8 = R_R0_4 / 80.; - Q->D9 = R_R0_4 * t * (-21. + t2 * (178. - t2 * 26.))/720.; - Q->D10 = R_R0_4 * t * (29. + t2 * (86. + t2 * 48.))/(96. * N0); - Q->D11 = R_R0_4 * t * (37. + t2 * 44.)/(96. * N0); - - P->fwd = e_forward; - P->inv = e_inverse; - P->destructor = destructor; - - return P; -} diff --git a/src/proj_rouss.cpp b/src/proj_rouss.cpp new file mode 100644 index 00000000..f39e0a15 --- /dev/null +++ b/src/proj_rouss.cpp @@ -0,0 +1,156 @@ +/* +** libproj -- library of cartographic projections +** +** 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" + +struct pj_opaque { + double s0; + double A1, A2, A3, A4, A5, A6; + double B1, B2, B3, B4, B5, B6, B7, B8; + double C1, C2, C3, C4, C5, C6, C7, C8; + double D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11; + void *en; +}; +PROJ_HEAD(rouss, "Roussilhe Stereographic") "\n\tAzi, 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 s, al, cp, sp, al2, s2; + + cp = cos(lp.phi); + sp = sin(lp.phi); + s = proj_mdist(lp.phi, sp, cp, Q->en) - Q->s0; + s2 = s * s; + al = lp.lam * cp / sqrt(1. - P->es * sp * sp); + al2 = al * al; + xy.x = P->k0 * al*(1.+s2*(Q->A1+s2*Q->A4)-al2*(Q->A2+s*Q->A3+s2*Q->A5 + +al2*Q->A6)); + xy.y = P->k0 * (al2*(Q->B1+al2*Q->B4)+ + s*(1.+al2*(Q->B3-al2*Q->B6)+s2*(Q->B2+s2*Q->B8)+ + s*al2*(Q->B5+s*Q->B7))); + + 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 s, al, x = xy.x / P->k0, y = xy.y / P->k0, x2, y2;; + + x2 = x * x; + y2 = y * y; + al = x*(1.-Q->C1*y2+x2*(Q->C2+Q->C3*y-Q->C4*x2+Q->C5*y2-Q->C7*x2*y) + +y2*(Q->C6*y2-Q->C8*x2*y)); + s = Q->s0 + y*(1.+y2*(-Q->D2+Q->D8*y2))+ + x2*(-Q->D1+y*(-Q->D3+y*(-Q->D5+y*(-Q->D7+y*Q->D11)))+ + x2*(Q->D4+y*(Q->D6+y*Q->D10)-x2*Q->D9)); + lp.phi=proj_inv_mdist(P->ctx, s, Q->en); + s = sin(lp.phi); + lp.lam=al * sqrt(1. - P->es * s * s)/cos(lp.phi); + + return lp; +} + + +static PJ *destructor (PJ *P, int errlev) { + if (0==P) + return 0; + + if (0==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, ENOMEM); +} + + +PJ *PROJECTION(rouss) { + double N0, es2, t, t2, R_R0_2, R_R0_4; + + struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); + if (0==Q) + return pj_default_destructor(P, ENOMEM); + P->opaque = Q; + + if (!((Q->en = proj_mdist_ini(P->es)))) + return pj_default_destructor (P, ENOMEM); + + es2 = sin(P->phi0); + Q->s0 = proj_mdist(P->phi0, es2, cos(P->phi0), Q->en); + t = 1. - (es2 = P->es * es2 * es2); + N0 = 1./sqrt(t); + R_R0_2 = t * t / P->one_es; + R_R0_4 = R_R0_2 * R_R0_2; + t = tan(P->phi0); + t2 = t * t; + Q->C1 = Q->A1 = R_R0_2 / 4.; + Q->C2 = Q->A2 = R_R0_2 * (2 * t2 - 1. - 2. * es2) / 12.; + Q->A3 = R_R0_2 * t * (1. + 4. * t2)/ ( 12. * N0); + Q->A4 = R_R0_4 / 24.; + Q->A5 = R_R0_4 * ( -1. + t2 * (11. + 12. * t2))/24.; + Q->A6 = R_R0_4 * ( -2. + t2 * (11. - 2. * t2))/240.; + Q->B1 = t / (2. * N0); + Q->B2 = R_R0_2 / 12.; + Q->B3 = R_R0_2 * (1. + 2. * t2 - 2. * es2)/4.; + Q->B4 = R_R0_2 * t * (2. - t2)/(24. * N0); + Q->B5 = R_R0_2 * t * (5. + 4.* t2)/(8. * N0); + Q->B6 = R_R0_4 * (-2. + t2 * (-5. + 6. * t2))/48.; + Q->B7 = R_R0_4 * (5. + t2 * (19. + 12. * t2))/24.; + Q->B8 = R_R0_4 / 120.; + Q->C3 = R_R0_2 * t * (1. + t2)/(3. * N0); + Q->C4 = R_R0_4 * (-3. + t2 * (34. + 22. * t2))/240.; + Q->C5 = R_R0_4 * (4. + t2 * (13. + 12. * t2))/24.; + Q->C6 = R_R0_4 / 16.; + Q->C7 = R_R0_4 * t * (11. + t2 * (33. + t2 * 16.))/(48. * N0); + Q->C8 = R_R0_4 * t * (1. + t2 * 4.)/(36. * N0); + Q->D1 = t / (2. * N0); + Q->D2 = R_R0_2 / 12.; + Q->D3 = R_R0_2 * (2 * t2 + 1. - 2. * es2) / 4.; + Q->D4 = R_R0_2 * t * (1. + t2)/(8. * N0); + Q->D5 = R_R0_2 * t * (1. + t2 * 2.)/(4. * N0); + Q->D6 = R_R0_4 * (1. + t2 * (6. + t2 * 6.))/16.; + Q->D7 = R_R0_4 * t2 * (3. + t2 * 4.)/8.; + Q->D8 = R_R0_4 / 80.; + Q->D9 = R_R0_4 * t * (-21. + t2 * (178. - t2 * 26.))/720.; + Q->D10 = R_R0_4 * t * (29. + t2 * (86. + t2 * 48.))/(96. * N0); + Q->D11 = R_R0_4 * t * (37. + t2 * 44.)/(96. * N0); + + P->fwd = e_forward; + P->inv = e_inverse; + P->destructor = destructor; + + return P; +} diff --git a/src/proj_strtod.c b/src/proj_strtod.c deleted file mode 100644 index a3bc7d40..00000000 --- a/src/proj_strtod.c +++ /dev/null @@ -1,440 +0,0 @@ -/*********************************************************************** - - proj_strtod: Convert string to double, accepting underscore separators - - Thomas Knudsen, 2017-01-17/09-19 - -************************************************************************ - -Conventionally, PROJ.4 does not honor locale settings, consistently -behaving as if LC_ALL=C. - -For this to work, we have, for many years, been using other solutions -than the C standard library strtod/atof functions for converting strings -to doubles. - -In the early versions of proj, iirc, a gnu version of strtod was used, -mostly to work around cases where the same system library was used for -C and Fortran linking, hence making strtod accept "D" and "d" as -exponentiation indicators, following Fortran Double Precision constant -syntax. This broke the proj angular syntax, accepting a "d" to mean -"degree": 12d34'56", meaning 12 degrees 34 minutes and 56 seconds. - -With an explicit MIT licence, PROJ.4 could not include GPL code any -longer, and apparently at some time, the GPL code was replaced by the -current C port of a GDAL function (in pj_strtod.c), which reads the -LC_NUMERIC setting and, behind the back of the user, momentarily changes -the conventional '.' delimiter to whatever the locale requires, then -calls the system supplied strtod. - -While this requires a minimum amount of coding, it only solves one -problem, and not in a very generic way. - -Another problem, I would like to see solved, is the handling of underscores -as generic delimiters. This is getting popular in a number of programming -languages (Ada, C++, C#, D, Java, Julia, Perl 5, Python, Rust, etc. -cf. e.g. https://www.python.org/dev/peps/pep-0515/), and in our case of -handling numbers being in the order of magnitude of the Earth's dimensions, -and a resolution of submillimetre, i.e. having 10 or more significant digits, -splitting the "wall of digits" into smaller chunks is of immense value. - -Hence this reimplementation of strtod, which hardcodes '.' as indicator of -numeric fractions, and accepts '_' anywhere in a numerical string sequence: -So a typical northing value can be written - - 6_098_907.8250 m -rather than - 6098907.8250 m - -which, in my humble opinion, is well worth the effort. - -While writing this code, I took ample inspiration from Michael Ringgaard's -strtod version over at http://www.jbox.dk/sanos/source/lib/strtod.c.html, -and Yasuhiro Matsumoto's public domain version over at -https://gist.github.com/mattn/1890186. The code below is, however, not -copied from any of the two mentioned - it is a reimplementation, and -probably suffers from its own set of bugs. So for now, it is intended -not as a replacement of pj_strtod, but only as an experimental piece of -code for use in an experimental new transformation program, cct. - -************************************************************************ - -Thomas Knudsen, thokn@sdfe.dk, 2017-01-17/2017-09-18 - -************************************************************************ - -* Copyright (c) 2017 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. - -***********************************************************************/ - -#include "proj_strtod.h" - -#include /* for abs */ -#include /* for strchr */ -#include -#include -#include /* for HUGE_VAL */ -#include /* for pow() */ - - -double proj_strtod(const char *str, char **endptr) { - double number = 0, integral_part = 0; - int exponent = 0; - int fraction_is_nonzero = 0; - int sign = 0; - char *p = (char *) str; - int n = 0; - int num_digits_total = 0; - int num_digits_after_comma = 0; - int num_prefixed_zeros = 0; - - if (0==str) { - errno = EFAULT; - if (endptr) - *endptr = p; - return HUGE_VAL; - } - - /* First skip leading whitespace */ - while (isspace(*p)) - p++; - - /* Empty string? */ - if (0==*p) { - if (endptr) - *endptr = (char *) str; - return 0; - } - - /* non-numeric? */ - if (0==strchr("0123456789+-._", *p)) { - if (endptr) - *endptr = (char *) str; - return 0; - } - - /* Then handle optional prefixed sign and skip prefix zeros */ - switch (*p) { - case '-': - sign = -1; - p++; - break; - case '+': - sign = 1; - p++; - break; - default: - if (isdigit(*p) || '_'==*p || '.'==*p) - break; - if (endptr) - *endptr = (char *) str; - return 0; - } - - /* stray sign, as in "+/-"? */ - if (0!=sign && (0==strchr ("0123456789._", *p) || 0==*p)) { - if (endptr) - *endptr = (char *) str; - return 0; - } - - /* skip prefixed zeros before '.' */ - while ('0'==*p || '_'==*p) - p++; - - /* zero? */ - if ((0==*p) || 0==strchr ("0123456789eE.", *p) || isspace(*p)) { - if (endptr) - *endptr = p; - return sign==-1? -0: 0; - } - - /* Now expect a (potentially zero-length) string of digits */ - while (isdigit(*p) || ('_'==*p)) { - if ('_'==*p) { - p++; - continue; - } - number = number * 10. + (*p - '0'); - p++; - num_digits_total++; - } - integral_part = number; - - /* Done? */ - if (0==*p) { - if (endptr) - *endptr = p; - if (sign==-1) - return -number; - return number; - } - - /* Do we have a fractional part? */ - if ('.'==*p) { - p++; - - /* keep on skipping prefixed zeros (i.e. allow writing 1e-20 */ - /* as 0.00000000000000000001 without losing precision) */ - if (0==integral_part) - while ('0'==*p || '_'==*p) { - if ('0'==*p) - num_prefixed_zeros++; - p++; - } - - /* if the next character is nonnumeric, we have reached the end */ - if (0==*p || 0==strchr ("_0123456789eE+-", *p)) { - if (endptr) - *endptr = p; - if (sign==-1) - return -number; - return number; - } - - while (isdigit(*p) || '_'==*p) { - /* Don't let pathologically long fractions destroy precision */ - if ('_'==*p || num_digits_total > 17) { - p++; - continue; - } - - number = number * 10. + (*p - '0'); - if (*p!='0') - fraction_is_nonzero = 1; - p++; - num_digits_total++; - num_digits_after_comma++; - } - - /* Avoid having long zero-tails (4321.000...000) destroy precision */ - if (fraction_is_nonzero) - exponent = -(num_digits_after_comma + num_prefixed_zeros); - else - number = integral_part; - } /* end of fractional part */ - - - /* non-digit */ - if (0==num_digits_total) { - errno = EINVAL; - if (endptr) - *endptr = p; - return HUGE_VAL; - } - - if (sign==-1) - number = -number; - - /* Do we have an exponent part? */ - while (*p == 'e' || *p == 'E') { - p++; - - /* Just a stray "e", as in 100elephants? */ - if (0==*p || 0==strchr ("0123456789+-_", *p)) { - p--; - break; - } - - while ('_'==*p) - p++; - /* Does it have a sign? */ - sign = 0; - if ('-'==*p) - sign = -1; - if ('+'==*p) - sign = +1; - if (0==sign) { - if (!isdigit(*p) && *p!='_') { - if (endptr) - *endptr = p; - return HUGE_VAL; - } - } - else - p++; - - - /* Go on and read the exponent */ - n = 0; - while (isdigit(*p) || '_'==*p) { - if ('_'==*p) { - p++; - continue; - } - n = n * 10 + (*p - '0'); - p++; - } - - if (-1==sign) - n = -n; - exponent += n; - break; - } - - if (endptr) - *endptr = p; - - if ((exponent < DBL_MIN_EXP) || (exponent > DBL_MAX_EXP)) { - errno = ERANGE; - return HUGE_VAL; - } - - /* on some platforms pow() is very slow - so don't call it if exponent is close to 0 */ - if (0==exponent) - return number; - if (abs (exponent) < 20) { - double ex = 1; - int absexp = exponent < 0? -exponent: exponent; - while (absexp--) - ex *= 10; - number = exponent < 0? number / ex: number * ex; - } - else - number *= pow (10, exponent); - - return number; -} - -double proj_atof(const char *str) { - return proj_strtod(str, (void *) 0); -} - -#ifdef TEST - -/* compile/run: gcc -DTEST -o proj_strtod_test proj_strtod.c && proj_strtod_test */ - -#include - -char *un_underscore (char *s) { - static char u[1024]; - int i, m, n; - for (i = m = 0, n = strlen (s); i < n; i++) { - if (s[i]=='_') { - m++; - continue; - } - u[i - m] = s[i]; - } - u[n-m] = 0; - return u; -} - -int thetest (char *s, int line) { - char *endp, *endq, *u; - double p, q; - int errnop, errnoq, prev_errno; - - prev_errno = errno; - - u = un_underscore (s); - - errno = 0; - p = proj_strtod (s, &endp); - errnop = errno; - errno = 0; - q = strtod (u, &endq); - errnoq = errno; - - errno = prev_errno; - - if (q==p && 0==strcmp (endp, endq) && errnop==errnoq) - return 0; - - errno = line; - printf ("Line: %3.3d - [%s] [%s]\n", line, s, u); - printf ("proj_strtod: %2d %.17g [%s]\n", errnop, p, endp); - printf ("libc_strtod: %2d %.17g [%s]\n", errnoq, q, endq); - return 1; -} - -#define test(s) thetest(s, __LINE__) - -int main (int argc, char **argv) { - double res; - char *endptr; - - errno = 0; - - test (""); - test (" "); - test (" abcde"); - test (" edcba"); - test ("abcde"); - test ("edcba"); - test ("+"); - test ("-"); - test ("+ "); - test ("- "); - test (" + "); - test (" - "); - test ("e 1"); - test ("e1"); - test ("0 66"); - test ("1."); - test ("0."); - test ("1.0"); - test ("0.0"); - test ("1 "); - test ("0 "); - test ("-0 "); - test ("0_ "); - test ("0_"); - test ("1e"); - test ("_1.0"); - test ("_0.0"); - test ("1_.0"); - test ("0_.0"); - test ("1__.0"); - test ("0__.0"); - test ("1.__0"); - test ("0.__0"); - test ("1.0___"); - test ("0.0___"); - test ("1e2"); - test ("__123_456_789_._10_11_12"); - test ("1______"); - test ("1___e__2__"); - test ("-1"); - test ("-1.0"); - test ("-0"); - test ("-1e__-_2__rest"); - test ("0.00002"); - test ("0.00001"); - test ("-0.00002"); - test ("-0.00001"); - test ("-0.00001e-2"); - test ("-0.00001e2"); - test ("1e9999"); - - /* We expect this one to differ */ - test ("0.000000000000000000000000000000000000000000000000000000000000000000000000002"); - - if (errno) - printf ("First discrepancy in line %d\n", errno); - - if (argc < 2) - return 0; - res = proj_strtod (argv[1], &endptr); - printf ("res = %20.15g. Rest = [%s], errno = %d\n", res, endptr, (int) errno); - return 0; -} -#endif diff --git a/src/proj_strtod.cpp b/src/proj_strtod.cpp new file mode 100644 index 00000000..05d448ec --- /dev/null +++ b/src/proj_strtod.cpp @@ -0,0 +1,440 @@ +/*********************************************************************** + + proj_strtod: Convert string to double, accepting underscore separators + + Thomas Knudsen, 2017-01-17/09-19 + +************************************************************************ + +Conventionally, PROJ.4 does not honor locale settings, consistently +behaving as if LC_ALL=C. + +For this to work, we have, for many years, been using other solutions +than the C standard library strtod/atof functions for converting strings +to doubles. + +In the early versions of proj, iirc, a gnu version of strtod was used, +mostly to work around cases where the same system library was used for +C and Fortran linking, hence making strtod accept "D" and "d" as +exponentiation indicators, following Fortran Double Precision constant +syntax. This broke the proj angular syntax, accepting a "d" to mean +"degree": 12d34'56", meaning 12 degrees 34 minutes and 56 seconds. + +With an explicit MIT licence, PROJ.4 could not include GPL code any +longer, and apparently at some time, the GPL code was replaced by the +current C port of a GDAL function (in pj_strtod.c), which reads the +LC_NUMERIC setting and, behind the back of the user, momentarily changes +the conventional '.' delimiter to whatever the locale requires, then +calls the system supplied strtod. + +While this requires a minimum amount of coding, it only solves one +problem, and not in a very generic way. + +Another problem, I would like to see solved, is the handling of underscores +as generic delimiters. This is getting popular in a number of programming +languages (Ada, C++, C#, D, Java, Julia, Perl 5, Python, Rust, etc. +cf. e.g. https://www.python.org/dev/peps/pep-0515/), and in our case of +handling numbers being in the order of magnitude of the Earth's dimensions, +and a resolution of submillimetre, i.e. having 10 or more significant digits, +splitting the "wall of digits" into smaller chunks is of immense value. + +Hence this reimplementation of strtod, which hardcodes '.' as indicator of +numeric fractions, and accepts '_' anywhere in a numerical string sequence: +So a typical northing value can be written + + 6_098_907.8250 m +rather than + 6098907.8250 m + +which, in my humble opinion, is well worth the effort. + +While writing this code, I took ample inspiration from Michael Ringgaard's +strtod version over at http://www.jbox.dk/sanos/source/lib/strtod.c.html, +and Yasuhiro Matsumoto's public domain version over at +https://gist.github.com/mattn/1890186. The code below is, however, not +copied from any of the two mentioned - it is a reimplementation, and +probably suffers from its own set of bugs. So for now, it is intended +not as a replacement of pj_strtod, but only as an experimental piece of +code for use in an experimental new transformation program, cct. + +************************************************************************ + +Thomas Knudsen, thokn@sdfe.dk, 2017-01-17/2017-09-18 + +************************************************************************ + +* Copyright (c) 2017 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. + +***********************************************************************/ + +#include "proj_strtod.h" + +#include /* for abs */ +#include /* for strchr */ +#include +#include +#include /* for HUGE_VAL */ +#include /* for pow() */ + + +double proj_strtod(const char *str, char **endptr) { + double number = 0, integral_part = 0; + int exponent = 0; + int fraction_is_nonzero = 0; + int sign = 0; + char *p = (char *) str; + int n = 0; + int num_digits_total = 0; + int num_digits_after_comma = 0; + int num_prefixed_zeros = 0; + + if (0==str) { + errno = EFAULT; + if (endptr) + *endptr = p; + return HUGE_VAL; + } + + /* First skip leading whitespace */ + while (isspace(*p)) + p++; + + /* Empty string? */ + if (0==*p) { + if (endptr) + *endptr = (char *) str; + return 0; + } + + /* non-numeric? */ + if (0==strchr("0123456789+-._", *p)) { + if (endptr) + *endptr = (char *) str; + return 0; + } + + /* Then handle optional prefixed sign and skip prefix zeros */ + switch (*p) { + case '-': + sign = -1; + p++; + break; + case '+': + sign = 1; + p++; + break; + default: + if (isdigit(*p) || '_'==*p || '.'==*p) + break; + if (endptr) + *endptr = (char *) str; + return 0; + } + + /* stray sign, as in "+/-"? */ + if (0!=sign && (0==strchr ("0123456789._", *p) || 0==*p)) { + if (endptr) + *endptr = (char *) str; + return 0; + } + + /* skip prefixed zeros before '.' */ + while ('0'==*p || '_'==*p) + p++; + + /* zero? */ + if ((0==*p) || 0==strchr ("0123456789eE.", *p) || isspace(*p)) { + if (endptr) + *endptr = p; + return sign==-1? -0: 0; + } + + /* Now expect a (potentially zero-length) string of digits */ + while (isdigit(*p) || ('_'==*p)) { + if ('_'==*p) { + p++; + continue; + } + number = number * 10. + (*p - '0'); + p++; + num_digits_total++; + } + integral_part = number; + + /* Done? */ + if (0==*p) { + if (endptr) + *endptr = p; + if (sign==-1) + return -number; + return number; + } + + /* Do we have a fractional part? */ + if ('.'==*p) { + p++; + + /* keep on skipping prefixed zeros (i.e. allow writing 1e-20 */ + /* as 0.00000000000000000001 without losing precision) */ + if (0==integral_part) + while ('0'==*p || '_'==*p) { + if ('0'==*p) + num_prefixed_zeros++; + p++; + } + + /* if the next character is nonnumeric, we have reached the end */ + if (0==*p || 0==strchr ("_0123456789eE+-", *p)) { + if (endptr) + *endptr = p; + if (sign==-1) + return -number; + return number; + } + + while (isdigit(*p) || '_'==*p) { + /* Don't let pathologically long fractions destroy precision */ + if ('_'==*p || num_digits_total > 17) { + p++; + continue; + } + + number = number * 10. + (*p - '0'); + if (*p!='0') + fraction_is_nonzero = 1; + p++; + num_digits_total++; + num_digits_after_comma++; + } + + /* Avoid having long zero-tails (4321.000...000) destroy precision */ + if (fraction_is_nonzero) + exponent = -(num_digits_after_comma + num_prefixed_zeros); + else + number = integral_part; + } /* end of fractional part */ + + + /* non-digit */ + if (0==num_digits_total) { + errno = EINVAL; + if (endptr) + *endptr = p; + return HUGE_VAL; + } + + if (sign==-1) + number = -number; + + /* Do we have an exponent part? */ + while (*p == 'e' || *p == 'E') { + p++; + + /* Just a stray "e", as in 100elephants? */ + if (0==*p || 0==strchr ("0123456789+-_", *p)) { + p--; + break; + } + + while ('_'==*p) + p++; + /* Does it have a sign? */ + sign = 0; + if ('-'==*p) + sign = -1; + if ('+'==*p) + sign = +1; + if (0==sign) { + if (!isdigit(*p) && *p!='_') { + if (endptr) + *endptr = p; + return HUGE_VAL; + } + } + else + p++; + + + /* Go on and read the exponent */ + n = 0; + while (isdigit(*p) || '_'==*p) { + if ('_'==*p) { + p++; + continue; + } + n = n * 10 + (*p - '0'); + p++; + } + + if (-1==sign) + n = -n; + exponent += n; + break; + } + + if (endptr) + *endptr = p; + + if ((exponent < DBL_MIN_EXP) || (exponent > DBL_MAX_EXP)) { + errno = ERANGE; + return HUGE_VAL; + } + + /* on some platforms pow() is very slow - so don't call it if exponent is close to 0 */ + if (0==exponent) + return number; + if (abs (exponent) < 20) { + double ex = 1; + int absexp = exponent < 0? -exponent: exponent; + while (absexp--) + ex *= 10; + number = exponent < 0? number / ex: number * ex; + } + else + number *= pow (10, exponent); + + return number; +} + +double proj_atof(const char *str) { + return proj_strtod(str, nullptr); +} + +#ifdef TEST + +/* compile/run: gcc -DTEST -o proj_strtod_test proj_strtod.c && proj_strtod_test */ + +#include + +char *un_underscore (char *s) { + static char u[1024]; + int i, m, n; + for (i = m = 0, n = strlen (s); i < n; i++) { + if (s[i]=='_') { + m++; + continue; + } + u[i - m] = s[i]; + } + u[n-m] = 0; + return u; +} + +int thetest (char *s, int line) { + char *endp, *endq, *u; + double p, q; + int errnop, errnoq, prev_errno; + + prev_errno = errno; + + u = un_underscore (s); + + errno = 0; + p = proj_strtod (s, &endp); + errnop = errno; + errno = 0; + q = strtod (u, &endq); + errnoq = errno; + + errno = prev_errno; + + if (q==p && 0==strcmp (endp, endq) && errnop==errnoq) + return 0; + + errno = line; + printf ("Line: %3.3d - [%s] [%s]\n", line, s, u); + printf ("proj_strtod: %2d %.17g [%s]\n", errnop, p, endp); + printf ("libc_strtod: %2d %.17g [%s]\n", errnoq, q, endq); + return 1; +} + +#define test(s) thetest(s, __LINE__) + +int main (int argc, char **argv) { + double res; + char *endptr; + + errno = 0; + + test (""); + test (" "); + test (" abcde"); + test (" edcba"); + test ("abcde"); + test ("edcba"); + test ("+"); + test ("-"); + test ("+ "); + test ("- "); + test (" + "); + test (" - "); + test ("e 1"); + test ("e1"); + test ("0 66"); + test ("1."); + test ("0."); + test ("1.0"); + test ("0.0"); + test ("1 "); + test ("0 "); + test ("-0 "); + test ("0_ "); + test ("0_"); + test ("1e"); + test ("_1.0"); + test ("_0.0"); + test ("1_.0"); + test ("0_.0"); + test ("1__.0"); + test ("0__.0"); + test ("1.__0"); + test ("0.__0"); + test ("1.0___"); + test ("0.0___"); + test ("1e2"); + test ("__123_456_789_._10_11_12"); + test ("1______"); + test ("1___e__2__"); + test ("-1"); + test ("-1.0"); + test ("-0"); + test ("-1e__-_2__rest"); + test ("0.00002"); + test ("0.00001"); + test ("-0.00002"); + test ("-0.00001"); + test ("-0.00001e-2"); + test ("-0.00001e2"); + test ("1e9999"); + + /* We expect this one to differ */ + test ("0.000000000000000000000000000000000000000000000000000000000000000000000000002"); + + if (errno) + printf ("First discrepancy in line %d\n", errno); + + if (argc < 2) + return 0; + res = proj_strtod (argv[1], &endptr); + printf ("res = %20.15g. Rest = [%s], errno = %d\n", res, endptr, (int) errno); + return 0; +} +#endif diff --git a/src/projects.h b/src/projects.h index 11467d56..ac1a2152 100644 --- a/src/projects.h +++ b/src/projects.h @@ -197,7 +197,6 @@ struct PJconsts; union PJ_COORD; struct geod_geodesic; -struct pj_opaque; struct ARG_list; struct PJ_REGION_S; typedef struct PJ_REGION_S PJ_Region; @@ -260,7 +259,7 @@ PJ_OPERATOR: *****************************************************************************/ typedef PJ *(* PJ_CONSTRUCTOR) (PJ *); -typedef void *(* PJ_DESTRUCTOR) (PJ *, int); +typedef PJ *(* PJ_DESTRUCTOR) (PJ *, int); typedef PJ_COORD (* PJ_OPERATOR) (PJ_COORD, PJ *); /****************************************************************************/ @@ -290,7 +289,7 @@ struct PJconsts { char *def_ellps; struct geod_geodesic *geod; /* For geodesic computations */ - struct pj_opaque *opaque; /* Projection specific parameters, Defined in PJ_*.c */ + void *opaque; /* Projection specific parameters, Defined in PJ_*.c */ int inverted; /* Tell high level API functions to swap inv/fwd */ @@ -828,7 +827,7 @@ extern char const PROJ_DLL pj_release[]; struct PJ_DATUMS PROJ_DLL *pj_get_datums_ref( void ); -void *pj_default_destructor (PJ *P, int errlev); +PJ *pj_default_destructor (PJ *P, int errlev); double PROJ_DLL pj_atof( const char* nptr ); double pj_strtod( const char *nptr, char **endptr ); diff --git a/src/rtodms.c b/src/rtodms.c deleted file mode 100644 index 674cebdf..00000000 --- a/src/rtodms.c +++ /dev/null @@ -1,86 +0,0 @@ -/* Convert radian argument to DMS ascii format */ - -#include -#include -#include -#include - -#include "projects.h" - -/* -** RES is fractional second figures -** RES60 = 60 * RES -** CONV = 180 * 3600 * RES / PI (radians to RES seconds) -*/ - static double -RES = 1000., -RES60 = 60000., -CONV = 206264806.24709635516; - static char -format[50] = "%dd%d'%.3f\"%c"; - static int -dolong = 0; - void -set_rtodms(int fract, int con_w) { - int i; - - if (fract >= 0 && fract < 9 ) { - RES = 1.; - /* following not very elegant, but used infrequently */ - for (i = 0; i < fract; ++i) - RES *= 10.; - RES60 = RES * 60.; - CONV = 180. * 3600. * RES / M_PI; - if (! con_w) - (void)sprintf(format,"%%dd%%d'%%.%df\"%%c", fract); - else - (void)sprintf(format,"%%dd%%02d'%%0%d.%df\"%%c", - fract+2+(fract?1:0), fract); - dolong = con_w; - } -} - char * -rtodms(char *s, double r, int pos, int neg) { - int deg, min, sign; - char *ss = s; - double sec; - - if (r < 0) { - r = -r; - if (!pos) { *ss++ = '-'; sign = 0; } - else sign = neg; - } else - sign = pos; - r = floor(r * CONV + .5); - sec = fmod(r / RES, 60.); - r = floor(r / RES60); - min = (int)fmod(r, 60.); - r = floor(r / 60.); - deg = (int)r; - - if (dolong) - (void)sprintf(ss,format,deg,min,sec,sign); - else if (sec != 0.0) { - char *p, *q; - /* double prime + pos/neg suffix (if included) + NUL */ - size_t suffix_len = sign ? 3 : 2; - - (void)sprintf(ss,format,deg,min,sec,sign); - /* Replace potential decimal comma by decimal point for non C locale */ - for( p = ss; *p != '\0'; ++p ) { - if( *p == ',' ) { - *p = '.'; - break; - } - } - for (q = p = ss + strlen(ss) - suffix_len; *p == '0'; --p) ; - if (*p != '.') - ++p; - if (++q != p) - (void)memmove(p, q, suffix_len); - } else if (min) - (void)sprintf(ss,"%dd%d'%c",deg,min,sign); - else - (void)sprintf(ss,"%dd%c",deg, sign); - return s; -} diff --git a/src/rtodms.cpp b/src/rtodms.cpp new file mode 100644 index 00000000..674cebdf --- /dev/null +++ b/src/rtodms.cpp @@ -0,0 +1,86 @@ +/* Convert radian argument to DMS ascii format */ + +#include +#include +#include +#include + +#include "projects.h" + +/* +** RES is fractional second figures +** RES60 = 60 * RES +** CONV = 180 * 3600 * RES / PI (radians to RES seconds) +*/ + static double +RES = 1000., +RES60 = 60000., +CONV = 206264806.24709635516; + static char +format[50] = "%dd%d'%.3f\"%c"; + static int +dolong = 0; + void +set_rtodms(int fract, int con_w) { + int i; + + if (fract >= 0 && fract < 9 ) { + RES = 1.; + /* following not very elegant, but used infrequently */ + for (i = 0; i < fract; ++i) + RES *= 10.; + RES60 = RES * 60.; + CONV = 180. * 3600. * RES / M_PI; + if (! con_w) + (void)sprintf(format,"%%dd%%d'%%.%df\"%%c", fract); + else + (void)sprintf(format,"%%dd%%02d'%%0%d.%df\"%%c", + fract+2+(fract?1:0), fract); + dolong = con_w; + } +} + char * +rtodms(char *s, double r, int pos, int neg) { + int deg, min, sign; + char *ss = s; + double sec; + + if (r < 0) { + r = -r; + if (!pos) { *ss++ = '-'; sign = 0; } + else sign = neg; + } else + sign = pos; + r = floor(r * CONV + .5); + sec = fmod(r / RES, 60.); + r = floor(r / RES60); + min = (int)fmod(r, 60.); + r = floor(r / 60.); + deg = (int)r; + + if (dolong) + (void)sprintf(ss,format,deg,min,sec,sign); + else if (sec != 0.0) { + char *p, *q; + /* double prime + pos/neg suffix (if included) + NUL */ + size_t suffix_len = sign ? 3 : 2; + + (void)sprintf(ss,format,deg,min,sec,sign); + /* Replace potential decimal comma by decimal point for non C locale */ + for( p = ss; *p != '\0'; ++p ) { + if( *p == ',' ) { + *p = '.'; + break; + } + } + for (q = p = ss + strlen(ss) - suffix_len; *p == '0'; --p) ; + if (*p != '.') + ++p; + if (++q != p) + (void)memmove(p, q, suffix_len); + } else if (min) + (void)sprintf(ss,"%dd%d'%c",deg,min,sign); + else + (void)sprintf(ss,"%dd%c",deg, sign); + return s; +} diff --git a/src/test228.c b/src/test228.c deleted file mode 100644 index 83d29f8f..00000000 --- a/src/test228.c +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H -#endif - -#include "proj_api.h" -#include /* for printf declaration */ - - -#ifdef _WIN32 - -int main(int argc, char* argv[]) -{ - printf("Test not yet ported on Win32\n"); - return 0; -} - -#else - -#include -#include -#include -#include - -static volatile int run = 0; -static volatile int started = 0; - -static void* thread_main(void* unused) -{ - projCtx p_proj_ctxt; - projPJ p_WGS84_proj; - projPJ p_OSGB36_proj; - (void)unused; - - __sync_add_and_fetch(&started, 1); - while(run == 0); - - p_proj_ctxt=pj_ctx_alloc(); - p_WGS84_proj=pj_init_plus_ctx(p_proj_ctxt,"+proj=longlat " - "+ellps=WGS84 +datum=WGS84 +no_defs"); - p_OSGB36_proj=pj_init_plus_ctx(p_proj_ctxt, - "+proj=longlat +ellps=airy +datum=OSGB36 +nadgrids=OSTN02_NTv2.gsb " - "+no_defs"); - - while(run) - { - double x, y; - int proj_ret; - - x = -5.2*DEG_TO_RAD; - y = 50*DEG_TO_RAD; - proj_ret = pj_transform(p_WGS84_proj, - p_OSGB36_proj, 1, 1, &x, &y, NULL ); - x *= RAD_TO_DEG; - y *= RAD_TO_DEG; - /*printf("%.18f %.18f\n", x, y); */ - assert(proj_ret == 0); - assert(fabs(x - -5.198965360936369962) < 1e-15); - assert(fabs(y - 49.999396034285531698) < 1e-15); - } - - pj_free (p_OSGB36_proj); - pj_free (p_WGS84_proj); - return NULL; -} - -int main() -{ - int i; - - pthread_t tid1, tid2; - pthread_attr_t attr1, attr2; - - pthread_attr_init(&attr1); - pthread_attr_init(&attr2); - - pthread_create(&tid1, &attr1, thread_main, NULL); - pthread_create(&tid2, &attr2, thread_main, NULL); - while(started != 2); - run = 1; - for(i=0;i<2;i++) - sleep(1); - run = 0; - return 0; -} - -#endif /* _WIN32 */ diff --git a/src/test228.cpp b/src/test228.cpp new file mode 100644 index 00000000..83d29f8f --- /dev/null +++ b/src/test228.cpp @@ -0,0 +1,86 @@ +#ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H +#endif + +#include "proj_api.h" +#include /* for printf declaration */ + + +#ifdef _WIN32 + +int main(int argc, char* argv[]) +{ + printf("Test not yet ported on Win32\n"); + return 0; +} + +#else + +#include +#include +#include +#include + +static volatile int run = 0; +static volatile int started = 0; + +static void* thread_main(void* unused) +{ + projCtx p_proj_ctxt; + projPJ p_WGS84_proj; + projPJ p_OSGB36_proj; + (void)unused; + + __sync_add_and_fetch(&started, 1); + while(run == 0); + + p_proj_ctxt=pj_ctx_alloc(); + p_WGS84_proj=pj_init_plus_ctx(p_proj_ctxt,"+proj=longlat " + "+ellps=WGS84 +datum=WGS84 +no_defs"); + p_OSGB36_proj=pj_init_plus_ctx(p_proj_ctxt, + "+proj=longlat +ellps=airy +datum=OSGB36 +nadgrids=OSTN02_NTv2.gsb " + "+no_defs"); + + while(run) + { + double x, y; + int proj_ret; + + x = -5.2*DEG_TO_RAD; + y = 50*DEG_TO_RAD; + proj_ret = pj_transform(p_WGS84_proj, + p_OSGB36_proj, 1, 1, &x, &y, NULL ); + x *= RAD_TO_DEG; + y *= RAD_TO_DEG; + /*printf("%.18f %.18f\n", x, y); */ + assert(proj_ret == 0); + assert(fabs(x - -5.198965360936369962) < 1e-15); + assert(fabs(y - 49.999396034285531698) < 1e-15); + } + + pj_free (p_OSGB36_proj); + pj_free (p_WGS84_proj); + return NULL; +} + +int main() +{ + int i; + + pthread_t tid1, tid2; + pthread_attr_t attr1, attr2; + + pthread_attr_init(&attr1); + pthread_attr_init(&attr2); + + pthread_create(&tid1, &attr1, thread_main, NULL); + pthread_create(&tid2, &attr2, thread_main, NULL); + while(started != 2); + run = 1; + for(i=0;i<2;i++) + sleep(1); + run = 0; + return 0; +} + +#endif /* _WIN32 */ diff --git a/src/vector1.c b/src/vector1.c deleted file mode 100644 index 22e1f5d0..00000000 --- a/src/vector1.c +++ /dev/null @@ -1,29 +0,0 @@ -/* make storage for one and two dimensional matricies */ -#include -#include "projects.h" - void * /* one dimension array */ -vector1(int nvals, int size) { return((void *)pj_malloc(size * nvals)); } - void /* free 2D array */ -freev2(void **v, int nrows) { - if (v) { - for (v += nrows; nrows > 0; --nrows) - pj_dalloc(*--v); - pj_dalloc(v); - } -} - void ** /* two dimension array */ -vector2(int nrows, int ncols, int size) { - void **s; - - if ((s = (void **)pj_malloc(sizeof(void *) * nrows)) != NULL) { - int rsize, i; - - rsize = size * ncols; - for (i = 0; i < nrows; ++i) - if (!(s[i] = pj_malloc(rsize))) { - freev2(s, i); - return (void **)0; - } - } - return s; -} diff --git a/src/vector1.cpp b/src/vector1.cpp new file mode 100644 index 00000000..22e1f5d0 --- /dev/null +++ b/src/vector1.cpp @@ -0,0 +1,29 @@ +/* make storage for one and two dimensional matricies */ +#include +#include "projects.h" + void * /* one dimension array */ +vector1(int nvals, int size) { return((void *)pj_malloc(size * nvals)); } + void /* free 2D array */ +freev2(void **v, int nrows) { + if (v) { + for (v += nrows; nrows > 0; --nrows) + pj_dalloc(*--v); + pj_dalloc(v); + } +} + void ** /* two dimension array */ +vector2(int nrows, int ncols, int size) { + void **s; + + if ((s = (void **)pj_malloc(sizeof(void *) * nrows)) != NULL) { + int rsize, i; + + rsize = size * ncols; + for (i = 0; i < nrows; ++i) + if (!(s[i] = pj_malloc(rsize))) { + freev2(s, i); + return (void **)0; + } + } + return s; +} -- cgit v1.2.3 From 93d8f3a3504c1e92333524aa6aeca169c103166a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Dec 2018 20:58:28 +0100 Subject: cpp conversion: fix One-Definition-Rule violations Defining struct pj_opaque with different definitions is a violation of the C++ One-Definition-Rule. When using link-time optimizations, this could break badly. The solution adopted here is to wrap those structures into a C++ anonymous namespace so they are considered different --- src/PJ_aea.cpp | 2 ++ src/PJ_aeqd.cpp | 4 ++++ src/PJ_affine.cpp | 4 ++++ src/PJ_airy.cpp | 4 ++++ src/PJ_aitoff.cpp | 4 ++++ src/PJ_axisswap.cpp | 2 ++ src/PJ_bacon.cpp | 2 ++ src/PJ_bertin1953.cpp | 2 ++ src/PJ_bipc.cpp | 2 ++ src/PJ_bonne.cpp | 2 ++ src/PJ_cass.cpp | 2 ++ src/PJ_ccon.cpp | 2 ++ src/PJ_cea.cpp | 2 ++ src/PJ_chamb.cpp | 2 ++ src/PJ_deformation.cpp | 2 ++ src/PJ_eck3.cpp | 2 ++ src/PJ_eqc.cpp | 2 ++ src/PJ_eqdc.cpp | 2 ++ src/PJ_eqearth.cpp | 2 ++ src/PJ_fouc_s.cpp | 2 ++ src/PJ_geos.cpp | 2 ++ src/PJ_gn_sinu.cpp | 2 ++ src/PJ_gnom.cpp | 4 ++++ src/PJ_goode.cpp | 2 ++ src/PJ_gstmerc.cpp | 2 ++ src/PJ_hammer.cpp | 2 ++ src/PJ_healpix.cpp | 2 ++ src/PJ_helmert.cpp | 2 ++ src/PJ_hgridshift.cpp | 2 ++ src/PJ_horner.cpp | 20 ++++++++++---------- src/PJ_igh.cpp | 2 ++ src/PJ_imw_p.cpp | 4 ++++ src/PJ_isea.cpp | 16 ++++++++++++++++ src/PJ_krovak.cpp | 2 ++ src/PJ_labrd.cpp | 2 ++ src/PJ_laea.cpp | 4 ++++ src/PJ_lagrng.cpp | 2 ++ src/PJ_lcc.cpp | 2 ++ src/PJ_lcca.cpp | 2 ++ src/PJ_loxim.cpp | 2 ++ src/PJ_lsat.cpp | 2 ++ src/PJ_misrsom.cpp | 2 ++ src/PJ_mod_ster.cpp | 2 ++ src/PJ_moll.cpp | 2 ++ src/PJ_molodensky.cpp | 2 ++ src/PJ_nsper.cpp | 4 ++++ src/PJ_ob_tran.cpp | 2 ++ src/PJ_ocea.cpp | 2 ++ src/PJ_oea.cpp | 2 ++ src/PJ_omerc.cpp | 2 ++ src/PJ_ortho.cpp | 4 ++++ src/PJ_pipeline.cpp | 2 ++ src/PJ_poly.cpp | 2 ++ src/PJ_putp3.cpp | 2 ++ src/PJ_putp4p.cpp | 2 ++ src/PJ_putp5.cpp | 2 ++ src/PJ_putp6.cpp | 2 ++ src/PJ_qsc.cpp | 6 ++++++ src/PJ_robin.cpp | 2 ++ src/PJ_rpoly.cpp | 2 ++ src/PJ_sch.cpp | 2 ++ src/PJ_sconics.cpp | 4 ++++ src/PJ_somerc.cpp | 2 ++ src/PJ_stere.cpp | 4 ++++ src/PJ_sterea.cpp | 2 ++ src/PJ_sts.cpp | 2 ++ src/PJ_tmerc.cpp | 2 ++ src/PJ_tpeqd.cpp | 2 ++ src/PJ_unitconvert.cpp | 4 ++++ src/PJ_urm5.cpp | 2 ++ src/PJ_urmfps.cpp | 2 ++ src/PJ_vandg2.cpp | 2 ++ src/PJ_vgridshift.cpp | 2 ++ src/PJ_wag3.cpp | 2 ++ src/PJ_wink1.cpp | 2 ++ src/PJ_wink2.cpp | 6 +++++- src/gie.cpp | 2 ++ src/pj_gauss.cpp | 2 ++ src/proj_etmerc.cpp | 2 ++ src/proj_mdist.cpp | 2 ++ src/proj_rouss.cpp | 2 ++ src/projinfo.cpp | 2 ++ 82 files changed, 217 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/PJ_aea.cpp b/src/PJ_aea.cpp index e4d1dc4f..88929e3b 100644 --- a/src/PJ_aea.cpp +++ b/src/PJ_aea.cpp @@ -67,6 +67,7 @@ static double phi1_(double qs, double Te, double Tone_es) { } +namespace { // anonymous namespace struct pj_opaque { double ec; double n; @@ -80,6 +81,7 @@ struct pj_opaque { double *en; int ellips; }; +} // anonymous namespace static PJ *destructor (PJ *P, int errlev) { /* Destructor */ diff --git a/src/PJ_aeqd.cpp b/src/PJ_aeqd.cpp index 72c7ae95..69bb76c3 100644 --- a/src/PJ_aeqd.cpp +++ b/src/PJ_aeqd.cpp @@ -32,13 +32,16 @@ #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; @@ -51,6 +54,7 @@ struct pj_opaque { enum Mode mode; struct geod_geodesic g; }; +} // anonymous namespace PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; diff --git a/src/PJ_affine.cpp b/src/PJ_affine.cpp index b8fa4c68..c53d4e60 100644 --- a/src/PJ_affine.cpp +++ b/src/PJ_affine.cpp @@ -32,6 +32,7 @@ PROJ_HEAD(affine, "Affine transformation"); PROJ_HEAD(geogoffset, "Geographic Offset"); +namespace { // anonymous namespace struct pj_affine_coeffs { double s11; double s12; @@ -44,7 +45,9 @@ struct pj_affine_coeffs { double s33; double tscale; }; +} // anonymous namespace +namespace { // anonymous namespace struct pj_opaque_affine { double xoff; double yoff; @@ -53,6 +56,7 @@ struct pj_opaque_affine { struct pj_affine_coeffs forward; struct pj_affine_coeffs reverse; }; +} // anonymous namespace static PJ_COORD forward_4d(PJ_COORD obs, PJ *P) { diff --git a/src/PJ_airy.cpp b/src/PJ_airy.cpp index 037362b3..fa159bae 100644 --- a/src/PJ_airy.cpp +++ b/src/PJ_airy.cpp @@ -34,13 +34,16 @@ 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; @@ -49,6 +52,7 @@ struct pj_opaque { enum Mode mode; int no_cut; /* do not cut at hemisphere limit */ }; +} // anonymous namespace # define EPS 1.e-10 diff --git a/src/PJ_aitoff.cpp b/src/PJ_aitoff.cpp index 1af75469..ba1402a4 100644 --- a/src/PJ_aitoff.cpp +++ b/src/PJ_aitoff.cpp @@ -37,15 +37,19 @@ #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"; diff --git a/src/PJ_axisswap.cpp b/src/PJ_axisswap.cpp index d21eddb1..69edac8b 100644 --- a/src/PJ_axisswap.cpp +++ b/src/PJ_axisswap.cpp @@ -62,10 +62,12 @@ operation: 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); diff --git a/src/PJ_bacon.cpp b/src/PJ_bacon.cpp index f995e420..b00e1523 100644 --- a/src/PJ_bacon.cpp +++ b/src/PJ_bacon.cpp @@ -7,10 +7,12 @@ #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"; diff --git a/src/PJ_bertin1953.cpp b/src/PJ_bertin1953.cpp index 46420314..a551a6b2 100644 --- a/src/PJ_bertin1953.cpp +++ b/src/PJ_bertin1953.cpp @@ -21,9 +21,11 @@ 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) { diff --git a/src/PJ_bipc.cpp b/src/PJ_bipc.cpp index 243ecccd..95cbcbb8 100644 --- a/src/PJ_bipc.cpp +++ b/src/PJ_bipc.cpp @@ -29,9 +29,11 @@ PROJ_HEAD(bipc, "Bipolar conic of western hemisphere") "\n\tConic Sph"; # define R104 1.81514242207410275904 +namespace { // anonymous namespace struct pj_opaque { int noskew; }; +} // anonymous namespace static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ diff --git a/src/PJ_bonne.cpp b/src/PJ_bonne.cpp index 6c51af7c..0e979f64 100644 --- a/src/PJ_bonne.cpp +++ b/src/PJ_bonne.cpp @@ -9,6 +9,7 @@ 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; @@ -16,6 +17,7 @@ struct pj_opaque { double m1; double *en; }; +} // anonymous namespace static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ diff --git a/src/PJ_cass.cpp b/src/PJ_cass.cpp index cc610940..2488f405 100644 --- a/src/PJ_cass.cpp +++ b/src/PJ_cass.cpp @@ -15,10 +15,12 @@ PROJ_HEAD(cass, "Cassini") "\n\tCyl, Sph&Ell"; # define C5 .06666666666666666666 +namespace { // anonymous namespace struct pj_opaque { double *en; double m0; }; +} // anonymous namespace diff --git a/src/PJ_ccon.cpp b/src/PJ_ccon.cpp index 6ff2d3b1..4ee53133 100644 --- a/src/PJ_ccon.cpp +++ b/src/PJ_ccon.cpp @@ -28,6 +28,7 @@ #define EPS10 1e-10 +namespace { // anonymous namespace struct pj_opaque { double phi1; double ctgphi1; @@ -35,6 +36,7 @@ struct pj_opaque { double cosphi1; double *en; }; +} // anonymous namespace PROJ_HEAD(ccon, "Central Conic") "\n\tCentral Conic, Sph\n\tlat_1="; diff --git a/src/PJ_cea.cpp b/src/PJ_cea.cpp index ee7eebf5..3c9e128d 100644 --- a/src/PJ_cea.cpp +++ b/src/PJ_cea.cpp @@ -6,10 +6,12 @@ #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 diff --git a/src/PJ_chamb.cpp b/src/PJ_chamb.cpp index 8d8f05ee..0da3899b 100644 --- a/src/PJ_chamb.cpp +++ b/src/PJ_chamb.cpp @@ -7,6 +7,7 @@ #include "projects.h" typedef struct { double r, Az; } VECT; +namespace { // anonymous namespace struct pj_opaque { struct { /* control point data */ double phi, lam; @@ -18,6 +19,7 @@ struct pj_opaque { 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="; diff --git a/src/PJ_deformation.cpp b/src/PJ_deformation.cpp index 1398f5fd..49f62c01 100644 --- a/src/PJ_deformation.cpp +++ b/src/PJ_deformation.cpp @@ -63,11 +63,13 @@ 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) { diff --git a/src/PJ_eck3.cpp b/src/PJ_eck3.cpp index 7993d781..beaf9357 100644 --- a/src/PJ_eck3.cpp +++ b/src/PJ_eck3.cpp @@ -10,9 +10,11 @@ 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 */ diff --git a/src/PJ_eqc.cpp b/src/PJ_eqc.cpp index b95471f7..f31da20a 100644 --- a/src/PJ_eqc.cpp +++ b/src/PJ_eqc.cpp @@ -6,9 +6,11 @@ #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]"; diff --git a/src/PJ_eqdc.cpp b/src/PJ_eqdc.cpp index 468b16df..7e5cd0da 100644 --- a/src/PJ_eqdc.cpp +++ b/src/PJ_eqdc.cpp @@ -7,6 +7,7 @@ #include "projects.h" #include "proj_math.h" +namespace { // anonymous namespace struct pj_opaque { double phi1; double phi2; @@ -17,6 +18,7 @@ struct pj_opaque { double *en; int ellips; }; +} // anonymous namespace PROJ_HEAD(eqdc, "Equidistant Conic") "\n\tConic, Sph&Ell\n\tlat_1= lat_2="; diff --git a/src/PJ_eqearth.cpp b/src/PJ_eqearth.cpp index 34d85fa5..3547d683 100644 --- a/src/PJ_eqearth.cpp +++ b/src/PJ_eqearth.cpp @@ -31,11 +31,13 @@ PROJ_HEAD(eqearth, "Equal Earth") "\n\tPCyl, Sph&Ell"; #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}; diff --git a/src/PJ_fouc_s.cpp b/src/PJ_fouc_s.cpp index 8c223336..6f046b0a 100644 --- a/src/PJ_fouc_s.cpp +++ b/src/PJ_fouc_s.cpp @@ -11,9 +11,11 @@ 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 */ diff --git a/src/PJ_geos.cpp b/src/PJ_geos.cpp index ffe0771c..6bf7c3f3 100644 --- a/src/PJ_geos.cpp +++ b/src/PJ_geos.cpp @@ -36,6 +36,7 @@ #include "projects.h" #include "proj_math.h" +namespace { // anonymous namespace struct pj_opaque { double h; double radius_p; @@ -46,6 +47,7 @@ struct pj_opaque { double C; int flip_axis; }; +} // anonymous namespace PROJ_HEAD(geos, "Geostationary Satellite View") "\n\tAzi, Sph&Ell\n\th="; diff --git a/src/PJ_gn_sinu.cpp b/src/PJ_gn_sinu.cpp index 2c7824ac..c9885012 100644 --- a/src/PJ_gn_sinu.cpp +++ b/src/PJ_gn_sinu.cpp @@ -15,10 +15,12 @@ PROJ_HEAD(mbtfps, "McBryde-Thomas Flat-Polar Sinusoidal") "\n\tPCyl, Sph"; #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 */ diff --git a/src/PJ_gnom.cpp b/src/PJ_gnom.cpp index 7313643f..27e5f925 100644 --- a/src/PJ_gnom.cpp +++ b/src/PJ_gnom.cpp @@ -11,18 +11,22 @@ 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 */ diff --git a/src/PJ_goode.cpp b/src/PJ_goode.cpp index 3f5a4f8c..14c78439 100644 --- a/src/PJ_goode.cpp +++ b/src/PJ_goode.cpp @@ -13,10 +13,12 @@ PROJ_HEAD(goode, "Goode Homolosine") "\n\tPCyl, Sph"; 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 */ diff --git a/src/PJ_gstmerc.cpp b/src/PJ_gstmerc.cpp index 6475f972..01ef796d 100644 --- a/src/PJ_gstmerc.cpp +++ b/src/PJ_gstmerc.cpp @@ -8,6 +8,7 @@ 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; @@ -17,6 +18,7 @@ struct pj_opaque { double XS; double YS; }; +} // anonymous namespace static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ diff --git a/src/PJ_hammer.cpp b/src/PJ_hammer.cpp index 474d44ca..974bc813 100644 --- a/src/PJ_hammer.cpp +++ b/src/PJ_hammer.cpp @@ -11,10 +11,12 @@ PROJ_HEAD(hammer, "Hammer & Eckert-Greifendorff") #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 */ diff --git a/src/PJ_healpix.cpp b/src/PJ_healpix.cpp index a7d42398..64b57a26 100644 --- a/src/PJ_healpix.cpp +++ b/src/PJ_healpix.cpp @@ -53,12 +53,14 @@ PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph&Ell\n\tnorth_square= south_square="; /* 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. */ diff --git a/src/PJ_helmert.cpp b/src/PJ_helmert.cpp index c19422cb..b2072a84 100644 --- a/src/PJ_helmert.cpp +++ b/src/PJ_helmert.cpp @@ -65,6 +65,7 @@ static LPZ helmert_reverse_3d (XYZ xyz, PJ *P); /***********************************************************************/ +namespace { // anonymous namespace struct pj_opaque_helmert { /************************************************************************ Projection specific elements for the "helmert" PJ object @@ -87,6 +88,7 @@ struct pj_opaque_helmert { 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 */ diff --git a/src/PJ_hgridshift.cpp b/src/PJ_hgridshift.cpp index 86c6cdee..9ed9ca02 100644 --- a/src/PJ_hgridshift.cpp +++ b/src/PJ_hgridshift.cpp @@ -10,10 +10,12 @@ 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}}; diff --git a/src/PJ_horner.cpp b/src/PJ_horner.cpp index 49e108c8..f2d8cb5a 100644 --- a/src/PJ_horner.cpp +++ b/src/PJ_horner.cpp @@ -92,13 +92,7 @@ PROJ_HEAD(horner, "Horner polynomial evaluation"); #define horner_dealloc(x) pj_dealloc(x) #define horner_calloc(n,x) pj_calloc(n,x) - -struct horner; -typedef struct horner HORNER; -static UV horner (const HORNER *transformation, PJ_DIRECTION direction, UV position); -static HORNER *horner_alloc (size_t order, int complex_polynomia); -static void horner_free (HORNER *h); - +namespace { // anonymous namespace struct horner { int uneg; /* u axis negated? */ int vneg; /* v axis negated? */ @@ -118,6 +112,12 @@ struct horner { 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) \ @@ -181,7 +181,7 @@ static HORNER *horner_alloc (size_t order, int complex_polynomia) { /**********************************************************************/ -static UV horner (const HORNER *transformation, PJ_DIRECTION direction, UV position) { +static UV horner_func (const HORNER *transformation, PJ_DIRECTION direction, UV position) { /*********************************************************************** A reimplementation of the classic Engsager/Poder 2D Horner polynomial @@ -297,12 +297,12 @@ summing the tiny high order elements first. static PJ_COORD horner_forward_4d (PJ_COORD point, PJ *P) { - point.uv = horner ((HORNER *) P->opaque, PJ_FWD, point.uv); + 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 ((HORNER *) P->opaque, PJ_INV, point.uv); + point.uv = horner_func ((HORNER *) P->opaque, PJ_INV, point.uv); return point; } diff --git a/src/PJ_igh.cpp b/src/PJ_igh.cpp index 476d1c6b..60f5a4e8 100644 --- a/src/PJ_igh.cpp +++ b/src/PJ_igh.cpp @@ -32,10 +32,12 @@ 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 */ diff --git a/src/PJ_imw_p.cpp b/src/PJ_imw_p.cpp index 7bf9405a..a6e9d0bb 100644 --- a/src/PJ_imw_p.cpp +++ b/src/PJ_imw_p.cpp @@ -12,18 +12,22 @@ PROJ_HEAD(imw_p, "International Map of the World Polyconic") #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) { diff --git a/src/PJ_isea.cpp b/src/PJ_isea.cpp index 6170b8a1..cc0ddf9e 100644 --- a/src/PJ_isea.cpp +++ b/src/PJ_isea.cpp @@ -46,10 +46,12 @@ #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) { @@ -124,12 +126,15 @@ static void hexbin2(double width, double x, double y, long *i, long *j) { *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 */ @@ -143,29 +148,38 @@ struct isea_dgg { 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[] = { @@ -974,9 +988,11 @@ static struct isea_pt isea_forward(struct isea_dgg *g, struct isea_geo *in) 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 */ diff --git a/src/PJ_krovak.cpp b/src/PJ_krovak.cpp index 7e0488f0..7728002a 100644 --- a/src/PJ_krovak.cpp +++ b/src/PJ_krovak.cpp @@ -90,6 +90,7 @@ PROJ_HEAD(krovak, "Krovak") "\n\tPCyl, Ell"; /* 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; @@ -98,6 +99,7 @@ struct pj_opaque { double ad; int czech; }; +} // anonymous namespace static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ diff --git a/src/PJ_labrd.cpp b/src/PJ_labrd.cpp index e40bbcbd..c72aeb93 100644 --- a/src/PJ_labrd.cpp +++ b/src/PJ_labrd.cpp @@ -8,9 +8,11 @@ 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 */ diff --git a/src/PJ_laea.cpp b/src/PJ_laea.cpp index 02b34858..f1626a55 100644 --- a/src/PJ_laea.cpp +++ b/src/PJ_laea.cpp @@ -6,13 +6,16 @@ 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; @@ -25,6 +28,7 @@ struct pj_opaque { double *apa; enum Mode mode; }; +} // anonymous namespace #define EPS10 1.e-10 diff --git a/src/PJ_lagrng.cpp b/src/PJ_lagrng.cpp index f5363287..30306db4 100644 --- a/src/PJ_lagrng.cpp +++ b/src/PJ_lagrng.cpp @@ -9,6 +9,7 @@ PROJ_HEAD(lagrng, "Lagrange") "\n\tMisc Sph\n\tW="; #define TOL 1e-10 +namespace { // anonymous namespace struct pj_opaque { double a1; double a2; @@ -17,6 +18,7 @@ struct pj_opaque { double rw; double w; }; +} // anonymous namespace static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ diff --git a/src/PJ_lcc.cpp b/src/PJ_lcc.cpp index 5c430ea0..9483c085 100644 --- a/src/PJ_lcc.cpp +++ b/src/PJ_lcc.cpp @@ -9,6 +9,7 @@ PROJ_HEAD(lcc, "Lambert Conformal Conic") #define EPS10 1.e-10 +namespace { // anonymous namespace struct pj_opaque { double phi1; double phi2; @@ -16,6 +17,7 @@ struct pj_opaque { double rho0; double c; }; +} // anonymous namespace static XY e_forward (LP lp, PJ *P) { /* Ellipsoidal, forward */ diff --git a/src/PJ_lcca.cpp b/src/PJ_lcca.cpp index cbb18709..add6dcc7 100644 --- a/src/PJ_lcca.cpp +++ b/src/PJ_lcca.cpp @@ -59,11 +59,13 @@ PROJ_HEAD(lcca, "Lambert Conformal Conic Alternative") #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 */ diff --git a/src/PJ_loxim.cpp b/src/PJ_loxim.cpp index 28e955d9..fc412997 100644 --- a/src/PJ_loxim.cpp +++ b/src/PJ_loxim.cpp @@ -10,11 +10,13 @@ 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 */ diff --git a/src/PJ_lsat.cpp b/src/PJ_lsat.cpp index e3e7e026..ba829d42 100644 --- a/src/PJ_lsat.cpp +++ b/src/PJ_lsat.cpp @@ -12,10 +12,12 @@ PROJ_HEAD(lsat, "Space oblique for LANDSAT") #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); diff --git a/src/PJ_misrsom.cpp b/src/PJ_misrsom.cpp index 537172c1..3d8adbe9 100644 --- a/src/PJ_misrsom.cpp +++ b/src/PJ_misrsom.cpp @@ -33,10 +33,12 @@ PROJ_HEAD(misrsom, "Space oblique for MISR") #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); diff --git a/src/PJ_mod_ster.cpp b/src/PJ_mod_ster.cpp index ad5c9cdb..7ef34f0a 100644 --- a/src/PJ_mod_ster.cpp +++ b/src/PJ_mod_ster.cpp @@ -12,11 +12,13 @@ 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 */ diff --git a/src/PJ_moll.cpp b/src/PJ_moll.cpp index ed7e946d..aed16132 100644 --- a/src/PJ_moll.cpp +++ b/src/PJ_moll.cpp @@ -12,9 +12,11 @@ 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 */ diff --git a/src/PJ_molodensky.cpp b/src/PJ_molodensky.cpp index 6b231081..6ed8dd33 100644 --- a/src/PJ_molodensky.cpp +++ b/src/PJ_molodensky.cpp @@ -56,6 +56,7 @@ 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; @@ -64,6 +65,7 @@ struct pj_opaque_molodensky { double df; int abridged; }; +} // anonymous namespace static double RN (double a, double es, double phi) { diff --git a/src/PJ_nsper.cpp b/src/PJ_nsper.cpp index e6ecb852..beace3d9 100644 --- a/src/PJ_nsper.cpp +++ b/src/PJ_nsper.cpp @@ -4,13 +4,16 @@ #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; @@ -27,6 +30,7 @@ struct pj_opaque { 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="; diff --git a/src/PJ_ob_tran.cpp b/src/PJ_ob_tran.cpp index 1a9417b8..ee1dc465 100644 --- a/src/PJ_ob_tran.cpp +++ b/src/PJ_ob_tran.cpp @@ -7,11 +7,13 @@ #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" diff --git a/src/PJ_ocea.cpp b/src/PJ_ocea.cpp index 81c506fe..414c296f 100644 --- a/src/PJ_ocea.cpp +++ b/src/PJ_ocea.cpp @@ -8,6 +8,7 @@ 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; @@ -16,6 +17,7 @@ struct pj_opaque { double singam; double cosgam; }; +} // anonymous namespace static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ diff --git a/src/PJ_oea.cpp b/src/PJ_oea.cpp index b39e8d5a..0b558f83 100644 --- a/src/PJ_oea.cpp +++ b/src/PJ_oea.cpp @@ -6,12 +6,14 @@ 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 */ diff --git a/src/PJ_omerc.cpp b/src/PJ_omerc.cpp index 27b65cc7..dbf90230 100644 --- a/src/PJ_omerc.cpp +++ b/src/PJ_omerc.cpp @@ -33,11 +33,13 @@ 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 diff --git a/src/PJ_ortho.cpp b/src/PJ_ortho.cpp index 0e3641c0..4f5cf99a 100644 --- a/src/PJ_ortho.cpp +++ b/src/PJ_ortho.cpp @@ -7,18 +7,22 @@ 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 diff --git a/src/PJ_pipeline.cpp b/src/PJ_pipeline.cpp index c20454df..bf28e93e 100644 --- a/src/PJ_pipeline.cpp +++ b/src/PJ_pipeline.cpp @@ -109,12 +109,14 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 PROJ_HEAD(pipeline, "Transformation pipeline manager"); /* Projection specific elements for the PJ object */ +namespace { // anonymous namespace struct pj_opaque { int steps; char **argv; char **current_argv; PJ **pipeline; }; +} // anonymous namespace diff --git a/src/PJ_poly.cpp b/src/PJ_poly.cpp index 3bf7a8dd..9eb97467 100644 --- a/src/PJ_poly.cpp +++ b/src/PJ_poly.cpp @@ -9,10 +9,12 @@ 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 diff --git a/src/PJ_putp3.cpp b/src/PJ_putp3.cpp index 6e85d35f..c55a1732 100644 --- a/src/PJ_putp3.cpp +++ b/src/PJ_putp3.cpp @@ -2,9 +2,11 @@ #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"; diff --git a/src/PJ_putp4p.cpp b/src/PJ_putp4p.cpp index 77a18651..2ad20d1b 100644 --- a/src/PJ_putp4p.cpp +++ b/src/PJ_putp4p.cpp @@ -5,9 +5,11 @@ #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"; diff --git a/src/PJ_putp5.cpp b/src/PJ_putp5.cpp index d73e9368..41849f33 100644 --- a/src/PJ_putp5.cpp +++ b/src/PJ_putp5.cpp @@ -5,9 +5,11 @@ #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"; diff --git a/src/PJ_putp6.cpp b/src/PJ_putp6.cpp index fcd8146f..bfa7d908 100644 --- a/src/PJ_putp6.cpp +++ b/src/PJ_putp6.cpp @@ -5,9 +5,11 @@ #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"; diff --git a/src/PJ_qsc.cpp b/src/PJ_qsc.cpp index 767ed4a8..bca7c4fb 100644 --- a/src/PJ_qsc.cpp +++ b/src/PJ_qsc.cpp @@ -46,6 +46,7 @@ #include "projects.h" /* The six cube faces. */ +namespace { // anonymous namespace enum Face { FACE_FRONT = 0, FACE_RIGHT = 1, @@ -54,7 +55,9 @@ enum Face { FACE_TOP = 4, FACE_BOTTOM = 5 }; +} // anonymous namespace +namespace { // anonymous namespace struct pj_opaque { enum Face face; double a_squared; @@ -62,18 +65,21 @@ struct pj_opaque { 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. */ diff --git a/src/PJ_robin.cpp b/src/PJ_robin.cpp index 19bdc2dc..987977ae 100644 --- a/src/PJ_robin.cpp +++ b/src/PJ_robin.cpp @@ -18,9 +18,11 @@ 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}, diff --git a/src/PJ_rpoly.cpp b/src/PJ_rpoly.cpp index 24360965..a3b07c45 100644 --- a/src/PJ_rpoly.cpp +++ b/src/PJ_rpoly.cpp @@ -5,12 +5,14 @@ #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="; diff --git a/src/PJ_sch.cpp b/src/PJ_sch.cpp index 4a0b1fb8..23b6c4c2 100644 --- a/src/PJ_sch.cpp +++ b/src/PJ_sch.cpp @@ -39,6 +39,7 @@ #include "projects.h" #include "geocent.h" +namespace { // anonymous namespace struct pj_opaque { double plat; /*Peg Latitude */ double plon; /*Peg Longitude*/ @@ -50,6 +51,7 @@ struct pj_opaque { 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=]"; diff --git a/src/PJ_sconics.cpp b/src/PJ_sconics.cpp index 4efec86f..788bdb02 100644 --- a/src/PJ_sconics.cpp +++ b/src/PJ_sconics.cpp @@ -5,6 +5,7 @@ #include "proj_math.h" +namespace { // anonymous namespace enum Type { EULER = 0, MURD1 = 1, @@ -14,7 +15,9 @@ enum Type { TISSOT = 5, VITK1 = 6 }; +} // anonymous namespace +namespace { // anonymous namespace struct pj_opaque { double n; double rho_c; @@ -23,6 +26,7 @@ struct pj_opaque { double c1, c2; enum Type type; }; +} // anonymous namespace #define EPS10 1.e-10 diff --git a/src/PJ_somerc.cpp b/src/PJ_somerc.cpp index 6a98f76f..b82f1a58 100644 --- a/src/PJ_somerc.cpp +++ b/src/PJ_somerc.cpp @@ -8,9 +8,11 @@ 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 diff --git a/src/PJ_stere.cpp b/src/PJ_stere.cpp index 94e7f91d..58d5858d 100644 --- a/src/PJ_stere.cpp +++ b/src/PJ_stere.cpp @@ -8,13 +8,16 @@ 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; @@ -22,6 +25,7 @@ struct pj_opaque { double akm1; enum Mode mode; }; +} // anonymous namespace #define sinph0 static_cast(P->opaque)->sinX1 #define cosph0 static_cast(P->opaque)->cosX1 diff --git a/src/PJ_sterea.cpp b/src/PJ_sterea.cpp index 4c2fe2a3..05a6e5e3 100644 --- a/src/PJ_sterea.cpp +++ b/src/PJ_sterea.cpp @@ -29,12 +29,14 @@ #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"; diff --git a/src/PJ_sts.cpp b/src/PJ_sts.cpp index 4aece68e..bc56ed81 100644 --- a/src/PJ_sts.cpp +++ b/src/PJ_sts.cpp @@ -11,10 +11,12 @@ 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 */ diff --git a/src/PJ_tmerc.cpp b/src/PJ_tmerc.cpp index 55f878c9..ac6319ff 100644 --- a/src/PJ_tmerc.cpp +++ b/src/PJ_tmerc.cpp @@ -9,11 +9,13 @@ 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. diff --git a/src/PJ_tpeqd.cpp b/src/PJ_tpeqd.cpp index 5691cd7b..a1c049aa 100644 --- a/src/PJ_tpeqd.cpp +++ b/src/PJ_tpeqd.cpp @@ -8,10 +8,12 @@ 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 */ diff --git a/src/PJ_unitconvert.cpp b/src/PJ_unitconvert.cpp index 7476620e..09eb3ae8 100644 --- a/src/PJ_unitconvert.cpp +++ b/src/PJ_unitconvert.cpp @@ -78,19 +78,23 @@ PROJ_HEAD(unitconvert, "Unit conversion"); typedef double (*tconvert)(double); +namespace { // anonymous namespace struct TIME_UNITS { char *id; /* units keyword */ tconvert t_in; /* unit -> mod. julian date function pointer */ tconvert t_out; /* mod. julian date > unit function pointer */ 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 /***********************************************************************/ diff --git a/src/PJ_urm5.cpp b/src/PJ_urm5.cpp index 6a208647..f16d1534 100644 --- a/src/PJ_urm5.cpp +++ b/src/PJ_urm5.cpp @@ -8,9 +8,11 @@ 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 */ diff --git a/src/PJ_urmfps.cpp b/src/PJ_urmfps.cpp index 1d147b9c..848ba4f9 100644 --- a/src/PJ_urmfps.cpp +++ b/src/PJ_urmfps.cpp @@ -9,9 +9,11 @@ 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 diff --git a/src/PJ_vandg2.cpp b/src/PJ_vandg2.cpp index 588366cf..f414f1ef 100644 --- a/src/PJ_vandg2.cpp +++ b/src/PJ_vandg2.cpp @@ -5,9 +5,11 @@ #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"; diff --git a/src/PJ_vgridshift.cpp b/src/PJ_vgridshift.cpp index 8aad3777..bf1300c7 100644 --- a/src/PJ_vgridshift.cpp +++ b/src/PJ_vgridshift.cpp @@ -10,11 +10,13 @@ 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; diff --git a/src/PJ_wag3.cpp b/src/PJ_wag3.cpp index 0eeb73df..f0443688 100644 --- a/src/PJ_wag3.cpp +++ b/src/PJ_wag3.cpp @@ -9,9 +9,11 @@ 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 */ diff --git a/src/PJ_wink1.cpp b/src/PJ_wink1.cpp index 6640f995..e466988f 100644 --- a/src/PJ_wink1.cpp +++ b/src/PJ_wink1.cpp @@ -7,9 +7,11 @@ PROJ_HEAD(wink1, "Winkel I") "\n\tPCyl, Sph\n\tlat_ts="; +namespace { // anonymous namespace struct pj_opaque { double cosphi1; }; +} // anonymous namespace diff --git a/src/PJ_wink2.cpp b/src/PJ_wink2.cpp index 9da65eaa..3935372a 100644 --- a/src/PJ_wink2.cpp +++ b/src/PJ_wink2.cpp @@ -7,7 +7,11 @@ PROJ_HEAD(wink2, "Winkel II") "\n\tPCyl, Sph, no inv\n\tlat_1="; -struct pj_opaque { double cosphi1; }; +namespace { // anonymous namespace +struct pj_opaque { + double cosphi1; +}; +} // anonymous namespace #define MAX_ITER 10 #define LOOP_TOL 1e-7 diff --git a/src/gie.cpp b/src/gie.cpp index 74d78db5..77c21083 100644 --- a/src/gie.cpp +++ b/src/gie.cpp @@ -1020,6 +1020,7 @@ static int dispatch (const char *cmnd, const char *args) { +namespace { // anonymous namespace struct errno_vs_err_const {const char *the_err_const; int the_errno;}; static const struct errno_vs_err_const lookup[] = { {"pjd_err_no_args" , -1}, @@ -1084,6 +1085,7 @@ static const struct errno_vs_err_const lookup[] = { {"pjd_err_unknown" , 9999}, {"pjd_err_enomem" , ENOMEM}, }; +} // anonymous namespace static const struct errno_vs_err_const unknown = {"PJD_ERR_UNKNOWN", 9999}; diff --git a/src/pj_gauss.cpp b/src/pj_gauss.cpp index 4520bb39..45b93f59 100644 --- a/src/pj_gauss.cpp +++ b/src/pj_gauss.cpp @@ -32,12 +32,14 @@ #define MAX_ITER 20 +namespace { // anonymous namespace struct GAUSS { double C; double K; double e; double ratexp; }; +} // anonymous namespace #define DEL_TOL 1e-14 static double srat(double esinp, double ratexp) { diff --git a/src/proj_etmerc.cpp b/src/proj_etmerc.cpp index 0ba710d7..b521c329 100644 --- a/src/proj_etmerc.cpp +++ b/src/proj_etmerc.cpp @@ -47,6 +47,7 @@ #include "proj_math.h" +namespace { // anonymous namespace struct pj_opaque { double Qn; /* Merid. quad., scaled to the projection */ \ double Zb; /* Radius vector in polar coord. systems */ \ @@ -55,6 +56,7 @@ struct pj_opaque { double utg[6]; /* Constants for transv. merc. -> geo */ \ double gtu[6]; /* Constants for geo -> transv. merc. */ }; +} // anonymous namespace PROJ_HEAD(etmerc, "Extended Transverse Mercator") "\n\tCyl, Sph\n\tlat_ts=(0)\nlat_0=(0)"; diff --git a/src/proj_mdist.cpp b/src/proj_mdist.cpp index 777f704d..c645d117 100644 --- a/src/proj_mdist.cpp +++ b/src/proj_mdist.cpp @@ -37,12 +37,14 @@ #define MAX_ITER 20 #define TOL 1e-14 +namespace { // anonymous namespace struct MDIST { int nb; double es; double E; double b[1]; }; +} // anonymous namespace void * proj_mdist_ini(double es) { double numf, numfi, twon1, denf, denfi, ens, T, twon; diff --git a/src/proj_rouss.cpp b/src/proj_rouss.cpp index f39e0a15..f4fa084f 100644 --- a/src/proj_rouss.cpp +++ b/src/proj_rouss.cpp @@ -31,6 +31,7 @@ #include "proj.h" #include "projects.h" +namespace { // anonymous namespace struct pj_opaque { double s0; double A1, A2, A3, A4, A5, A6; @@ -39,6 +40,7 @@ struct pj_opaque { double D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11; void *en; }; +} // anonymous namespace PROJ_HEAD(rouss, "Roussilhe Stereographic") "\n\tAzi, Ell"; diff --git a/src/projinfo.cpp b/src/projinfo.cpp index 89b42e12..d604365a 100644 --- a/src/projinfo.cpp +++ b/src/projinfo.cpp @@ -56,6 +56,7 @@ using namespace NS_PROJ::internal; // --------------------------------------------------------------------------- +namespace { // anonymous namespace struct OutputOptions { bool quiet = false; bool PROJ5 = false; @@ -69,6 +70,7 @@ struct OutputOptions { bool c_ify = false; bool singleLine = false; }; +} // anonymous namespace // --------------------------------------------------------------------------- -- cgit v1.2.3 From 8211f48b1ac6c941f46a8f2df90bdbfdcbc85981 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Dec 2018 21:31:28 +0100 Subject: cpp conversion: fix zero-as-null-pointer-constant warnings --- src/PJ_aea.cpp | 10 +-- src/PJ_aeqd.cpp | 8 +-- src/PJ_affine.cpp | 14 ++-- src/PJ_airy.cpp | 2 +- src/PJ_aitoff.cpp | 4 +- src/PJ_august.cpp | 2 +- src/PJ_axisswap.cpp | 6 +- src/PJ_bacon.cpp | 6 +- src/PJ_bertin1953.cpp | 2 +- src/PJ_bipc.cpp | 2 +- src/PJ_bonne.cpp | 10 +-- src/PJ_calcofi.cpp | 2 +- src/PJ_cass.cpp | 10 +-- src/PJ_ccon.cpp | 8 +-- src/PJ_cea.cpp | 8 +-- src/PJ_chamb.cpp | 2 +- src/PJ_deformation.cpp | 14 ++-- src/PJ_eck3.cpp | 8 +-- src/PJ_eqc.cpp | 2 +- src/PJ_eqdc.cpp | 8 +-- src/PJ_eqearth.cpp | 10 +-- src/PJ_fouc_s.cpp | 2 +- src/PJ_geos.cpp | 4 +- src/PJ_gins8.cpp | 2 +- src/PJ_gn_sinu.cpp | 14 ++-- src/PJ_gnom.cpp | 2 +- src/PJ_goode.cpp | 10 +-- src/PJ_gstmerc.cpp | 2 +- src/PJ_hammer.cpp | 2 +- src/PJ_healpix.cpp | 14 ++-- src/PJ_helmert.cpp | 10 +-- src/PJ_hgridshift.cpp | 10 +-- src/PJ_horner.cpp | 34 ++++----- src/PJ_igh.cpp | 10 +-- src/PJ_imw_p.cpp | 8 +-- src/PJ_isea.cpp | 2 +- src/PJ_krovak.cpp | 2 +- src/PJ_labrd.cpp | 2 +- src/PJ_laea.cpp | 10 +-- src/PJ_lagrng.cpp | 2 +- src/PJ_lcc.cpp | 2 +- src/PJ_lcca.cpp | 8 +-- src/PJ_loxim.cpp | 2 +- src/PJ_lsat.cpp | 2 +- src/PJ_misrsom.cpp | 2 +- src/PJ_mod_ster.cpp | 10 +-- src/PJ_moll.cpp | 6 +- src/PJ_molodensky.cpp | 2 +- src/PJ_nsper.cpp | 4 +- src/PJ_ob_tran.cpp | 26 +++---- src/PJ_ocea.cpp | 2 +- src/PJ_oea.cpp | 2 +- src/PJ_omerc.cpp | 2 +- src/PJ_ortho.cpp | 2 +- src/PJ_pipeline.cpp | 48 ++++++------- src/PJ_poly.cpp | 8 +-- src/PJ_putp3.cpp | 4 +- src/PJ_putp4p.cpp | 4 +- src/PJ_putp5.cpp | 4 +- src/PJ_putp6.cpp | 4 +- src/PJ_qsc.cpp | 2 +- src/PJ_rpoly.cpp | 2 +- src/PJ_sch.cpp | 2 +- src/PJ_sconics.cpp | 2 +- src/PJ_somerc.cpp | 2 +- src/PJ_stere.cpp | 4 +- src/PJ_sterea.cpp | 10 +-- src/PJ_sts.cpp | 8 +-- src/PJ_tcc.cpp | 2 +- src/PJ_tmerc.cpp | 8 +-- src/PJ_tpeqd.cpp | 2 +- src/PJ_unitconvert.cpp | 28 ++++---- src/PJ_urm5.cpp | 4 +- src/PJ_urmfps.cpp | 4 +- src/PJ_vandg2.cpp | 4 +- src/PJ_vgridshift.cpp | 10 +-- src/PJ_wag3.cpp | 2 +- src/PJ_wag7.cpp | 2 +- src/PJ_wink1.cpp | 2 +- src/PJ_wink2.cpp | 4 +- src/cct.cpp | 18 ++--- src/emess.cpp | 4 +- src/geod.cpp | 8 +-- src/geod_set.cpp | 30 ++++---- src/geodesic.cpp | 50 ++++++------- src/geodtest.cpp | 114 ++++++++++++++--------------- src/gie.cpp | 58 +++++++-------- src/mk_cheby.cpp | 22 +++--- src/multistresstest.cpp | 10 +-- src/nad2bin.cpp | 8 +-- src/nad_init.cpp | 38 +++++----- src/optargpm.h | 88 +++++++++++------------ src/pj_apply_gridshift.cpp | 30 ++++---- src/pj_apply_vgridshift.cpp | 16 ++--- src/pj_auth.cpp | 2 +- src/pj_ctx.cpp | 36 +++++----- src/pj_datum_set.cpp | 10 +-- src/pj_datums.cpp | 4 +- src/pj_deriv.cpp | 2 +- src/pj_ell_set.cpp | 42 +++++------ src/pj_ellps.cpp | 2 +- src/pj_factors.cpp | 4 +- src/pj_fileapi.cpp | 8 +-- src/pj_fwd.cpp | 2 +- src/pj_gauss.cpp | 6 +- src/pj_gc_reader.cpp | 26 +++---- src/pj_gridcatalog.cpp | 42 +++++------ src/pj_gridinfo.cpp | 64 ++++++++--------- src/pj_gridlist.cpp | 26 +++---- src/pj_init.cpp | 172 ++++++++++++++++++++++---------------------- src/pj_initcache.cpp | 22 +++--- src/pj_internal.cpp | 42 +++++------ src/pj_inv.cpp | 2 +- src/pj_list.cpp | 2 +- src/pj_log.cpp | 2 +- src/pj_malloc.cpp | 18 ++--- src/pj_mlfn.cpp | 4 +- src/pj_mutex.cpp | 7 ++ src/pj_open_lib.cpp | 36 +++++----- src/pj_param.cpp | 34 ++++----- src/pj_pr_list.cpp | 4 +- src/pj_strerrno.cpp | 2 +- src/pj_strtod.cpp | 4 +- src/pj_transform.cpp | 22 +++--- src/pj_units.cpp | 4 +- src/pj_utils.cpp | 6 +- src/proj.cpp | 12 ++-- src/proj.h | 4 ++ src/proj_4D_api.cpp | 118 +++++++++++++++--------------- src/proj_etmerc.cpp | 4 +- src/proj_mdist.cpp | 4 +- src/proj_rouss.cpp | 8 +-- src/proj_strtod.cpp | 12 ++-- src/projects.h | 4 +- src/test228.cpp | 8 +-- src/vector1.cpp | 4 +- 136 files changed, 935 insertions(+), 924 deletions(-) (limited to 'src') diff --git a/src/PJ_aea.cpp b/src/PJ_aea.cpp index 88929e3b..c4a4a72a 100644 --- a/src/PJ_aea.cpp +++ b/src/PJ_aea.cpp @@ -85,10 +85,10 @@ struct pj_opaque { static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -199,7 +199,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(aea) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -212,7 +212,7 @@ PJ *PROJECTION(aea) { PJ *PROJECTION(leac) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_aeqd.cpp b/src/PJ_aeqd.cpp index 69bb76c3..1a350d90 100644 --- a/src/PJ_aeqd.cpp +++ b/src/PJ_aeqd.cpp @@ -63,10 +63,10 @@ PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -270,7 +270,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(aeqd) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_affine.cpp b/src/PJ_affine.cpp index c53d4e60..e2b668d3 100644 --- a/src/PJ_affine.cpp +++ b/src/PJ_affine.cpp @@ -112,8 +112,8 @@ static LP reverse_2d(XY xy, PJ *P) { static struct pj_opaque_affine * initQ() { struct pj_opaque_affine *Q = static_cast(pj_calloc(1, sizeof(struct pj_opaque_affine))); - if (0==Q) - return 0; + if (nullptr==Q) + return nullptr; /* default values */ Q->forward.s11 = 1.0; @@ -157,9 +157,9 @@ static void computeReverseParameters(PJ* P) if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { proj_log_debug(P, "Affine: matrix non invertible"); } - P->inv4d = NULL; - P->inv3d = NULL; - P->inv = NULL; + P->inv4d = nullptr; + P->inv3d = nullptr; + P->inv = nullptr; } else { Q->reverse.s11 = A / det; Q->reverse.s12 = D / det; @@ -176,7 +176,7 @@ static void computeReverseParameters(PJ* P) PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = (void *) Q; @@ -227,7 +227,7 @@ PJ *TRANSFORMATION(affine,0 /* no need for ellipsoid */) { PJ *TRANSFORMATION(geogoffset,0 /* no need for ellipsoid */) { struct pj_opaque_affine *Q = initQ(); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = (void *) Q; diff --git a/src/PJ_airy.cpp b/src/PJ_airy.cpp index fa159bae..0eb5efd7 100644 --- a/src/PJ_airy.cpp +++ b/src/PJ_airy.cpp @@ -116,7 +116,7 @@ PJ *PROJECTION(airy) { double beta; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_aitoff.cpp b/src/PJ_aitoff.cpp index ba1402a4..effd2c29 100644 --- a/src/PJ_aitoff.cpp +++ b/src/PJ_aitoff.cpp @@ -175,7 +175,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(aitoff) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; @@ -186,7 +186,7 @@ PJ *PROJECTION(aitoff) { PJ *PROJECTION(wintri) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_august.cpp b/src/PJ_august.cpp index e891e84e..b5a21ef7 100644 --- a/src/PJ_august.cpp +++ b/src/PJ_august.cpp @@ -27,7 +27,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(august) { - P->inv = 0; + P->inv = nullptr; P->fwd = s_forward; P->es = 0.; return P; diff --git a/src/PJ_axisswap.cpp b/src/PJ_axisswap.cpp index 69edac8b..8714ec85 100644 --- a/src/PJ_axisswap.cpp +++ b/src/PJ_axisswap.cpp @@ -167,7 +167,7 @@ PJ *CONVERSION(axisswap,0) { char *s; unsigned int i, j, n = 0; - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = (void *) Q; @@ -189,7 +189,7 @@ PJ *CONVERSION(axisswap,0) { /* check that all characters are valid */ for (i=0; ifwd4d == NULL && P->fwd3d == NULL && P->fwd == NULL) { + 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); } diff --git a/src/PJ_bacon.cpp b/src/PJ_bacon.cpp index b00e1523..6c6350fe 100644 --- a/src/PJ_bacon.cpp +++ b/src/PJ_bacon.cpp @@ -42,7 +42,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(bacon) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -56,7 +56,7 @@ PJ *PROJECTION(bacon) { PJ *PROJECTION(apian) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -69,7 +69,7 @@ PJ *PROJECTION(apian) { PJ *PROJECTION(ortel) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_bertin1953.cpp b/src/PJ_bertin1953.cpp index a551a6b2..2203d6f1 100644 --- a/src/PJ_bertin1953.cpp +++ b/src/PJ_bertin1953.cpp @@ -77,7 +77,7 @@ static XY s_forward (LP lp, PJ *P) { PJ *PROJECTION(bertin1953) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_bipc.cpp b/src/PJ_bipc.cpp index 95cbcbb8..19a6bbe1 100644 --- a/src/PJ_bipc.cpp +++ b/src/PJ_bipc.cpp @@ -164,7 +164,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(bipc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_bonne.cpp b/src/PJ_bonne.cpp index 0e979f64..385c1c4b 100644 --- a/src/PJ_bonne.cpp +++ b/src/PJ_bonne.cpp @@ -90,10 +90,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -104,7 +104,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor PJ *PROJECTION(bonne) { double c; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -115,7 +115,7 @@ PJ *PROJECTION(bonne) { if (P->es != 0.0) { Q->en = pj_enfn(P->es); - if (0==Q->en) + 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); diff --git a/src/PJ_calcofi.cpp b/src/PJ_calcofi.cpp index ed4cfe86..e81e4d2a 100644 --- a/src/PJ_calcofi.cpp +++ b/src/PJ_calcofi.cpp @@ -141,7 +141,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(calcofi) { - P->opaque = 0; + 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 */ diff --git a/src/PJ_cass.cpp b/src/PJ_cass.cpp index 2488f405..c831558c 100644 --- a/src/PJ_cass.cpp +++ b/src/PJ_cass.cpp @@ -84,10 +84,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ } static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -107,12 +107,12 @@ PJ *PROJECTION(cass) { /* otherwise it's ellipsoidal */ P->opaque = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, ENOMEM); P->destructor = destructor; static_cast(P->opaque)->en = pj_enfn (P->es); - if (0==static_cast(P->opaque)->en) + 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); diff --git a/src/PJ_ccon.cpp b/src/PJ_ccon.cpp index 4ee53133..4f7dedb4 100644 --- a/src/PJ_ccon.cpp +++ b/src/PJ_ccon.cpp @@ -69,10 +69,10 @@ static LP inverse (XY xy, PJ *P) { static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -83,7 +83,7 @@ static PJ *destructor (PJ *P, int errlev) { PJ *PROJECTION(ccon) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_cea.cpp b/src/PJ_cea.cpp index 3c9e128d..f8275b62 100644 --- a/src/PJ_cea.cpp +++ b/src/PJ_cea.cpp @@ -59,10 +59,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ } static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->apa); @@ -73,7 +73,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor PJ *PROJECTION(cea) { double t = 0.0; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_chamb.cpp b/src/PJ_chamb.cpp index 0da3899b..a490e817 100644 --- a/src/PJ_chamb.cpp +++ b/src/PJ_chamb.cpp @@ -104,7 +104,7 @@ PJ *PROJECTION(chamb) { int i, j; char line[10]; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_deformation.cpp b/src/PJ_deformation.cpp index 49f62c01..6c30f21c 100644 --- a/src/PJ_deformation.cpp +++ b/src/PJ_deformation.cpp @@ -248,10 +248,10 @@ static PJ_COORD reverse_4d(PJ_COORD in, PJ *P) { } static PJ *destructor(PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); if (static_cast(P->opaque)->cart) @@ -265,12 +265,12 @@ 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 (0==Q) + if (nullptr==Q) return destructor(P, ENOMEM); P->opaque = (void *) Q; Q->cart = proj_create(P->ctx, "+proj=cart"); - if (Q->cart == 0) + if (Q->cart == nullptr) return destructor(P, ENOMEM); /* inherit ellipsoid definition from P to Q->cart */ @@ -313,8 +313,8 @@ PJ *TRANSFORMATION(deformation,1) { P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; - P->fwd = 0; - P->inv = 0; + P->fwd = nullptr; + P->inv = nullptr; P->left = PJ_IO_UNITS_CARTESIAN; P->right = PJ_IO_UNITS_CARTESIAN; diff --git a/src/PJ_eck3.cpp b/src/PJ_eck3.cpp index beaf9357..90376631 100644 --- a/src/PJ_eck3.cpp +++ b/src/PJ_eck3.cpp @@ -52,7 +52,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(eck3) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -67,7 +67,7 @@ PJ *PROJECTION(eck3) { PJ *PROJECTION(kav7) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -85,7 +85,7 @@ PJ *PROJECTION(kav7) { PJ *PROJECTION(wag6) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -99,7 +99,7 @@ PJ *PROJECTION(wag6) { PJ *PROJECTION(putp1) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_eqc.cpp b/src/PJ_eqc.cpp index f31da20a..3fdb6dc0 100644 --- a/src/PJ_eqc.cpp +++ b/src/PJ_eqc.cpp @@ -40,7 +40,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(eqc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_eqdc.cpp b/src/PJ_eqdc.cpp index 7e5cd0da..0831fca4 100644 --- a/src/PJ_eqdc.cpp +++ b/src/PJ_eqdc.cpp @@ -61,10 +61,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -77,7 +77,7 @@ PJ *PROJECTION(eqdc) { int secant; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_eqearth.cpp b/src/PJ_eqearth.cpp index 3547d683..e5c1f974 100644 --- a/src/PJ_eqearth.cpp +++ b/src/PJ_eqearth.cpp @@ -130,10 +130,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal/spheroidal, invers } static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->apa); @@ -143,7 +143,7 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ PJ *PROJECTION(eqearth) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -154,7 +154,7 @@ PJ *PROJECTION(eqearth) { /* Ellipsoidal case */ if (P->es != 0.0) { Q->apa = pj_authset(P->es); /* For auth_lat(). */ - if (0 == Q->apa) + 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 */ diff --git a/src/PJ_fouc_s.cpp b/src/PJ_fouc_s.cpp index 6f046b0a..c5e711de 100644 --- a/src/PJ_fouc_s.cpp +++ b/src/PJ_fouc_s.cpp @@ -56,7 +56,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(fouc_s) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_geos.cpp b/src/PJ_geos.cpp index 6bf7c3f3..90fb01ab 100644 --- a/src/PJ_geos.cpp +++ b/src/PJ_geos.cpp @@ -198,7 +198,7 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ *PROJECTION(geos) { char *sweep_axis; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -206,7 +206,7 @@ PJ *PROJECTION(geos) { return pj_default_destructor (P, PJD_ERR_H_LESS_THAN_ZERO); sweep_axis = pj_param(P->ctx, P->params, "ssweep").s; - if (sweep_axis == NULL) + if (sweep_axis == nullptr) Q->flip_axis = 0; else { if ((sweep_axis[0] != 'x' && sweep_axis[0] != 'y') || diff --git a/src/PJ_gins8.cpp b/src/PJ_gins8.cpp index c1a2fff0..cc422437 100644 --- a/src/PJ_gins8.cpp +++ b/src/PJ_gins8.cpp @@ -24,7 +24,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(gins8) { P->es = 0.0; - P->inv = 0; + P->inv = nullptr; P->fwd = s_forward; return P; diff --git a/src/PJ_gn_sinu.cpp b/src/PJ_gn_sinu.cpp index c9885012..530de229 100644 --- a/src/PJ_gn_sinu.cpp +++ b/src/PJ_gn_sinu.cpp @@ -93,10 +93,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -118,7 +118,7 @@ static void setup(PJ *P) { PJ *PROJECTION(sinu) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -140,7 +140,7 @@ PJ *PROJECTION(sinu) { PJ *PROJECTION(eck6) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -155,7 +155,7 @@ PJ *PROJECTION(eck6) { PJ *PROJECTION(mbtfps) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -170,7 +170,7 @@ PJ *PROJECTION(mbtfps) { PJ *PROJECTION(gn_sinu) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; diff --git a/src/PJ_gnom.cpp b/src/PJ_gnom.cpp index 27e5f925..a4b5e35d 100644 --- a/src/PJ_gnom.cpp +++ b/src/PJ_gnom.cpp @@ -125,7 +125,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(gnom) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_goode.cpp b/src/PJ_goode.cpp index 14c78439..c79d125e 100644 --- a/src/PJ_goode.cpp +++ b/src/PJ_goode.cpp @@ -50,9 +50,9 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - if (0==P->opaque) + 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); @@ -63,13 +63,13 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ PJ *PROJECTION(goode) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; P->es = 0.; - if (!(Q->sinu = pj_sinu(0)) || !(Q->moll = pj_moll(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; diff --git a/src/PJ_gstmerc.cpp b/src/PJ_gstmerc.cpp index 01ef796d..9b819bac 100644 --- a/src/PJ_gstmerc.cpp +++ b/src/PJ_gstmerc.cpp @@ -54,7 +54,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(gstmerc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_hammer.cpp b/src/PJ_hammer.cpp index 974bc813..d4caa656 100644 --- a/src/PJ_hammer.cpp +++ b/src/PJ_hammer.cpp @@ -51,7 +51,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(hammer) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_healpix.cpp b/src/PJ_healpix.cpp index 64b57a26..7f0b3e83 100644 --- a/src/PJ_healpix.cpp +++ b/src/PJ_healpix.cpp @@ -605,10 +605,10 @@ static LP e_rhealpix_inverse(XY xy, PJ *P) { /* ellipsoid */ static PJ *destructor (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->apa); @@ -618,14 +618,14 @@ static PJ *destructor (PJ *P, int errlev) { /* Destructor PJ *PROJECTION(healpix) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + 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 (0==Q->apa) + 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. */ @@ -643,7 +643,7 @@ PJ *PROJECTION(healpix) { PJ *PROJECTION(rhealpix) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -658,7 +658,7 @@ PJ *PROJECTION(rhealpix) { return destructor (P, PJD_ERR_AXIS); if (P->es != 0.0) { Q->apa = pj_authset(P->es); /* For auth_lat(). */ - if (0==Q->apa) + 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. */ diff --git a/src/PJ_helmert.cpp b/src/PJ_helmert.cpp index b2072a84..4a3abf4e 100644 --- a/src/PJ_helmert.cpp +++ b/src/PJ_helmert.cpp @@ -479,7 +479,7 @@ static PJ_COORD helmert_reverse_4d (PJ_COORD point, PJ *P) { static PJ* init_helmert_six_parameters(PJ* P) { struct pj_opaque_helmert *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_helmert))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = (void *) Q; @@ -560,7 +560,7 @@ PJ *TRANSFORMATION(helmert, 0) { struct pj_opaque_helmert *Q; if( !init_helmert_six_parameters(P) ) { - return 0; + return nullptr; } /* In the 2D case, the coordinates are projected */ @@ -663,7 +663,7 @@ PJ *TRANSFORMATION(helmert, 0) { } if( !read_convention(P) ) { - return 0; + return nullptr; } /* Let's help with debugging */ @@ -699,7 +699,7 @@ PJ *TRANSFORMATION(molobadekas, 0) { struct pj_opaque_helmert *Q; if( !init_helmert_six_parameters(P) ) { - return 0; + return nullptr; } P->fwd3d = helmert_forward_3d; @@ -716,7 +716,7 @@ PJ *TRANSFORMATION(molobadekas, 0) { Q->scale = Q->scale_0; if( !read_convention(P) ) { - return 0; + return nullptr; } /* Reference point */ diff --git a/src/PJ_hgridshift.cpp b/src/PJ_hgridshift.cpp index 9ed9ca02..f0e57251 100644 --- a/src/PJ_hgridshift.cpp +++ b/src/PJ_hgridshift.cpp @@ -21,7 +21,7 @@ static XYZ forward_3d(LPZ lpz, PJ *P) { PJ_COORD point = {{0,0,0,0}}; point.lpz = lpz; - if (P->gridlist != NULL) { + 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); @@ -35,7 +35,7 @@ static LPZ reverse_3d(XYZ xyz, PJ *P) { PJ_COORD point = {{0,0,0,0}}; point.xyz = xyz; - if (P->gridlist != NULL) { + 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); @@ -82,7 +82,7 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { PJ *TRANSFORMATION(hgridshift,0) { struct pj_opaque_hgridshift *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_hgridshift))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = (void *) Q; @@ -90,8 +90,8 @@ PJ *TRANSFORMATION(hgridshift,0) { P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; - P->fwd = 0; - P->inv = 0; + P->fwd = nullptr; + P->inv = nullptr; P->left = PJ_IO_UNITS_ANGULAR; P->right = PJ_IO_UNITS_ANGULAR; diff --git a/src/PJ_horner.cpp b/src/PJ_horner.cpp index f2d8cb5a..3a1b7cca 100644 --- a/src/PJ_horner.cpp +++ b/src/PJ_horner.cpp @@ -143,8 +143,8 @@ static HORNER *horner_alloc (size_t order, int complex_polynomia) { int polynomia_ok = 0; HORNER *h = static_cast(horner_calloc (1, sizeof (HORNER))); - if (0==h) - return 0; + if (nullptr==h) + return nullptr; if (complex_polynomia) n = 2*(int)order + 2; @@ -174,7 +174,7 @@ static HORNER *horner_alloc (size_t order, int complex_polynomia) { /* safe, since all pointers are null-initialized (by calloc) */ horner_free (h); - return 0; + return nullptr; } @@ -226,7 +226,7 @@ summing the tiny high order elements first. UV uv_error; uv_error.u = uv_error.v = HUGE_VAL; - if (0==transformation) + if (nullptr==transformation) return uv_error; /* Check for valid value of direction (-1, 0, 1) */ @@ -326,7 +326,7 @@ polynomial evaluation engine. UV uv_error; uv_error.u = uv_error.v = HUGE_VAL; - if (0==transformation) + if (nullptr==transformation) return uv_error; /* Check for valid value of direction (-1, 0, 1) */ @@ -398,22 +398,22 @@ static PJ_COORD complex_horner_reverse_4d (PJ_COORD point, PJ *P) { static PJ *horner_freeup (PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; - if (0==P->opaque) + if (nullptr==P) + return nullptr; + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); horner_free ((HORNER *) P->opaque); - P->opaque = 0; + P->opaque = nullptr; return pj_default_destructor (P, errlev); } static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) { - char *buf, *init, *next = 0; + char *buf, *init, *next = nullptr; int i; buf = static_cast(pj_calloc (strlen (param) + 2, sizeof(char))); - if (0==buf) { + if (nullptr==buf) { proj_log_error (P, "Horner: No memory left"); return 0; } @@ -429,7 +429,7 @@ static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) { for (i = 0; i < ncoefs; i++) { if (i > 0) { - if ( next == 0 || ','!=*next) { + if ( next == nullptr || ','!=*next) { proj_log_error (P, "Horner: Malformed polynomium set %s. need %d coefs", param, ncoefs); return 0; } @@ -448,10 +448,10 @@ PJ *PROJECTION(horner) { HORNER *Q; P->fwd4d = horner_forward_4d; P->inv4d = horner_reverse_4d; - P->fwd3d = 0; - P->inv3d = 0; - P->fwd = 0; - P->inv = 0; + P->fwd3d = nullptr; + P->inv3d = nullptr; + P->fwd = nullptr; + P->inv = nullptr; P->left = P->right = PJ_IO_UNITS_PROJECTED; P->destructor = horner_freeup; @@ -472,7 +472,7 @@ PJ *PROJECTION(horner) { complex_polynomia = 1; Q = horner_alloc (degree, complex_polynomia); - if (Q == 0) + if (Q == nullptr) return horner_freeup (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_igh.cpp b/src/PJ_igh.cpp index 60f5a4e8..e3576861 100644 --- a/src/PJ_igh.cpp +++ b/src/PJ_igh.cpp @@ -138,10 +138,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor (PJ *P, int errlev) { int i; - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); for (i = 0; i < 12; ++i) { @@ -175,7 +175,7 @@ static PJ *destructor (PJ *P, int errlev) { */ #define SETUP(n, proj, x_0, y_0, lon_0) \ - if (!(Q->pj[n-1] = pj_##proj(0))) return destructor(P, ENOMEM); \ + 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; \ @@ -187,7 +187,7 @@ PJ *PROJECTION(igh) { XY xy1, xy3; LP lp = { 0, d4044118 }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_imw_p.cpp b/src/PJ_imw_p.cpp index a6e9d0bb..012c5caa 100644 --- a/src/PJ_imw_p.cpp +++ b/src/PJ_imw_p.cpp @@ -143,10 +143,10 @@ static void xy(PJ *P, double phi, double *x, double *y, double *sp, double *R) { static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); if( static_cast(P->opaque)->en ) @@ -160,7 +160,7 @@ 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 (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_isea.cpp b/src/PJ_isea.cpp index cc0ddf9e..522e6813 100644 --- a/src/PJ_isea.cpp +++ b/src/PJ_isea.cpp @@ -1016,7 +1016,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(isea) { char *opt; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_krovak.cpp b/src/PJ_krovak.cpp index 7728002a..9ecffb89 100644 --- a/src/PJ_krovak.cpp +++ b/src/PJ_krovak.cpp @@ -179,7 +179,7 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ *PROJECTION(krovak) { double u0, n0, g; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_labrd.cpp b/src/PJ_labrd.cpp index c72aeb93..d3930243 100644 --- a/src/PJ_labrd.cpp +++ b/src/PJ_labrd.cpp @@ -103,7 +103,7 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ *PROJECTION(labrd) { double Az, sinp, R, N, t; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_laea.cpp b/src/PJ_laea.cpp index f1626a55..dd02c75a 100644 --- a/src/PJ_laea.cpp +++ b/src/PJ_laea.cpp @@ -227,10 +227,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->apa); @@ -242,7 +242,7 @@ static PJ *destructor (PJ *P, int errlev) { PJ *PROJECTION(laea) { double t; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; P->destructor = destructor; @@ -261,7 +261,7 @@ PJ *PROJECTION(laea) { Q->qp = pj_qsfn(1., P->e, P->one_es); Q->mmf = .5 / (1. - P->es); Q->apa = pj_authset(P->es); - if (0==Q->apa) + if (nullptr==Q->apa) return destructor(P, ENOMEM); switch (Q->mode) { case N_POLE: diff --git a/src/PJ_lagrng.cpp b/src/PJ_lagrng.cpp index 30306db4..8c0150aa 100644 --- a/src/PJ_lagrng.cpp +++ b/src/PJ_lagrng.cpp @@ -72,7 +72,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(lagrng) { double phi1; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_lcc.cpp b/src/PJ_lcc.cpp index 9483c085..7d6e3f57 100644 --- a/src/PJ_lcc.cpp +++ b/src/PJ_lcc.cpp @@ -82,7 +82,7 @@ PJ *PROJECTION(lcc) { int secant; struct pj_opaque *Q = static_cast(pj_calloc(1, sizeof (struct pj_opaque))); - if (0 == Q) + if (nullptr == Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_lcca.cpp b/src/PJ_lcca.cpp index add6dcc7..70b5dff9 100644 --- a/src/PJ_lcca.cpp +++ b/src/PJ_lcca.cpp @@ -121,10 +121,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -135,7 +135,7 @@ static PJ *destructor (PJ *P, int errlev) { PJ *PROJECTION(lcca) { double s2p0, N0, R0, tan0; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_loxim.cpp b/src/PJ_loxim.cpp index fc412997..f68e844a 100644 --- a/src/PJ_loxim.cpp +++ b/src/PJ_loxim.cpp @@ -57,7 +57,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(loxim) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_lsat.cpp b/src/PJ_lsat.cpp index ba829d42..a0eca1bd 100644 --- a/src/PJ_lsat.cpp +++ b/src/PJ_lsat.cpp @@ -156,7 +156,7 @@ 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 (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_misrsom.cpp b/src/PJ_misrsom.cpp index 3d8adbe9..c84b96e3 100644 --- a/src/PJ_misrsom.cpp +++ b/src/PJ_misrsom.cpp @@ -173,7 +173,7 @@ PJ *PROJECTION(misrsom) { double lam, alf, esc, ess; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_mod_ster.cpp b/src/PJ_mod_ster.cpp index 7ef34f0a..7c4f363b 100644 --- a/src/PJ_mod_ster.cpp +++ b/src/PJ_mod_ster.cpp @@ -130,7 +130,7 @@ PJ *PROJECTION(mil_os) { }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -153,7 +153,7 @@ PJ *PROJECTION(lee_os) { }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -178,7 +178,7 @@ PJ *PROJECTION(gs48) { }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -213,7 +213,7 @@ PJ *PROJECTION(alsk) { }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -261,7 +261,7 @@ PJ *PROJECTION(gs50) { }; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_moll.cpp b/src/PJ_moll.cpp index aed16132..c877a1bb 100644 --- a/src/PJ_moll.cpp +++ b/src/PJ_moll.cpp @@ -77,7 +77,7 @@ static PJ * setup(PJ *P, double p) { PJ *PROJECTION(moll) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -87,7 +87,7 @@ PJ *PROJECTION(moll) { PJ *PROJECTION(wag4) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -96,7 +96,7 @@ PJ *PROJECTION(wag4) { PJ *PROJECTION(wag5) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_molodensky.cpp b/src/PJ_molodensky.cpp index 6ed8dd33..91743fda 100644 --- a/src/PJ_molodensky.cpp +++ b/src/PJ_molodensky.cpp @@ -276,7 +276,7 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { PJ *TRANSFORMATION(molodensky,1) { int count_required_params = 0; struct pj_opaque_molodensky *Q = static_cast(pj_calloc(1, sizeof(struct pj_opaque_molodensky))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = (void *) Q; diff --git a/src/PJ_nsper.cpp b/src/PJ_nsper.cpp index beace3d9..f93010f8 100644 --- a/src/PJ_nsper.cpp +++ b/src/PJ_nsper.cpp @@ -173,7 +173,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(nsper) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -187,7 +187,7 @@ PJ *PROJECTION(tpers) { double omega, gamma; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_ob_tran.cpp b/src/PJ_ob_tran.cpp index ee1dc465..d34059a9 100644 --- a/src/PJ_ob_tran.cpp +++ b/src/PJ_ob_tran.cpp @@ -87,9 +87,9 @@ static LP t_inverse(XY xy, PJ *P) { /* spheroid */ static PJ *destructor(PJ *P, int errlev) { - if (0==P) - return 0; - if (0==P->opaque) + if (nullptr==P) + return nullptr; + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); if (static_cast(P->opaque)->link) @@ -122,7 +122,7 @@ 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 != 0; params = params->next) + for (; params != nullptr; params = params->next) argc++; return argc; } @@ -131,18 +131,18 @@ static size_t paralist_params_argc (paralist *params) { /* turn paralist into argc/argv style argument list */ static ARGS ob_tran_target_params (paralist *params) { int i = 0; - ARGS args = {0, 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 (0==args.argv) + if (nullptr==args.argv) return args; /* Copy all args *except* the proj=ob_tran arg to the argv array */ - for (i = 0; params != 0; params = params->next) { + for (i = 0; params != nullptr; params = params->next) { if (0==strcmp (params->param, "proj=ob_tran")) continue; args.argv[i++] = params->param; @@ -169,7 +169,7 @@ PJ *PROJECTION(ob_tran) { PJ *R; /* projection to rotate */ struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return destructor(P, ENOMEM); P->opaque = Q; @@ -188,7 +188,7 @@ PJ *PROJECTION(ob_tran) { R = pj_init_ctx (pj_get_ctx(P), args.argc, args.argv); pj_dealloc (args.argv); - if (0==R) + if (nullptr==R) return destructor (P, PJD_ERR_UNKNOWN_PROJECTION_ID); Q->link = R; @@ -228,11 +228,11 @@ PJ *PROJECTION(ob_tran) { if (fabs(phip) > TOL) { /* oblique */ Q->cphip = cos(phip); Q->sphip = sin(phip); - P->fwd = Q->link->fwd ? o_forward : 0; - P->inv = Q->link->inv ? o_inverse : 0; + 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 : 0; - P->inv = Q->link->inv ? t_inverse : 0; + 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 */ diff --git a/src/PJ_ocea.cpp b/src/PJ_ocea.cpp index 414c296f..0576ace7 100644 --- a/src/PJ_ocea.cpp +++ b/src/PJ_ocea.cpp @@ -54,7 +54,7 @@ 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 (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_oea.cpp b/src/PJ_oea.cpp index 0b558f83..0c401b2f 100644 --- a/src/PJ_oea.cpp +++ b/src/PJ_oea.cpp @@ -60,7 +60,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(oea) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_omerc.cpp b/src/PJ_omerc.cpp index dbf90230..ead07128 100644 --- a/src/PJ_omerc.cpp +++ b/src/PJ_omerc.cpp @@ -123,7 +123,7 @@ PJ *PROJECTION(omerc) { int alp, gam, no_off = 0; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_ortho.cpp b/src/PJ_ortho.cpp index 4f5cf99a..6ea55248 100644 --- a/src/PJ_ortho.cpp +++ b/src/PJ_ortho.cpp @@ -122,7 +122,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(ortho) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_pipeline.cpp b/src/PJ_pipeline.cpp index bf28e93e..6d409690 100644 --- a/src/PJ_pipeline.cpp +++ b/src/PJ_pipeline.cpp @@ -211,14 +211,14 @@ static LP pipeline_reverse (XY xy, PJ *P) { static PJ *destructor (PJ *P, int errlev) { int i; - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); /* Deallocate each pipeine step, then pipeline array */ - if (0!=static_cast(P->opaque)->pipeline) + if (nullptr!=static_cast(P->opaque)->pipeline) for (i = 0; i < static_cast(P->opaque)->steps; i++) proj_destroy (static_cast(P->opaque)->pipeline[i+1]); pj_dealloc (static_cast(P->opaque)->pipeline); @@ -234,8 +234,8 @@ static PJ *pj_create_pipeline (PJ *P, size_t steps) { /* Room for the pipeline: An array of PJ * with room for sentinels at both ends */ static_cast(P->opaque)->pipeline = static_cast(pj_calloc (steps + 2, sizeof(PJ *))); - if (0==static_cast(P->opaque)->pipeline) - return 0; + if (nullptr==static_cast(P->opaque)->pipeline) + return nullptr; static_cast(P->opaque)->steps = (int)steps; @@ -248,7 +248,7 @@ static PJ *pj_create_pipeline (PJ *P, size_t steps) { /* count the number of args in pipeline definition, and mark all args as used */ static size_t argc_params (paralist *params) { size_t argc = 0; - for (; params != 0; params = params->next) { + for (; params != nullptr; params = params->next) { argc++; params->used = 1; } @@ -263,9 +263,9 @@ static char **argv_params (paralist *params, size_t argc) { char **argv; size_t i = 0; argv = static_cast(pj_calloc (argc, sizeof (char *))); - if (0==argv) - return 0; - for (; params != 0; params = params->next) + if (nullptr==argv) + return nullptr; + for (; params != nullptr; params = params->next) argv[i++] = params->param; argv[i++] = argv_sentinel; return argv; @@ -290,13 +290,13 @@ static void set_ellipsoid(PJ *P) { int err = proj_errno_reset (P); /* Break the linked list after the global args */ - attachment = 0; - for (cur = P->params; cur != 0; cur = cur->next) + attachment = nullptr; + for (cur = P->params; cur != nullptr; cur = cur->next) /* cur->next will always be non 0 given argv_sentinel presence, */ /* but this is far from being obvious for a static analyzer */ - if (cur->next != 0 && strcmp(argv_sentinel, cur->next->param) == 0) { + if (cur->next != nullptr && strcmp(argv_sentinel, cur->next->param) == 0) { attachment = cur->next; - cur->next = 0; + cur->next = nullptr; break; } @@ -322,7 +322,7 @@ static void set_ellipsoid(PJ *P) { /* Re-attach the dangling list */ /* Note: cur will always be non 0 given argv_sentinel presence, */ /* but this is far from being obvious for a static analyzer */ - if( cur != 0 ) + if( cur != nullptr ) cur->next = attachment; proj_errno_restore (P, err); } @@ -353,16 +353,16 @@ PJ *OPERATION(pipeline,0) { P->opaque = static_cast(pj_calloc (1, sizeof(struct pj_opaque))); - if (0==P->opaque) + if (nullptr==P->opaque) return destructor(P, ENOMEM); argc = (int)argc_params (P->params); static_cast(P->opaque)->argv = argv = argv_params (P->params, argc); - if (0==argv) + if (nullptr==argv) return destructor (P, ENOMEM); static_cast(P->opaque)->current_argv = current_argv = static_cast(pj_calloc (argc, sizeof (char *))); - if (0==current_argv) + if (nullptr==current_argv) return destructor (P, ENOMEM); /* Do some syntactical sanity checking */ @@ -396,7 +396,7 @@ PJ *OPERATION(pipeline,0) { return destructor (P, PJD_ERR_MALFORMED_PIPELINE); /* ERROR: no pipeline def */ /* Make room for the pipeline and execution indicators */ - if (0==pj_create_pipeline (P, nsteps)) + if (nullptr==pj_create_pipeline (P, nsteps)) return destructor (P, ENOMEM); set_ellipsoid(P); @@ -407,7 +407,7 @@ PJ *OPERATION(pipeline,0) { int j; int current_argc = 0; int err; - PJ *next_step = 0; + PJ *next_step = nullptr; /* Build a set of setup args for the current step */ proj_log_trace (P, "Pipeline: Building arg list for step no. %d", i); @@ -431,7 +431,7 @@ PJ *OPERATION(pipeline,0) { next_step = proj_create_argv (P->ctx, current_argc, current_argv); proj_log_trace (P, "Pipeline: Step %d (%s) at %p", i, current_argv[0], next_step); - if (0==next_step) { + if (nullptr==next_step) { /* The step init failed, but possibly without setting errno. If so, we say "malformed" */ int err_to_report = proj_errno(P); if (0==err_to_report) @@ -472,9 +472,9 @@ PJ *OPERATION(pipeline,0) { if ( pj_has_inverse(Q) ) { continue; } else { - P->inv = 0; - P->inv3d = 0; - P->inv4d = 0; + P->inv = nullptr; + P->inv3d = nullptr; + P->inv4d = nullptr; break; } } diff --git a/src/PJ_poly.cpp b/src/PJ_poly.cpp index 9eb97467..a970fdb1 100644 --- a/src/PJ_poly.cpp +++ b/src/PJ_poly.cpp @@ -134,10 +134,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor(PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); if (static_cast(P->opaque)->en) @@ -149,7 +149,7 @@ static PJ *destructor(PJ *P, int errlev) { PJ *PROJECTION(poly) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_putp3.cpp b/src/PJ_putp3.cpp index c55a1732..98bb2ff0 100644 --- a/src/PJ_putp3.cpp +++ b/src/PJ_putp3.cpp @@ -37,7 +37,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(putp3) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -52,7 +52,7 @@ PJ *PROJECTION(putp3) { PJ *PROJECTION(putp3p) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_putp4p.cpp b/src/PJ_putp4p.cpp index 2ad20d1b..608fc76e 100644 --- a/src/PJ_putp4p.cpp +++ b/src/PJ_putp4p.cpp @@ -44,7 +44,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(putp4p) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -61,7 +61,7 @@ PJ *PROJECTION(putp4p) { PJ *PROJECTION(weren) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_putp5.cpp b/src/PJ_putp5.cpp index 41849f33..79e2ad15 100644 --- a/src/PJ_putp5.cpp +++ b/src/PJ_putp5.cpp @@ -43,7 +43,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(putp5) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -60,7 +60,7 @@ PJ *PROJECTION(putp5) { PJ *PROJECTION(putp5p) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_putp6.cpp b/src/PJ_putp6.cpp index bfa7d908..1186b18b 100644 --- a/src/PJ_putp6.cpp +++ b/src/PJ_putp6.cpp @@ -59,7 +59,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(putp6) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; @@ -79,7 +79,7 @@ PJ *PROJECTION(putp6) { PJ *PROJECTION(putp6p) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_qsc.cpp b/src/PJ_qsc.cpp index bca7c4fb..b50a7c95 100644 --- a/src/PJ_qsc.cpp +++ b/src/PJ_qsc.cpp @@ -377,7 +377,7 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ *PROJECTION(qsc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_rpoly.cpp b/src/PJ_rpoly.cpp index a3b07c45..a34f6171 100644 --- a/src/PJ_rpoly.cpp +++ b/src/PJ_rpoly.cpp @@ -43,7 +43,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(rpoly) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_sch.cpp b/src/PJ_sch.cpp index 23b6c4c2..5a2f944b 100644 --- a/src/PJ_sch.cpp +++ b/src/PJ_sch.cpp @@ -195,7 +195,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(sch) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_sconics.cpp b/src/PJ_sconics.cpp index 788bdb02..1d19a13d 100644 --- a/src/PJ_sconics.cpp +++ b/src/PJ_sconics.cpp @@ -117,7 +117,7 @@ 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 (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; Q->type = type; diff --git a/src/PJ_somerc.cpp b/src/PJ_somerc.cpp index b82f1a58..15d2e765 100644 --- a/src/PJ_somerc.cpp +++ b/src/PJ_somerc.cpp @@ -72,7 +72,7 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ PJ *PROJECTION(somerc) { double cp, phip0, sp; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_stere.cpp b/src/PJ_stere.cpp index 58d5858d..1502b2a6 100644 --- a/src/PJ_stere.cpp +++ b/src/PJ_stere.cpp @@ -286,7 +286,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(stere) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -299,7 +299,7 @@ PJ *PROJECTION(stere) { PJ *PROJECTION(ups) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_sterea.cpp b/src/PJ_sterea.cpp index 05a6e5e3..bb498068 100644 --- a/src/PJ_sterea.cpp +++ b/src/PJ_sterea.cpp @@ -81,10 +81,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -96,12 +96,12 @@ PJ *PROJECTION(sterea) { double R; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + 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 (0==Q->en) + if (nullptr==Q->en) return pj_default_destructor (P, ENOMEM); Q->sinc0 = sin (Q->phic0); diff --git a/src/PJ_sts.cpp b/src/PJ_sts.cpp index bc56ed81..9f889611 100644 --- a/src/PJ_sts.cpp +++ b/src/PJ_sts.cpp @@ -71,7 +71,7 @@ static PJ *setup(PJ *P, double p, double q, int mode) { PJ *PROJECTION(fouc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; return setup(P, 2., 2., 1); @@ -81,7 +81,7 @@ PJ *PROJECTION(fouc) { PJ *PROJECTION(kav5) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; @@ -92,7 +92,7 @@ PJ *PROJECTION(kav5) { PJ *PROJECTION(qua_aut) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; return setup(P, 2., 2., 0); @@ -102,7 +102,7 @@ PJ *PROJECTION(qua_aut) { PJ *PROJECTION(mbt_s) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + 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 index 60ded63e..64fdc182 100644 --- a/src/PJ_tcc.cpp +++ b/src/PJ_tcc.cpp @@ -28,7 +28,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(tcc) { P->es = 0.; P->fwd = s_forward; - P->inv = 0; + P->inv = nullptr; return P; } diff --git a/src/PJ_tmerc.cpp b/src/PJ_tmerc.cpp index ac6319ff..5a2dacbd 100644 --- a/src/PJ_tmerc.cpp +++ b/src/PJ_tmerc.cpp @@ -167,10 +167,10 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ static PJ *destructor(PJ *P, int errlev) { /* Destructor */ - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor(P, errlev); pj_dealloc (static_cast(P->opaque)->en); @@ -200,7 +200,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(tmerc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_tpeqd.cpp b/src/PJ_tpeqd.cpp index a1c049aa..2720327a 100644 --- a/src/PJ_tpeqd.cpp +++ b/src/PJ_tpeqd.cpp @@ -62,7 +62,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ 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 (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_unitconvert.cpp b/src/PJ_unitconvert.cpp index 09eb3ae8..f7545680 100644 --- a/src/PJ_unitconvert.cpp +++ b/src/PJ_unitconvert.cpp @@ -276,7 +276,7 @@ static const struct TIME_UNITS time_units[] = { {"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"}, - {NULL, NULL, NULL, NULL} + {nullptr, nullptr, nullptr, nullptr} }; @@ -424,7 +424,7 @@ static double get_unit_conversion_factor(const char* name, } } if( p_normalized_name ) { - *p_normalized_name = NULL; + *p_normalized_name = nullptr; } if( p_is_linear ) { *p_is_linear = -1; @@ -444,7 +444,7 @@ PJ *CONVERSION(unitconvert,0) { int z_in_is_linear = -1; /* unknown */ int z_out_is_linear = -1; /* unknown */ - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = (void *) Q; @@ -465,8 +465,8 @@ PJ *CONVERSION(unitconvert,0) { Q->xy_factor = 1.0; Q->z_factor = 1.0; - if ((name = pj_param (P->ctx, P->params, "sxy_in").s) != NULL) { - const char* normalized_name = NULL; + 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); @@ -478,8 +478,8 @@ PJ *CONVERSION(unitconvert,0) { Q->xy_factor *= f; } - if ((name = pj_param (P->ctx, P->params, "sxy_out").s) != NULL) { - const char* normalized_name = NULL; + 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); @@ -497,8 +497,8 @@ PJ *CONVERSION(unitconvert,0) { return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); } - if ((name = pj_param (P->ctx, P->params, "sz_in").s) != NULL) { - const char* normalized_name = NULL; + 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); @@ -510,8 +510,8 @@ PJ *CONVERSION(unitconvert,0) { Q->z_factor *= f; } - if ((name = pj_param (P->ctx, P->params, "sz_out").s) != NULL) { - const char* normalized_name = NULL; + 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); @@ -529,7 +529,7 @@ PJ *CONVERSION(unitconvert,0) { return pj_default_destructor(P, PJD_ERR_INCONSISTENT_UNIT); } - if ((name = pj_param (P->ctx, P->params, "st_in").s) != NULL) { + 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 */ @@ -538,8 +538,8 @@ PJ *CONVERSION(unitconvert,0) { proj_log_debug(P, "t_in unit: %s", time_units[i].name); } - s = 0; - if ((name = pj_param (P->ctx, P->params, "st_out").s) != NULL) { + 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 */ diff --git a/src/PJ_urm5.cpp b/src/PJ_urm5.cpp index f16d1534..0e3c7e3c 100644 --- a/src/PJ_urm5.cpp +++ b/src/PJ_urm5.cpp @@ -31,7 +31,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(urm5) { double alpha, t; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; @@ -49,7 +49,7 @@ PJ *PROJECTION(urm5) { Q->rmn = 1. / (Q->m * Q->n); P->es = 0.; - P->inv = 0; + P->inv = nullptr; P->fwd = s_forward; return P; diff --git a/src/PJ_urmfps.cpp b/src/PJ_urmfps.cpp index 848ba4f9..7103222a 100644 --- a/src/PJ_urmfps.cpp +++ b/src/PJ_urmfps.cpp @@ -48,7 +48,7 @@ static PJ *setup(PJ *P) { PJ *PROJECTION(urmfps) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; @@ -67,7 +67,7 @@ PJ *PROJECTION(urmfps) { PJ *PROJECTION(wag1) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_vandg2.cpp b/src/PJ_vandg2.cpp index f414f1ef..61d50044 100644 --- a/src/PJ_vandg2.cpp +++ b/src/PJ_vandg2.cpp @@ -52,7 +52,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(vandg2) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; @@ -64,7 +64,7 @@ PJ *PROJECTION(vandg2) { PJ *PROJECTION(vandg3) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_vgridshift.cpp b/src/PJ_vgridshift.cpp index bf1300c7..b3da906d 100644 --- a/src/PJ_vgridshift.cpp +++ b/src/PJ_vgridshift.cpp @@ -23,7 +23,7 @@ static XYZ forward_3d(LPZ lpz, PJ *P) { PJ_COORD point = {{0,0,0,0}}; point.lpz = lpz; - if (P->vgridlist_geoid != NULL) { + 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); @@ -38,7 +38,7 @@ static LPZ reverse_3d(XYZ xyz, PJ *P) { PJ_COORD point = {{0,0,0,0}}; point.xyz = xyz; - if (P->vgridlist_geoid != NULL) { + 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); @@ -86,7 +86,7 @@ static PJ_COORD reverse_4d(PJ_COORD obs, PJ *P) { PJ *TRANSFORMATION(vgridshift,0) { struct pj_opaque_vgridshift *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_vgridshift))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = (void *) Q; @@ -134,8 +134,8 @@ PJ *TRANSFORMATION(vgridshift,0) { P->inv4d = reverse_4d; P->fwd3d = forward_3d; P->inv3d = reverse_3d; - P->fwd = 0; - P->inv = 0; + P->fwd = nullptr; + P->inv = nullptr; P->left = PJ_IO_UNITS_ANGULAR; P->right = PJ_IO_UNITS_ANGULAR; diff --git a/src/PJ_wag3.cpp b/src/PJ_wag3.cpp index f0443688..bb1b4d49 100644 --- a/src/PJ_wag3.cpp +++ b/src/PJ_wag3.cpp @@ -35,7 +35,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(wag3) { double ts; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_wag7.cpp b/src/PJ_wag7.cpp index 2009e672..c8807f12 100644 --- a/src/PJ_wag7.cpp +++ b/src/PJ_wag7.cpp @@ -24,7 +24,7 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(wag7) { P->fwd = s_forward; - P->inv = 0; + P->inv = nullptr; P->es = 0.; return P; } diff --git a/src/PJ_wink1.cpp b/src/PJ_wink1.cpp index e466988f..de2f55ee 100644 --- a/src/PJ_wink1.cpp +++ b/src/PJ_wink1.cpp @@ -33,7 +33,7 @@ static LP s_inverse (XY xy, PJ *P) { /* Spheroidal, inverse */ PJ *PROJECTION(wink1) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/PJ_wink2.cpp b/src/PJ_wink2.cpp index 3935372a..74a47283 100644 --- a/src/PJ_wink2.cpp +++ b/src/PJ_wink2.cpp @@ -43,13 +43,13 @@ static XY s_forward (LP lp, PJ *P) { /* Spheroidal, forward */ PJ *PROJECTION(wink2) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + 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 = 0; + P->inv = nullptr; P->fwd = s_forward; return P; diff --git a/src/cct.cpp b/src/cct.cpp index 0493e721..046257da 100644 --- a/src/cct.cpp +++ b/src/cct.cpp @@ -187,7 +187,7 @@ static void print(PJ_LOG_LEVEL log_level, const char *fmt, ...) { va_start( args, fmt ); msg_buf = (char *) malloc(100000); - if( msg_buf == NULL ) { + if( msg_buf == nullptr ) { va_end( args ); return; } @@ -216,7 +216,7 @@ int main(int argc, char **argv) { 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", 0}; + const char *longflags[] = {"v=verbose", "h=help", "I=inverse", "version", nullptr}; const char *longkeys[] = { "o=output", "c=columns", @@ -224,12 +224,12 @@ int main(int argc, char **argv) { "z=height", "t=time", "s=skip-lines", - 0}; + nullptr}; fout = stdout; o = opt_parse (argc, argv, "hvI", "cdozts", longflags, longkeys); - if (0==o) + if (nullptr==o) return 0; if (opt_given (o, "h") || argc==1) { @@ -250,7 +250,7 @@ int main(int argc, char **argv) { if (opt_given (o, "o")) fout = fopen (opt_arg (o, "output"), "wt"); - if (0==fout) { + if (nullptr==fout) { print (PJ_LOG_ERROR, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output")); free (o); return 1; @@ -296,8 +296,8 @@ int main(int argc, char **argv) { } /* Setup transformation */ - P = proj_create_argv (0, o->pargc, o->pargv); - if ((0==P) || (0==o->pargc)) { + 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); @@ -324,7 +324,7 @@ int main(int argc, char **argv) { /* Allocate input buffer */ buf = static_cast(calloc (1, 10000)); - if (0==buf) { + if (nullptr==buf) { print (PJ_LOG_ERROR, "%s: Out of memory\n", o->progname); pj_free (P); free (o); @@ -340,7 +340,7 @@ int main(int argc, char **argv) { void *ret = fgets (buf, 10000, o->input); char *c = column (buf, 1); opt_eof_handler (o); - if (0==ret) { + if (nullptr==ret) { print (PJ_LOG_ERROR, "Read error in record %d\n", (int) o->record_index); continue; } diff --git a/src/emess.cpp b/src/emess.cpp index eb2ac9d6..144e9e23 100644 --- a/src/emess.cpp +++ b/src/emess.cpp @@ -29,11 +29,11 @@ emess(int code, const char *fmt, ...) { va_start(args, fmt); /* prefix program name, if given */ - if (fmt != NULL) + 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 != NULL && *emess_dat.File_name) { + 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); diff --git a/src/geod.cpp b/src/geod.cpp index bb52818e..d7741679 100644 --- a/src/geod.cpp +++ b/src/geod.cpp @@ -17,7 +17,7 @@ tag = '#', /* beginning of line tag character */ pos_azi = 0, /* output azimuths as positive values */ inverse = 0; /* != 0 then inverse geodesic */ static char -*oform = (char *)0, /* output format for decimal degrees */ +*oform = (char *)nullptr, /* output format for decimal degrees */ *osform = "%.3f", /* output format for S */ pline[50], /* work string */ *usage = @@ -133,7 +133,7 @@ int main(int argc, char **argv) { FILE *fid; static int eargc = 0, c; - if ((emess_dat.Prog_name = strrchr(*argv,'/')) != NULL) ++emess_dat.Prog_name; + 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 ) { @@ -223,7 +223,7 @@ noargument: emess(1,"missing argument for -%c",*arg); fid = stdin; emess_dat.File_name = ""; } else { - if ((fid = fopen(*eargv, "r")) == NULL) { + if ((fid = fopen(*eargv, "r")) == nullptr) { emess(-2, *eargv, "input file"); continue; } @@ -232,7 +232,7 @@ noargument: emess(1,"missing argument for -%c",*arg); emess_dat.File_line = 0; process(fid); (void)fclose(fid); - emess_dat.File_name = (char *)0; + emess_dat.File_name = (char *)nullptr; } } exit(0); /* normal completion */ diff --git a/src/geod_set.cpp b/src/geod_set.cpp index b5bd0667..b9e9c42f 100644 --- a/src/geod_set.cpp +++ b/src/geod_set.cpp @@ -11,7 +11,7 @@ void geod_set(int argc, char **argv) { - paralist *start = 0, *curr; + paralist *start = nullptr, *curr; double es; char *name; int i; @@ -22,7 +22,7 @@ geod_set(int argc, char **argv) { start = curr = pj_mkparam(argv[0]); if (!curr) emess(1, "memory allocation failed"); - for (i = 1; curr != 0 && i < argc; ++i) { + for (i = 1; curr != nullptr && i < argc; ++i) { curr->next = pj_mkparam(argv[i]); if (!curr->next) emess(1, "memory allocation failed"); @@ -31,7 +31,7 @@ geod_set(int argc, char **argv) { /* 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(NULL,start, "sunits").s) != NULL) { + 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) ; @@ -44,27 +44,27 @@ geod_set(int argc, char **argv) { geod_f = es/(1 + sqrt(1 - es)); geod_ini(); /* check if line or arc mode */ - if (pj_param(NULL,start, "tlat_1").i) { + if (pj_param(nullptr,start, "tlat_1").i) { double del_S; #undef f - phi1 = pj_param(NULL,start, "rlat_1").f; - lam1 = pj_param(NULL,start, "rlon_1").f; - if (pj_param(NULL,start, "tlat_2").i) { - phi2 = pj_param(NULL,start, "rlat_2").f; - lam2 = pj_param(NULL,start, "rlon_2").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(NULL,start, "dS").f) != 0.) { - al12 = pj_param(NULL,start, "rA").f; + } 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(NULL,start, "in_A").i) > 0) { - if ((del_alpha = pj_param(NULL,start, "rdel_A").f) == 0.0) + 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(NULL,start, "ddel_S").f)) != 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(NULL,start, "in_S").i) <= 0) + } else if ((n_S = pj_param(nullptr,start, "in_S").i) <= 0) emess(1,"no interval divisor selected"); } /* free up linked list */ diff --git a/src/geodesic.cpp b/src/geodesic.cpp index 220dcd7f..badfc9fa 100644 --- a/src/geodesic.cpp +++ b/src/geodesic.cpp @@ -693,12 +693,12 @@ real geod_genposition(const struct geod_geodesicline* l, void geod_setdistance(struct geod_geodesicline* l, real s13) { l->s13 = s13; - l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, 0, 0, 0, 0, 0, 0, 0, 0); + l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } static void geod_setarc(struct geod_geodesicline* l, real a13) { l->a13 = a13; l->s13 = NaN; - geod_genposition(l, GEOD_ARCMODE, l->a13, 0, 0, 0, &l->s13, 0, 0, 0, 0); + geod_genposition(l, GEOD_ARCMODE, l->a13, nullptr, nullptr, nullptr, &l->s13, nullptr, nullptr, nullptr, nullptr); } void geod_gensetdistance(struct geod_geodesicline* l, @@ -710,7 +710,7 @@ void geod_gensetdistance(struct geod_geodesicline* l, void geod_position(const struct geod_geodesicline* l, real s12, real* plat2, real* plon2, real* pazi2) { - geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, 0, 0, 0, 0, 0); + geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, nullptr, nullptr, nullptr, nullptr, nullptr); } real geod_gendirect(const struct geod_geodesic* g, @@ -742,7 +742,7 @@ void geod_direct(const struct geod_geodesic* g, real s12, real* plat2, real* plon2, real* pazi2) { geod_gendirect(g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, plat2, plon2, pazi2, - 0, 0, 0, 0, 0); + nullptr, nullptr, nullptr, nullptr, nullptr); } static real geod_geninverse_int(const struct geod_geodesic* g, @@ -858,9 +858,9 @@ static real geod_geninverse_int(const struct geod_geodesic* g, sig12 = atan2(maxx((real)(0), csig1 * ssig2 - ssig1 * csig2), csig1 * csig2 + ssig1 * ssig2); Lengths(g, g->n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, &s12x, &m12x, 0, - (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, - (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, + cbet1, cbet2, &s12x, &m12x, nullptr, + (outmask & GEOD_GEODESICSCALE) ? &M12 : nullptr, + (outmask & GEOD_GEODESICSCALE) ? &M21 : nullptr, Ca); /* Add the check for sig12 since zero length geodesics might yield m12 < * 0. Test case was @@ -983,9 +983,9 @@ static real geod_geninverse_int(const struct geod_geodesic* g, fabs(salp1 - salp1b) + (calp1 - calp1b) < tolb); } Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, &s12x, &m12x, 0, - (outmask & GEOD_GEODESICSCALE) ? &M12 : 0, - (outmask & GEOD_GEODESICSCALE) ? &M21 : 0, Ca); + cbet1, cbet2, &s12x, &m12x, nullptr, + (outmask & GEOD_GEODESICSCALE) ? &M12 : nullptr, + (outmask & GEOD_GEODESICSCALE) ? &M21 : nullptr, Ca); m12x *= g->b; s12x *= g->b; a12 = sig12 / degree; @@ -1115,9 +1115,9 @@ void geod_inverseline(struct geod_geodesicline* l, real lat1, real lon1, real lat2, real lon2, unsigned caps) { real salp1, calp1, - a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, 0, - &salp1, &calp1, 0, 0, - 0, 0, 0, 0), + a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, nullptr, + &salp1, &calp1, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr), azi1 = atan2dx(salp1, calp1); caps = caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE; /* Ensure that a12 can be converted to a distance */ @@ -1129,7 +1129,7 @@ void geod_inverseline(struct geod_geodesicline* l, void geod_inverse(const struct geod_geodesic* g, real lat1, real lon1, real lat2, real lon2, real* ps12, real* pazi1, real* pazi2) { - geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, 0, 0, 0, 0); + geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, nullptr, nullptr, nullptr, nullptr); } real SinCosSeries(boolx sinp, real sinx, real cosx, const real c[], int n) { @@ -1371,7 +1371,7 @@ real InverseStart(const struct geod_geodesic* g, * Inverse. */ Lengths(g, g->n, pi + bet12a, sbet1, -cbet1, dn1, sbet2, cbet2, dn2, - cbet1, cbet2, 0, &m12b, &m0, 0, 0, Ca); + cbet1, cbet2, nullptr, &m12b, &m0, nullptr, nullptr, Ca); x = -1 + m12b / (cbet1 * cbet2 * m0 * pi); betscale = x < -(real)(0.01) ? sbet12a / x : -g->f * sq(cbet1) * pi; @@ -1531,7 +1531,7 @@ real Lambda12(const struct geod_geodesic* g, dlam12 = - 2 * g->f1 * dn1 / sbet1; else { Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, - cbet1, cbet2, 0, &dlam12, 0, 0, 0, Ca); + cbet1, cbet2, nullptr, &dlam12, nullptr, nullptr, nullptr, Ca); dlam12 *= g->f1 / (calp2 * cbet2); } } @@ -1819,7 +1819,7 @@ int transit(real lon1, real lon2) { /* Compute lon12 the same way as Geodesic::Inverse. */ lon1 = AngNormalize(lon1); lon2 = AngNormalize(lon2); - lon12 = AngDiff(lon1, lon2, 0); + lon12 = AngDiff(lon1, lon2, nullptr); return lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 : (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0); } @@ -1893,7 +1893,7 @@ void geod_polygon_addpoint(const struct geod_geodesic* g, } else { real s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ geod_geninverse(g, p->lat, p->lon, lat, lon, - &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); + &s12, nullptr, nullptr, nullptr, nullptr, nullptr, p->polyline ? nullptr : &S12); accadd(p->P, s12); if (!p->polyline) { accadd(p->A, S12); @@ -1912,8 +1912,8 @@ void geod_polygon_addedge(const struct geod_geodesic* g, * lon is to make CLang static analyzer happy. */ real lat = 0, lon = 0, S12 = 0; geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, - &lat, &lon, 0, - 0, 0, 0, 0, p->polyline ? 0 : &S12); + &lat, &lon, nullptr, + nullptr, nullptr, nullptr, nullptr, p->polyline ? nullptr : &S12); accadd(p->P, s); if (!p->polyline) { accadd(p->A, S12); @@ -1940,7 +1940,7 @@ unsigned geod_polygon_compute(const struct geod_geodesic* g, return p->num; } geod_geninverse(g, p->lat, p->lon, p->lat0, p->lon0, - &s12, 0, 0, 0, 0, 0, &S12); + &s12, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); if (pP) *pP = accsum(p->P, s12); acccopy(p->A, t); accadd(t, S12); @@ -1989,7 +1989,7 @@ unsigned geod_polygon_testpoint(const struct geod_geodesic* g, geod_geninverse(g, i == 0 ? p->lat : lat, i == 0 ? p->lon : lon, i != 0 ? p->lat0 : lat, i != 0 ? p->lon0 : lon, - &s12, 0, 0, 0, 0, 0, p->polyline ? 0 : &S12); + &s12, nullptr, nullptr, nullptr, nullptr, nullptr, p->polyline ? nullptr : &S12); perimeter += s12; if (!p->polyline) { tempsum += S12; @@ -2051,12 +2051,12 @@ unsigned geod_polygon_testedge(const struct geod_geodesic* g, happy. */ real lat = 0, lon = 0, s12, S12 = 0; geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, - &lat, &lon, 0, - 0, 0, 0, 0, &S12); + &lat, &lon, nullptr, + nullptr, nullptr, nullptr, nullptr, &S12); tempsum += S12; crossings += transitdirect(p->lon, lon); geod_geninverse(g, lat, lon, p->lat0, p->lon0, - &s12, 0, 0, 0, 0, 0, &S12); + &s12, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); perimeter += s12; tempsum += S12; crossings += transit(lon, p->lon0); diff --git a/src/geodtest.cpp b/src/geodtest.cpp index 0ee86d5c..6b3ea8b2 100644 --- a/src/geodtest.cpp +++ b/src/geodtest.cpp @@ -157,7 +157,7 @@ static int testdirect() { s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, s12, - &lat2a, &lon2a, &azi2a, 0, + &lat2a, &lon2a, &azi2a, nullptr, &m12a, &M12a, &M21a, &S12a); result += checkEquals(lat2, lat2a, 1e-13); result += checkEquals(lon2, lon2a, 1e-13); @@ -246,7 +246,7 @@ static int GeodSolve4() { int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 36.493349428792, 0, 36.49334942879201, .0000008, - &s12, 0, 0); + &s12, nullptr, nullptr); result += checkEquals(s12, 0.072, 0.5e-3); return result; } @@ -277,13 +277,13 @@ static int GeodSolve6() { int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 88.202499451857, 0, - -88.202499451857, 179.981022032992859592, &s12, 0, 0); + -88.202499451857, 179.981022032992859592, &s12, nullptr, nullptr); result += checkEquals(s12, 20003898.214, 0.5e-3); geod_inverse(&g, 89.262080389218, 0, - -89.262080389218, 179.992207982775375662, &s12, 0, 0); + -89.262080389218, 179.992207982775375662, &s12, nullptr, nullptr); result += checkEquals(s12, 20003925.854, 0.5e-3); geod_inverse(&g, 89.333123580033, 0, - -89.333123580032997687, 179.99295812360148422, &s12, 0, 0); + -89.333123580032997687, 179.99295812360148422, &s12, nullptr, nullptr); result += checkEquals(s12, 20003926.881, 0.5e-3); return result; } @@ -295,7 +295,7 @@ static int GeodSolve9() { int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 56.320923501171, 0, - -56.320923501171, 179.664747671772880215, &s12, 0, 0); + -56.320923501171, 179.664747671772880215, &s12, nullptr, nullptr); result += checkEquals(s12, 19993558.287, 0.5e-3); return result; } @@ -308,7 +308,7 @@ static int GeodSolve10() { int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 52.784459512564, 0, - -52.784459512563990912, 179.634407464943777557, &s12, 0, 0); + -52.784459512563990912, 179.634407464943777557, &s12, nullptr, nullptr); result += checkEquals(s12, 19991596.095, 0.5e-3); return result; } @@ -321,7 +321,7 @@ static int GeodSolve11() { int result = 0; geod_init(&g, wgs84_a, wgs84_f); geod_inverse(&g, 48.522876735459, 0, - -48.52287673545898293, 179.599720456223079643, &s12, 0, 0); + -48.52287673545898293, 179.599720456223079643, &s12, nullptr, nullptr); result += checkEquals(s12, 19989144.774, 0.5e-3); return result; } @@ -367,7 +367,7 @@ static int GeodSolve15() { int result = 0; geod_init(&g, 6.4e6, -1/150.0); geod_gendirect(&g, 1, 2, 3, 0, 4, - 0, 0, 0, 0, 0, 0, 0, &S12); + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); result += checkEquals(S12, 23700, 0.5); return result; } @@ -381,12 +381,12 @@ static int GeodSolve17() { unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_gendirect(&g, 40, -75, -10, flags, 2e7, - &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, -254, 1); result += checkEquals(azi2, -170, 1); geod_lineinit(&l, &g, 40, -75, -10, 0); - geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -39, 1); result += checkEquals(lon2, -254, 1); result += checkEquals(azi2, -170, 1); @@ -407,7 +407,7 @@ static int GeodSolve26() { struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, 0); - geod_geninverse(&g, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, &S12); + geod_geninverse(&g, 1, 2, 3, 4, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); result += checkEquals(S12, 49911046115.0, 0.5); return result; } @@ -419,7 +419,7 @@ static int GeodSolve28() { struct geod_geodesic g; int result = 0; geod_init(&g, 6.4e6, 0.1); - a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, 0, 0, 0, 0, 0, 0, 0, 0); + a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(a12, 48.55570690, 0.5e-8); return result; } @@ -527,12 +527,12 @@ static int GeodSolve61() { unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_gendirect(&g, 45, 0, -0.000000000000000003, flags, 1e7, - &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 45.30632, 0.5e-5); result += checkEquals(lon2, -180, 0.5e-5); result += checkEquals(fabs(azi2), 180, 0.5e-5); geod_inverseline(&l, &g, 45, 0, 80, -0.000000000000000003, 0); - geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 45.30632, 0.5e-5); result += checkEquals(lon2, -180, 0.5e-5); result += checkEquals(fabs(azi2), 180, 0.5e-5); @@ -585,11 +585,11 @@ static int GeodSolve67() { unsigned flags = GEOD_LONG_UNROLL; geod_init(&g, wgs84_a, wgs84_f); geod_inverseline(&l, &g, -5, -0.000000000000002, -10, 180, 0); - geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, 4.96445, 0.5e-5); result += checkEquals(lon2, -180.00000, 0.5e-5); result += checkEquals(azi2, -0.00000, 0.5e-5); - geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, 0, 0, 0, 0, 0); + geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkEquals(lat2, -87.52461, 0.5e-5); result += checkEquals(lon2, -0.00000, 0.5e-5); result += checkEquals(azi2, -180.00000, 0.5e-5); @@ -647,7 +647,7 @@ static void polylength(const struct geod_geodesic* g, geod_polygon_init(&p, 1); for (i = 0; i < N; ++i) geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); - geod_polygon_compute(g, &p, 0, 1, 0, perimeter); + geod_polygon_compute(g, &p, 0, 1, nullptr, perimeter); } static int GeodSolve74() { @@ -707,10 +707,10 @@ static int GeodSolve80() { struct geod_geodesicline l; int result = 0; geod_init(&g, wgs84_a, wgs84_f); - geod_geninverse(&g, 0, 0, 0, 90, 0, 0, 0, 0, &M12, &M21, 0); + geod_geninverse(&g, 0, 0, 0, 90, nullptr, nullptr, nullptr, nullptr, &M12, &M21, nullptr); result += checkEquals(M12, -0.00528427534, 0.5e-10); result += checkEquals(M21, -0.00528427534, 0.5e-10); - geod_geninverse(&g, 0, 0, 1e-6, 1e-6, 0, 0, 0, 0, &M12, &M21, 0); + geod_geninverse(&g, 0, 0, 1e-6, 1e-6, nullptr, nullptr, nullptr, nullptr, &M12, &M21, nullptr); result += checkEquals(M12, 1, 0.5e-10); result += checkEquals(M21, 1, 0.5e-10); a12 = geod_geninverse(&g, 20.001, 0, 20.001, 0, @@ -735,7 +735,7 @@ static int GeodSolve80() { result += checkEquals(S12, 127516405431022, 0.5); /* An incapable line which can't take distance as input */ geod_lineinit(&l, &g, 1, 2, 90, GEOD_LATITUDE); - a12 = geod_genposition(&l, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0); + a12 = geod_genposition(&l, 0, 1000, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); result += checkNaN(a12); return result; } @@ -853,33 +853,33 @@ static int Planimeter15() { geod_polygon_init(&p, 0); geod_polygon_addpoint(&g, &p, lat[0], lon[0]); geod_polygon_addpoint(&g, &p, lat[1], lon[1]); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, 0); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, 0); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, 0); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); - geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, 0); + geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); - geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, 0); - geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, 0); + geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, nullptr); + geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, 0); + geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, 0); + geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); - geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, 0); + geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); geod_polygon_addpoint(&g, &p, lat[2], lon[2]); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); + geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_compute(&g, &p, 0, 0, &area, 0); + geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); result += checkEquals(area, r, 0.5); - geod_polygon_compute(&g, &p, 1, 1, &area, 0); + geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); result += checkEquals(area, -r, 0.5); - geod_polygon_compute(&g, &p, 1, 0, &area, 0); + geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); result += checkEquals(area, a0-r, 0.5); - geod_polygonarea(&g, lat, lon, 3, &area, 0); + geod_polygonarea(&g, lat, lon, 3, &area, nullptr); result += checkEquals(area, r, 0.5); return result; } @@ -907,14 +907,14 @@ static int Planimeter19() { result += area == 0 ? 0 : 1; result += perim == 0 ? 0 : 1; geod_polygon_init(&p, 1); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); result += checkNaN(perim); geod_polygon_addpoint(&g, &p, 1, 1); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; return result; } @@ -944,30 +944,30 @@ static int Planimeter21() { for (i = 3; i <= 4; ++i) { geod_polygon_addpoint(&g, &p, lat, 60); geod_polygon_addpoint(&g, &p, lat, 180); - geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, 0); + geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, 0); + geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, 0); + geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, nullptr); if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, 0); + geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); - geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, 0); + geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, 0); + geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, 0); + geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, nullptr); if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, 0); + geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); geod_polygon_addpoint(&g, &p, lat, -60); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); + geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_compute(&g, &p, 0, 0, &area, 0); + geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); if (i != 4) result += checkEquals(area, i*r, 0.5); - geod_polygon_compute(&g, &p, 1, 1, &area, 0); + geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); if (i != 4) result += checkEquals(area, -i*r, 0.5); - geod_polygon_compute(&g, &p, 1, 0, &area, 0); + geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); result += checkEquals(area, -i*r + a0, 0.5); } return result; @@ -985,7 +985,7 @@ static int AddEdge1() { geod_polygon_addedge(&g, &p, 90, 1000); geod_polygon_addedge(&g, &p, 0, 1000); geod_polygon_addedge(&g, &p, -90, 1000); - geod_polygon_compute(&g, &p, 0, 1, &area, 0); + geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); result += checkEquals(area, 1000000.0, 0.01); return result; } @@ -1007,16 +1007,16 @@ static int EmptyPoly() { result += area == 0 ? 0 : 1; result += perim == 0 ? 0 : 1; geod_polygon_init(&p, 1); - geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, 0, &perim); + geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); result += checkNaN(perim); - geod_polygon_compute(&g, &p, 0, 1, 0, &perim); + geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); result += perim == 0 ? 0 : 1; geod_polygon_addpoint(&g, &p, 1, 1); - geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, 0, &perim); + geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); result += checkEquals(perim, 1000, 1e-10); - geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, 0, &perim); + geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, nullptr, &perim); result += checkEquals(perim, 156876.149, 0.5e-3); return result; } diff --git a/src/gie.cpp b/src/gie.cpp index 77c21083..21a3a279 100644 --- a/src/gie.cpp +++ b/src/gie.cpp @@ -194,7 +194,7 @@ typedef struct { FILE *fout; } gie_ctx; -ffio *F = 0; +ffio *F = nullptr; static gie_ctx T; int tests=0, succs=0, succ_fails=0, fail_fails=0, succ_rtps=0, fail_rtps=0; @@ -236,8 +236,8 @@ static const char usage[] = { int main (int argc, char **argv) { int i; - const char *longflags[] = {"v=verbose", "q=quiet", "h=help", "l=list", "version", 0}; - const char *longkeys[] = {"o=output", 0}; + 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)); @@ -248,7 +248,7 @@ int main (int argc, char **argv) { T.use_proj4_init_rules = FALSE; o = opt_parse (argc, argv, "hlvq", "o", longflags, longkeys); - if (0==o) + if (nullptr==o) return 0; if (opt_given (o, "h") || argc==1) { @@ -274,7 +274,7 @@ int main (int argc, char **argv) { if (opt_given (o, "o")) T.fout = fopen (opt_arg (o, "output"), "rt"); - if (0==T.fout) { + if (nullptr==T.fout) { fprintf (stderr, "%s: Cannot open '%s' for output\n", o->progname, opt_arg (o, "output")); free (o); return 1; @@ -294,7 +294,7 @@ int main (int argc, char **argv) { } F = ffio_create (gie_tags, n_gie_tags, 1000); - if (0==F) { + if (nullptr==F) { fprintf (stderr, "%s: No memory\n", o->progname); free (o); return 1; @@ -379,12 +379,12 @@ static int process_file (const char *fname) { if (T.skip) { proj_destroy (T.P); - T.P = 0; + T.P = nullptr; return 0; } f = fopen (fname, "rt"); - if (0==f) { + if (nullptr==f) { if (T.verbosity > 0) { fprintf (T.fout, "%sCannot open spec'd input file '%s' - bye!\n", delim, fname); return 2; @@ -400,7 +400,7 @@ static int process_file (const char *fname) { while (get_inp(F)) { if (SKIP==dispatch (F->tag, F->args)) { proj_destroy (T.P); - T.P = 0; + T.P = nullptr; return 0; } } @@ -591,10 +591,10 @@ either a conversion or a transformation) if (T.P) proj_destroy (T.P); - proj_errno_reset (0); - proj_context_use_proj4_init_rules(0, T.use_proj4_init_rules); + proj_errno_reset (nullptr); + proj_context_use_proj4_init_rules(nullptr, T.use_proj4_init_rules); - T.P = proj_create (0, F->args); + 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 */ @@ -715,7 +715,7 @@ Always returns 0. char *endp; PJ_COORD coo; - if (0==T.P) { + if (nullptr==T.P) { if (T.ignore == proj_errno(T.P)) return another_skip(); @@ -840,7 +840,7 @@ Tell GIE what to expect, when transforming the ACCEPTed input if (T.ignore==proj_errno(T.P)) return another_skip (); - if (0==T.P) { + if (nullptr==T.P) { /* If we expect failure, and fail, then it's a success... */ if (expect_failure) { /* Failed to fail correctly? */ @@ -1198,23 +1198,23 @@ static ffio *ffio_create (const char **tags, size_t n_tags, size_t max_record_si Constructor for the ffio object. ****************************************************************************************/ ffio *G = static_cast(calloc (1, sizeof (ffio))); - if (0==G) - return 0; + 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 (0==G->args) { + if (nullptr==G->args) { free (G); - return 0; + return nullptr; } G->next_args = static_cast(calloc (1, max_record_size)); - if (0==G->args) { + if (nullptr==G->args) { free (G->args); free (G); - return 0; + return nullptr; } G->args_size = 5*max_record_size; @@ -1238,7 +1238,7 @@ fclose has been called prior to ffio_destroy. free (G->args); free (G->next_args); free (G); - return 0; + return nullptr; } @@ -1255,10 +1255,10 @@ continues until a gie command verb is found at the start of a line ****************************************************************************************/ int i; char *c; - if (0==G) + if (nullptr==G) return 0; c = G->next_args; - if (0==c) + if (nullptr==c) return 0; if (0==c[0]) return 0; @@ -1279,7 +1279,7 @@ A start of a new command serves as an end delimiter for the current command 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 0; + return nullptr; } @@ -1293,7 +1293,7 @@ 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==0) + if (G==nullptr) return 0; if (at_decorative_element (G)) return 1; @@ -1312,7 +1312,7 @@ Read next line of input file. Returns 1 on success, 0 on failure. G->next_args[0] = 0; if (T.skip) return 0; - if (0==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) + if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) return 0; if (feof (G->f)) return 0; @@ -1356,7 +1356,7 @@ Make sure we're inside a -block. Return 1 on success, 0 otherwise. G->next_args[0] = 0; if (feof (G->f)) return 0; - if (0==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) + if (nullptr==fgets (G->next_args, (int) G->next_args_size - 1, G->f)) return 0; pj_chomp (G->next_args); G->next_lineno++; @@ -1415,7 +1415,7 @@ static int append_args (ffio *G) { /* +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 (0==p) + if (nullptr==p) return 0; G->args = p; G->args_size = 2 * G->args_size; @@ -1444,7 +1444,7 @@ whitespace etc. The block is stored in G->args. Returns 1 on success, 0 otherwis return 0; G->tag = at_tag (G); - if (0==G->tag) + if (nullptr==G->tag) return 0; do { diff --git a/src/mk_cheby.cpp b/src/mk_cheby.cpp index a2f90bef..5e6bfef0 100644 --- a/src/mk_cheby.cpp +++ b/src/mk_cheby.cpp @@ -23,36 +23,36 @@ makeT(int nru, int nrv) { int i; if (!(T = (Tseries *)pj_malloc(sizeof(Tseries)))) - return 0; + return nullptr; if (!(T->cu = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nru))) { pj_dalloc(T); - return 0; + return nullptr; } if (!(T->cv = (struct PW_COEF *)pj_malloc(sizeof(struct PW_COEF) * nrv))) { pj_dalloc(T->cu); pj_dalloc(T); - return 0; + return nullptr; } for (i = 0; i < nru; ++i) - T->cu[i].c = 0; + T->cu[i].c = nullptr; for (i = 0; i < nrv; ++i) - T->cv[i].c = 0; + T->cv[i].c = nullptr; return T; } Tseries * mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), int nu, int nv, int power) { int j, i, nru, nrv, *ncu, *ncv; - Tseries *T = NULL; + Tseries *T = nullptr; projUV **w; double cutres; if (!(w = (projUV **)vector2(nu, nv, sizeof(projUV)))) - return 0; + return nullptr; if (!(ncu = (int *)vector1(nu + nv, sizeof(int)))) { freev2((void **)w, nu); - return 0; + return nullptr; } ncv = ncu + nu; if (!bchgen(a, b, nu, nv, w, func)) { @@ -106,7 +106,7 @@ mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), if (ncu[j]) nru = j + 1; /* update row max */ if (ncv[j]) nrv = j + 1; } - if ((T = makeT(nru, nrv)) != NULL ) { + if ((T = makeT(nru, nrv)) != nullptr ) { T->a = a; T->b = b; T->mu = nru - 1; @@ -137,7 +137,7 @@ mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), } } } - } else if ((T = makeT(nru, nrv)) != NULL) { + } else if ((T = makeT(nru, nrv)) != nullptr) { /* else make returned Chebyshev coefficient structure */ T->mu = nru - 1; /* save row degree */ T->mv = nrv - 1; @@ -184,7 +184,7 @@ mk_cheby(projUV a, projUV b, double res, projUV *resid, projUV (*func)(projUV), pj_dalloc(T->cv[i].c); pj_dalloc(T); } - T = 0; + T = nullptr; gohome: freev2((void **) w, nu); pj_dalloc(ncu); diff --git a/src/multistresstest.cpp b/src/multistresstest.cpp index a6653e7e..234783b3 100644 --- a/src/multistresstest.cpp +++ b/src/multistresstest.cpp @@ -243,7 +243,7 @@ static void TestThread() dst_pj_list[i] = custom_pj_init_plus_ctx( ctx, test->dst_def ); { - int skipTest = (src_pj_list[i] == NULL || dst_pj_list[i] == NULL); + 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" ); @@ -335,7 +335,7 @@ static void *PosixTestThread( void *pData ) { (void)pData; TestThread(); - return NULL; + return nullptr; } #endif @@ -363,7 +363,7 @@ static int do_main(void) 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 == NULL ) + if( src_pj == nullptr ) { printf( "Unable to translate:\n%s\n", test->src_def ); test->skip = 1; @@ -371,7 +371,7 @@ static int do_main(void) continue; } - if( dst_pj == NULL ) + if( dst_pj == nullptr ) { printf( "Unable to translate:\n%s\n", test->dst_def ); test->skip = 1; @@ -439,7 +439,7 @@ static int do_main(void) active_thread_count++; pthread_create( &(ahThread[i]), &hThreadAttr, - PosixTestThread, NULL ); + PosixTestThread, nullptr ); } printf( "%d test threads launched.\n", num_threads ); diff --git a/src/nad2bin.cpp b/src/nad2bin.cpp index eb8672a5..ff8f2ebd 100644 --- a/src/nad2bin.cpp +++ b/src/nad2bin.cpp @@ -61,7 +61,7 @@ int main(int argc, char **argv) { long lam, laml, phi, phil; FILE *fp; - const char *output_file = NULL; + const char *output_file = nullptr; const char *format = "ctable2"; const char *GS_TYPE = "SECONDS"; @@ -81,7 +81,7 @@ int main(int argc, char **argv) { { format = argv[++i]; } - else if( output_file == NULL ) + else if( output_file == nullptr ) { output_file = argv[i]; } @@ -89,7 +89,7 @@ int main(int argc, char **argv) { Usage(); } - if( output_file == NULL ) + if( output_file == nullptr ) Usage(); fprintf( stdout, "Output Binary File Format: %s\n", format ); @@ -99,7 +99,7 @@ int main(int argc, char **argv) { /* ==================================================================== */ memset(ct.id,0,MAX_TAB_ID); - if ( NULL == fgets(ct.id, MAX_TAB_ID, stdin) ) { + if ( nullptr == fgets(ct.id, MAX_TAB_ID, stdin) ) { perror("fgets"); exit(1); } diff --git a/src/nad_init.cpp b/src/nad_init.cpp index 8b024ac7..1e2b150d 100644 --- a/src/nad_init.cpp +++ b/src/nad_init.cpp @@ -83,11 +83,11 @@ int nad_ctable_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) /* read all the actual shift values */ a_size = ct->lim.lam * ct->lim.phi; ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); - if( ct->cvs == NULL + if( ct->cvs == nullptr || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) { pj_dalloc( ct->cvs ); - ct->cvs = NULL; + ct->cvs = nullptr; pj_log( ctx, PJ_LOG_ERROR, "ctable loading failed on fread() - binary incompatible?" ); @@ -111,12 +111,12 @@ struct CTABLE *nad_ctable_init( projCtx ctx, PAFile fid ) /* read the table header */ ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if( ct == NULL + if( ct == nullptr || pj_ctx_fread( ctx, ct, sizeof(struct CTABLE), 1, fid ) != 1 ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_dalloc( ct ); - return NULL; + return nullptr; } /* do some minimal validation to ensure the structure isn't corrupt */ @@ -125,7 +125,7 @@ struct CTABLE *nad_ctable_init( projCtx ctx, PAFile fid ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_dalloc( ct ); - return NULL; + return nullptr; } /* trim white space and newlines off id */ @@ -137,7 +137,7 @@ struct CTABLE *nad_ctable_init( projCtx ctx, PAFile fid ) break; } - ct->cvs = NULL; + ct->cvs = nullptr; return ct; } @@ -158,13 +158,13 @@ int nad_ctable2_load( projCtx ctx, struct CTABLE *ct, PAFile fid ) /* read all the actual shift values */ a_size = ct->lim.lam * ct->lim.phi; ct->cvs = (FLP *) pj_malloc(sizeof(FLP) * a_size); - if( ct->cvs == NULL + if( ct->cvs == nullptr || pj_ctx_fread(ctx, ct->cvs, sizeof(FLP), a_size, fid) != a_size ) { pj_dalloc( ct->cvs ); - ct->cvs = NULL; + ct->cvs = nullptr; - if( getenv("PROJ_DEBUG") != NULL ) + if( getenv("PROJ_DEBUG") != nullptr ) { fprintf( stderr, "ctable2 loading failed on fread() - binary incompatible?\n" ); @@ -197,7 +197,7 @@ struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) if( pj_ctx_fread( ctx, header, sizeof(header), 1, fid ) != 1 ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; + return nullptr; } if( !IS_LSB ) @@ -210,15 +210,15 @@ struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) { pj_log( ctx, PJ_LOG_ERROR, "ctable2 - wrong header!" ); pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; + return nullptr; } /* read the table header */ ct = (struct CTABLE *) pj_malloc(sizeof(struct CTABLE)); - if( ct == NULL ) + if( ct == nullptr ) { pj_ctx_set_errno( ctx, ENOMEM ); - return NULL; + return nullptr; } memcpy( ct->id, header + 16, 80 ); @@ -235,7 +235,7 @@ struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_dalloc( ct ); - return NULL; + return nullptr; } /* trim white space and newlines off id */ @@ -247,7 +247,7 @@ struct CTABLE *nad_ctable2_init( projCtx ctx, PAFile fid ) break; } - ct->cvs = NULL; + ct->cvs = nullptr; return ct; } @@ -271,16 +271,16 @@ struct CTABLE *nad_init(projCtx ctx, char *name) /* -------------------------------------------------------------------- */ strcpy(fname, name); if (!(fid = pj_open_lib(ctx, fname, "rb"))) { - return 0; + return nullptr; } ct = nad_ctable_init( ctx, fid ); - if( ct != NULL ) + if( ct != nullptr ) { if( !nad_ctable_load( ctx, ct, fid ) ) { nad_free( ct ); - ct = NULL; + ct = nullptr; } } @@ -297,7 +297,7 @@ struct CTABLE *nad_init(projCtx ctx, char *name) void nad_free(struct CTABLE *ct) { if (ct) { - if( ct->cvs != NULL ) + if( ct->cvs != nullptr ) pj_dalloc(ct->cvs); pj_dalloc(ct); diff --git a/src/optargpm.h b/src/optargpm.h index 48cb260b..edd9fbee 100644 --- a/src/optargpm.h +++ b/src/optargpm.h @@ -107,8 +107,8 @@ int main(int argc, char **argv) { char *longkeys[] = {"o=output", "hello", 0}; o = opt_parse (argc, argv, "hv", "o", longflags, longkeys); - if (0==o) - return 0; + if (nullptr==o) + return nullptr; if (opt_given (o, "h")) { @@ -135,14 +135,14 @@ int main(int argc, char **argv) { char buf[1000]; int ret = fgets (buf, 1000, o->input); opt_eof_handler (o); - if (0==ret) { + if (nullptr==ret) { fprintf (stderr, "Read error in record %d\n", (int) o->record_index); continue; } do_what_needs_to_be_done (buf); } - return 0; + return nullptr; } @@ -229,22 +229,22 @@ struct OPTARGS { /* name of file currently read from */ char *opt_filename (OPTARGS *opt) { - if (0==opt) - return 0; + if (nullptr==opt) + return nullptr; if (0==opt->fargc) return opt->flaglevel; return opt->fargv[opt->input_index]; } static int opt_eof (OPTARGS *opt) { - if (0==opt) + if (nullptr==opt) return 1; return feof (opt->input); } /* record number of most recently read record */ int opt_record (OPTARGS *opt) { - if (0==opt) + if (nullptr==opt) return 0; return opt->record_index + 1; } @@ -252,11 +252,11 @@ int opt_record (OPTARGS *opt) { /* handle closing/opening of a "stream-of-streams" */ int opt_input_loop (OPTARGS *opt, int binary) { - if (0==opt) + if (nullptr==opt) return 0; /* most common case: increment record index and read on */ - if ( (opt->input!=0) && !feof (opt->input) ) { + if ( (opt->input!=nullptr) && !feof (opt->input) ) { opt->record_index++; return 1; } @@ -264,7 +264,7 @@ int opt_input_loop (OPTARGS *opt, int binary) { opt->record_index = 0; /* no input files specified - read from stdin */ - if ((0==opt->fargc) && (0==opt->input)) { + if ((0==opt->fargc) && (nullptr==opt->input)) { opt->input = stdin; return 1; } @@ -275,18 +275,18 @@ int opt_input_loop (OPTARGS *opt, int binary) { return 0; /* end if no more input */ - if (0!=opt->input) + if (nullptr!=opt->input) fclose (opt->input); if (opt->input_index >= opt->fargc) return 0; /* otherwise, open next input file */ opt->input = fopen (opt->fargv[opt->input_index++], binary? "rb": "rt"); - if (0 != opt->input) + if (nullptr != opt->input) return 1; /* ignore non-existing files - go on! */ - if (0==opt->input) + if (nullptr==opt->input) return opt_input_loop (opt, binary); return 0; } @@ -317,16 +317,16 @@ static int opt_raise_flag (OPTARGS *opt, int ordinal) { /* Find the ordinal value of any (short or long) option */ static int opt_ordinal (OPTARGS *opt, char *option) { int i; - if (0==opt) + if (nullptr==opt) return 0; - if (0==option) + if (nullptr==option) return 0; if (0==option[0]) return 0; /* An ordinary -o style short option */ if (strlen (option)==1) { /* Undefined option? */ - if (0==opt->optarg[(int) option[0]]) + if (nullptr==opt->optarg[(int) option[0]]) return 0; return (int) option[0]; } @@ -334,9 +334,9 @@ static int opt_ordinal (OPTARGS *opt, char *option) { /* --longname style long options are slightly harder */ for (i = 0; i < 64; i++) { const char **f = opt->longflags; - if (0==f) + if (nullptr==f) break; - if (0==f[i]) + if (nullptr==f[i]) break; if (0==strcmp(f[i], "END")) break; @@ -346,7 +346,7 @@ static int opt_ordinal (OPTARGS *opt, char *option) { /* long alias? - return ordinal for corresponding short */ if ((strlen(f[i]) > 2) && (f[i][1]=='=') && (0==strcmp(f[i]+2, option))) { /* Undefined option? */ - if (0==opt->optarg[(int) f[i][0]]) + if (nullptr==opt->optarg[(int) f[i][0]]) return 0; return (int) f[i][0]; } @@ -354,9 +354,9 @@ static int opt_ordinal (OPTARGS *opt, char *option) { for (i = 0; i < 64; i++) { const char **v = opt->longkeys; - if (0==v) + if (nullptr==v) return 0; - if (0==v[i]) + if (nullptr==v[i]) return 0; if (0==strcmp (v[i], "END")) return 0; @@ -366,14 +366,14 @@ static int opt_ordinal (OPTARGS *opt, char *option) { /* long alias? - return ordinal for corresponding short */ if ((strlen(v[i]) > 2) && (v[i][1]=='=') && (0==strcmp(v[i]+2, option))) { /* Undefined option? */ - if (0==opt->optarg[(int) v[i][0]]) + if (nullptr==opt->optarg[(int) v[i][0]]) return 0; return (int) v[i][0]; } } /* kill some potential compiler warnings about unused functions */ - (void) opt_eof (0); + (void) opt_eof (nullptr); return 0; } @@ -394,7 +394,7 @@ int opt_given (OPTARGS *opt, char *option) { char *opt_arg (OPTARGS *opt, char *option) { int ordinal = opt_ordinal (opt, option); if (0==ordinal) - return 0; + return nullptr; return opt->optarg[ordinal]; } @@ -418,8 +418,8 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, OPTARGS *o; o = (OPTARGS *) calloc (1, sizeof(OPTARGS)); - if (0==o) - return 0; + if (nullptr==o) + return nullptr; o->argc = argc; o->argv = argv; @@ -448,10 +448,10 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, continue; if ('='!=longflags[i][1]) continue; - if (0==strchr (flags, longflags[i][0])) { + if (nullptr==strchr (flags, longflags[i][0])) { fprintf (stderr, "%s: Invalid alias - '%s'. Valid short flags are '%s'\n", o->progname, longflags[i], flags); free (o); - return 0; + return nullptr; } } for (i = 0; longkeys && longkeys[i]; i++) { @@ -460,10 +460,10 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, continue; if ('='!=longkeys[i][1]) continue; - if (0==strchr (keys, longkeys[i][0])) { + if (nullptr==strchr (keys, longkeys[i][0])) { fprintf (stderr, "%s: Invalid alias - '%s'. Valid short flags are '%s'\n", o->progname, longkeys[i], keys); free (o); - return 0; + return nullptr; } } @@ -471,20 +471,20 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, /* flaglevel array to provide a pseudo-filename for the case of reading from stdin */ strcpy (o->flaglevel, ""); - for (i = 128; (longflags != 0) && (longflags[i - 128] != 0); i++) { + for (i = 128; (longflags != nullptr) && (longflags[i - 128] != nullptr); i++) { if (i==192) { free (o); fprintf (stderr, "Too many flag style long options\n"); - return 0; + return nullptr; } o->optarg[i] = o->flaglevel; } - for (i = 192; (longkeys != 0) && (longkeys[i - 192] != 0); i++) { + for (i = 192; (longkeys != nullptr) && (longkeys[i - 192] != nullptr); i++) { if (i==256) { free (o); fprintf (stderr, "Too many value style long options\n"); - return 0; + return nullptr; } o->optarg[i] = argv[0]; } @@ -500,7 +500,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, if ('-' != argv[i][0]) break; - if (0==o->margv) + if (nullptr==o->margv) o->margv = argv + i; o->margc++; @@ -526,7 +526,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, c = opt_ordinal (o, crepr); if (0==c) { fprintf (stderr, "Invalid option \"%s\"\n", crepr); - return (OPTARGS *) 0; + return nullptr; } /* inline (gnu) --foo=bar style arg */ @@ -534,7 +534,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, *equals = '='; if (opt_is_flag (o, c)) { fprintf (stderr, "Option \"%s\" takes no arguments\n", crepr); - return (OPTARGS *) 0; + return nullptr; } o->optarg[c] = equals + 1; break; @@ -544,7 +544,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, if (!opt_is_flag (o, c)) { if ((argc==i + 1) || ('+'==argv[i+1][0]) || ('-'==argv[i+1][0])) { fprintf (stderr, "Missing argument for option \"%s\"\n", crepr); - return (OPTARGS *) 0; + return nullptr; } o->optarg[c] = argv[i + 1]; i++; /* eat the arg */ @@ -553,7 +553,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, if (!opt_is_flag (o, c)) { fprintf (stderr, "Expected flag style long option here, but got \"%s\"\n", crepr); - return (OPTARGS *) 0; + return nullptr; } /* Flag style option, i.e. taking no arguments */ @@ -562,9 +562,9 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, } /* classic short options */ - if (0==o->optarg[c]) { + if (nullptr==o->optarg[c]) { fprintf (stderr, "Invalid option \"%s\"\n", crepr); - return (OPTARGS *) 0; + return nullptr; } /* Flag style option, i.e. taking no arguments */ @@ -580,7 +580,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, if ((argc==i + 1) || ('+'==argv[i+1][0]) || ('-'==argv[i+1][0])) { fprintf (stderr, "Bad or missing arg for option \"%s\"\n", crepr); - return (OPTARGS *) 0; + return nullptr; } o->optarg[(int) c] = argv[i + 1]; i++; @@ -617,7 +617,7 @@ OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, if ('-' == argv[i][0]) { free (o); fprintf (stderr, "+ and - style options must not be mixed\n"); - return 0; + return nullptr; } if ('+' != argv[i][0]) diff --git a/src/pj_apply_gridshift.cpp b/src/pj_apply_gridshift.cpp index 45ce5c8e..4c8115cc 100644 --- a/src/pj_apply_gridshift.cpp +++ b/src/pj_apply_gridshift.cpp @@ -57,7 +57,7 @@ int pj_apply_gridshift( projCtx ctx, const char *nadgrids, int inverse, gridlist = pj_gridlist_from_nadgrids( ctx, nadgrids, &grid_count ); - if( gridlist == NULL || grid_count == 0 ) + if( gridlist == nullptr || grid_count == 0 ) return ctx->last_errno; ret = pj_apply_gridshift_3( ctx, gridlist, grid_count, inverse, @@ -86,18 +86,18 @@ int pj_apply_gridshift_2( PJ *defn, int inverse, double *x, double *y, double *z ) { - if( defn->catalog_name != NULL ) + if( defn->catalog_name != nullptr ) return pj_gc_apply_gridshift( defn, inverse, point_count, point_offset, x, y, z ); - if( defn->gridlist == NULL ) + if( defn->gridlist == nullptr ) { defn->gridlist = pj_gridlist_from_nadgrids( pj_get_ctx( defn ), pj_param(defn->ctx, defn->params,"snadgrids").s, &(defn->gridlist_count) ); - if( defn->gridlist == NULL || defn->gridlist_count == 0 ) + if( defn->gridlist == nullptr || defn->gridlist_count == 0 ) return defn->ctx->last_errno; } @@ -135,7 +135,7 @@ static struct CTABLE* find_ctable(projCtx ctx, LP input, int grid_count, PJ_GRID { PJ_GRIDINFO *child; - for( child = gi->child; child != NULL; child = child->next ) + for( child = gi->child; child != nullptr; child = child->next ) { struct CTABLE *ct1 = child->ct; epsilon = (fabs(ct1->del.phi)+fabs(ct1->del.lam))/10000.0; @@ -150,24 +150,24 @@ static struct CTABLE* find_ctable(projCtx ctx, LP input, int grid_count, PJ_GRID } /* If we didn't find a child then nothing more to do */ - if( child == NULL ) break; + if( child == nullptr ) break; /* Otherwise use the child, first checking it's children */ gi = child; ct = child->ct; } /* load the grid shift info if we don't have it. */ - if( ct->cvs == NULL) { + if( ct->cvs == nullptr) { if (!pj_gridinfo_load( ctx, gi ) ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); - return NULL; + return nullptr; } } /* if we get this far we have found a suitable grid */ return ct; } - return NULL; + return nullptr; } /************************************************************************/ @@ -185,7 +185,7 @@ int pj_apply_gridshift_3( projCtx ctx, PJ_GRIDINFO **gridlist, int gridlist_coun static int debug_count = 0; (void) z; - if( gridlist== NULL || gridlist_count == 0 ) + if( gridlist== nullptr || gridlist_count == 0 ) { pj_ctx_set_errno(ctx, PJD_ERR_FAILED_TO_LOAD_GRID); return PJD_ERR_FAILED_TO_LOAD_GRID; @@ -205,7 +205,7 @@ int pj_apply_gridshift_3( projCtx ctx, PJ_GRIDINFO **gridlist, int gridlist_coun output.lam = HUGE_VAL; ct = find_ctable(ctx, input, gridlist_count, gridlist); - if( ct != NULL ) + if( ct != nullptr ) { output = nad_cvt( input, inverse, ct ); @@ -280,14 +280,14 @@ int proj_hgrid_init(PJ* P, const char *grids) { char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); sprintf(sgrids, "%s%s", "s", grids); - if (P->gridlist == NULL) { + if (P->gridlist == nullptr) { P->gridlist = pj_gridlist_from_nadgrids( P->ctx, pj_param(P->ctx, P->params, sgrids).s, &(P->gridlist_count) ); - if( P->gridlist == NULL || P->gridlist_count == 0 ) { + if( P->gridlist == nullptr || P->gridlist_count == 0 ) { pj_dealloc(sgrids); return 0; } @@ -311,7 +311,7 @@ LP proj_hgrid_value(PJ *P, LP lp) { LP out = proj_coord_error().lp; ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); - if (ct == 0) { + if (ct == nullptr) { pj_ctx_set_errno( P->ctx, PJD_ERR_GRID_AREA); return out; } @@ -340,7 +340,7 @@ LP proj_hgrid_apply(PJ *P, LP lp, PJ_DIRECTION direction) { ct = find_ctable(P->ctx, lp, P->gridlist_count, P->gridlist); - if (ct == NULL || ct->cvs == NULL) { + if (ct == nullptr || ct->cvs == nullptr) { pj_ctx_set_errno( P->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return out; } diff --git a/src/pj_apply_vgridshift.cpp b/src/pj_apply_vgridshift.cpp index c1344951..1facfed6 100644 --- a/src/pj_apply_vgridshift.cpp +++ b/src/pj_apply_vgridshift.cpp @@ -70,11 +70,11 @@ static double read_vgrid_value( PJ *defn, LP input, int *gridlist_count_p, PJ_GR continue; /* If we have child nodes, check to see if any of them apply. */ - while( gi->child != NULL ) + while( gi->child != nullptr ) { PJ_GRIDINFO *child; - for( child = gi->child; child != NULL; child = child->next ) + for( child = gi->child; child != nullptr; child = child->next ) { struct CTABLE *ct1 = child->ct; @@ -87,7 +87,7 @@ static double read_vgrid_value( PJ *defn, LP input, int *gridlist_count_p, PJ_GR } /* we didn't find a more refined child node to use, so go with current grid */ - if( child == NULL ) + if( child == nullptr ) { break; } @@ -98,7 +98,7 @@ static double read_vgrid_value( PJ *defn, LP input, int *gridlist_count_p, PJ_GR } /* load the grid shift info if we don't have it. */ - if( ct->cvs == NULL && !pj_gridinfo_load( pj_get_ctx(defn), gi ) ) + if( ct->cvs == nullptr && !pj_gridinfo_load( pj_get_ctx(defn), gi ) ) { pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return PJD_ERR_FAILED_TO_LOAD_GRID; @@ -189,14 +189,14 @@ int pj_apply_vgridshift( PJ *defn, const char *listname, PJ_GRIDINFO **tables; struct CTABLE ct; - if( *gridlist_p == NULL ) + if( *gridlist_p == nullptr ) { *gridlist_p = pj_gridlist_from_nadgrids( pj_get_ctx(defn), pj_param(defn->ctx,defn->params,listname).s, gridlist_count_p ); - if( *gridlist_p == NULL || *gridlist_count_p == 0 ) + if( *gridlist_p == nullptr || *gridlist_count_p == 0 ) return defn->ctx->last_errno; } @@ -288,14 +288,14 @@ int proj_vgrid_init(PJ* P, const char *grids) { char *sgrids = (char *) pj_malloc( (strlen(grids)+1+1) *sizeof(char) ); sprintf(sgrids, "%s%s", "s", grids); - if (P->vgridlist_geoid == NULL) { + if (P->vgridlist_geoid == nullptr) { P->vgridlist_geoid = pj_gridlist_from_nadgrids( P->ctx, pj_param(P->ctx, P->params, sgrids).s, &(P->vgridlist_geoid_count) ); - if( P->vgridlist_geoid == NULL || P->vgridlist_geoid_count == 0 ) { + if( P->vgridlist_geoid == nullptr || P->vgridlist_geoid_count == 0 ) { pj_dealloc(sgrids); return 0; } diff --git a/src/pj_auth.cpp b/src/pj_auth.cpp index d6024671..cde60a29 100644 --- a/src/pj_auth.cpp +++ b/src/pj_auth.cpp @@ -17,7 +17,7 @@ pj_authset(double es) { double t, *APA; - if ((APA = (double *)pj_malloc(APA_SIZE * sizeof(double))) != NULL) { + if ((APA = (double *)pj_malloc(APA_SIZE * sizeof(double))) != nullptr) { APA[0] = es * P00; t = es * es; APA[0] += t * P01; diff --git a/src/pj_ctx.cpp b/src/pj_ctx.cpp index 1c99e921..ab51d6c1 100644 --- a/src/pj_ctx.cpp +++ b/src/pj_ctx.cpp @@ -42,9 +42,9 @@ static volatile int default_context_initialized = 0; projCtx pj_get_ctx( projPJ pj ) { - if (0==pj) + if (nullptr==pj) return pj_get_default_ctx (); - if (0==pj->ctx) + if (nullptr==pj->ctx) return pj_get_default_ctx (); return pj->ctx; } @@ -58,7 +58,7 @@ projCtx pj_get_ctx( projPJ pj ) void pj_set_ctx( projPJ pj, projCtx ctx ) { - if (pj==0) + if (pj==nullptr) return; pj->ctx = ctx; } @@ -82,13 +82,13 @@ projCtx pj_get_default_ctx() default_context.last_errno = 0; default_context.debug_level = PJ_LOG_NONE; default_context.logger = pj_stderr_logger; - default_context.app_data = NULL; + default_context.app_data = nullptr; default_context.fileapi = pj_get_default_fileapi(); - default_context.cpp_context = NULL; + default_context.cpp_context = nullptr; default_context.use_proj4_init_rules = -1; default_context.epsg_file_exists = -1; - if( getenv("PROJ_DEBUG") != NULL ) + if( getenv("PROJ_DEBUG") != nullptr ) { if( atoi(getenv("PROJ_DEBUG")) >= -PJ_LOG_DEBUG_MINOR ) default_context.debug_level = atoi(getenv("PROJ_DEBUG")); @@ -111,11 +111,11 @@ projCtx pj_ctx_alloc() { projCtx ctx = (projCtx_t *) malloc(sizeof(projCtx_t)); - if (0==ctx) - return 0; + if (nullptr==ctx) + return nullptr; memcpy( ctx, pj_get_default_ctx(), sizeof(projCtx_t) ); ctx->last_errno = 0; - ctx->cpp_context = NULL; + ctx->cpp_context = nullptr; ctx->use_proj4_init_rules = -1; return ctx; @@ -139,7 +139,7 @@ void pj_ctx_free( projCtx ctx ) int pj_ctx_get_errno( projCtx ctx ) { - if (0==ctx) + if (nullptr==ctx) return pj_get_default_ctx ()->last_errno; return ctx->last_errno; } @@ -167,7 +167,7 @@ void pj_ctx_set_errno( projCtx ctx, int new_errno ) void pj_ctx_set_debug( projCtx ctx, int new_debug ) { - if (0==ctx) + if (nullptr==ctx) return; ctx->debug_level = new_debug; } @@ -179,7 +179,7 @@ void pj_ctx_set_debug( projCtx ctx, int new_debug ) void pj_ctx_set_logger( projCtx ctx, void (*new_logger)(void*,int,const char*) ) { - if (0==ctx) + if (nullptr==ctx) return; ctx->logger = new_logger; } @@ -191,7 +191,7 @@ void pj_ctx_set_logger( projCtx ctx, void (*new_logger)(void*,int,const char*) ) void pj_ctx_set_app_data( projCtx ctx, void *new_app_data ) { - if (0==ctx) + if (nullptr==ctx) return; ctx->app_data = new_app_data; } @@ -203,8 +203,8 @@ void pj_ctx_set_app_data( projCtx ctx, void *new_app_data ) void *pj_ctx_get_app_data( projCtx ctx ) { - if (0==ctx) - return 0; + if (nullptr==ctx) + return nullptr; return ctx->app_data; } @@ -215,7 +215,7 @@ void *pj_ctx_get_app_data( projCtx ctx ) void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi ) { - if (0==ctx) + if (nullptr==ctx) return; ctx->fileapi = fileapi; } @@ -227,7 +227,7 @@ void pj_ctx_set_fileapi( projCtx ctx, projFileAPI *fileapi ) projFileAPI *pj_ctx_get_fileapi( projCtx ctx ) { - if (0==ctx) - return 0; + if (nullptr==ctx) + return nullptr; return ctx->fileapi; } diff --git a/src/pj_datum_set.cpp b/src/pj_datum_set.cpp index 466b56c5..c9dfdb80 100644 --- a/src/pj_datum_set.cpp +++ b/src/pj_datum_set.cpp @@ -54,7 +54,7 @@ int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) /* definition will last into the pj_ell_set() function called */ /* after this one. */ /* -------------------------------------------------------------------- */ - if( (name = pj_param(ctx, pl,"sdatum").s) != NULL ) + if( (name = pj_param(ctx, pl,"sdatum").s) != nullptr ) { paralist *curr; const char *s; @@ -96,7 +96,7 @@ int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) /* Check for nadgrids parameter. */ /* -------------------------------------------------------------------- */ nadgrids = pj_param(ctx, pl,"snadgrids").s; - if( nadgrids != NULL ) + if( nadgrids != nullptr ) { /* We don't actually save the value separately. It will continue to exist int he param list for use in pj_apply_gridshift.c */ @@ -107,7 +107,7 @@ int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) /* -------------------------------------------------------------------- */ /* Check for grid catalog parameter, and optional date. */ /* -------------------------------------------------------------------- */ - else if( (catalog = pj_param(ctx, pl,"scatalog").s) != NULL ) + else if( (catalog = pj_param(ctx, pl,"scatalog").s) != nullptr ) { const char *date; @@ -119,14 +119,14 @@ int pj_datum_set(projCtx ctx, paralist *pl, PJ *projdef) } date = pj_param(ctx, pl, "sdate").s; - if( date != NULL) + if( date != nullptr) projdef->datum_date = pj_gc_parsedate( ctx, date); } /* -------------------------------------------------------------------- */ /* Check for towgs84 parameter. */ /* -------------------------------------------------------------------- */ - else if( (towgs84 = pj_param(ctx, pl,"stowgs84").s) != NULL ) + else if( (towgs84 = pj_param(ctx, pl,"stowgs84").s) != nullptr ) { int parm_count = 0; const char *s; diff --git a/src/pj_datums.cpp b/src/pj_datums.cpp index 2951b7bd..acbe12f7 100644 --- a/src/pj_datums.cpp +++ b/src/pj_datums.cpp @@ -65,7 +65,7 @@ C_NAMESPACE_VAR const struct PJ_DATUMS pj_datums[] = { "intl", "New Zealand Geodetic Datum 1949"}, {"OSGB36", "towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", "airy", "Airy 1830"}, -{NULL, NULL, NULL, NULL} +{nullptr, nullptr, nullptr, nullptr} }; struct PJ_DATUMS *pj_get_datums_ref() @@ -90,7 +90,7 @@ static const struct PJ_PRIME_MERIDIANS pj_prime_meridians[] = { {"athens", "23d42'58.815\"E"}, {"oslo", "10d43'22.5\"E"}, {"copenhagen","12d34'40.35\"E"}, - {NULL, NULL} + {nullptr, nullptr} }; const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void) diff --git a/src/pj_deriv.cpp b/src/pj_deriv.cpp index 2c91e0cc..0e285265 100644 --- a/src/pj_deriv.cpp +++ b/src/pj_deriv.cpp @@ -9,7 +9,7 @@ int pj_deriv(LP lp, double h, const PJ *P, struct DERIVS *der) { XY t; /* get rid of constness until we can do it for real */ PJ *Q = (PJ *) P; - if (0==Q->fwd) + if (nullptr==Q->fwd) return 1; lp.lam += h; diff --git a/src/pj_ell_set.cpp b/src/pj_ell_set.cpp index e2d2750c..9d7fae0a 100644 --- a/src/pj_ell_set.cpp +++ b/src/pj_ell_set.cpp @@ -77,7 +77,7 @@ int pj_ellipsoid (PJ *P) { int err = proj_errno_reset (P); char *empty = {""}; - P->def_size = P->def_shape = P->def_spherification = P->def_ellps = 0; + P->def_size = P->def_shape = P->def_spherification = P->def_ellps = nullptr; /* Specifying R overrules everything */ if (pj_get_param (P->params, "R")) { @@ -129,13 +129,13 @@ static int ellps_ellps (PJ *P) { /***************************************************************************************/ PJ B; const PJ_ELLPS *ellps; - paralist *par = 0; + paralist *par = nullptr; char *name; int err; /* Sail home if ellps=xxx is not specified */ par = pj_get_param (P->params, "ellps"); - if (0==par) + if (nullptr==par) return 0; /* Otherwise produce a fake PJ to make ellps_size/ellps_shape do the hard work for us */ @@ -148,7 +148,7 @@ static int ellps_ellps (PJ *P) { return proj_errno_set (P, PJD_ERR_INVALID_ARG); name = par->param + 6; ellps = pj_find_ellps (name); - if (0==ellps) + if (nullptr==ellps) return proj_errno_set (P, PJD_ERR_UNKNOWN_ELLP_PARAM); /* Now, get things ready for ellps_size/ellps_shape, make them do their thing, and clean up */ @@ -178,7 +178,7 @@ static int ellps_ellps (PJ *P) { /***************************************************************************************/ static int ellps_size (PJ *P) { /***************************************************************************************/ - paralist *par = 0; + paralist *par = nullptr; int a_was_set = 0; /* A size parameter *must* be given, but may have been given as ellps prior */ @@ -187,9 +187,9 @@ static int ellps_size (PJ *P) { /* Check which size key is specified */ par = pj_get_param (P->params, "R"); - if (0==par) + if (nullptr==par) par = pj_get_param (P->params, "a"); - if (0==par) + if (nullptr==par) return a_was_set? 0: proj_errno_set (P, PJD_ERR_MAJOR_AXIS_NOT_GIVEN); P->def_size = par->param; @@ -212,11 +212,11 @@ static int ellps_size (PJ *P) { static int ellps_shape (PJ *P) { /***************************************************************************************/ char *keys[] = {"rf", "f", "es", "e", "b"}; - paralist *par = 0; - char *def = 0; + paralist *par = nullptr; + char *def = nullptr; size_t i, len; - par = 0; + par = nullptr; len = sizeof (keys) / sizeof (char *); /* Check which shape key is specified */ @@ -228,9 +228,9 @@ static int ellps_shape (PJ *P) { /* Not giving a shape parameter means selecting a sphere, unless shape */ /* has been selected previously via ellps=xxx */ - if (0==par && P->es != 0) + if (nullptr==par && P->es != 0) return 0; - if (0==par && P->es==0) { + if (nullptr==par && P->es==0) { P->es = P->f = 0; P->b = P->a; return 0; @@ -319,14 +319,14 @@ static int ellps_spherification (PJ *P) { /***************************************************************************************/ char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g"}; size_t len, i; - paralist *par = 0; - char *def = 0; + paralist *par = nullptr; + char *def = nullptr; double t; char *v, *endp; len = sizeof (keys) / sizeof (char *); - P->def_spherification = 0; + P->def_spherification = nullptr; /* Check which spherification key is specified */ for (i = 0; i < len; i++) { @@ -410,8 +410,8 @@ static paralist *pj_get_param (paralist *list, char *key) { static char *pj_param_value (paralist *list) { char *key, *value; - if (0==list) - return 0; + if (nullptr==list) + return nullptr; key = list->param; value = strchr (key, '='); @@ -426,15 +426,15 @@ static const PJ_ELLPS *pj_find_ellps (char *name) { const char *s; const PJ_ELLPS *ellps; - if (0==name) - return 0; + if (nullptr==name) + return nullptr; ellps = proj_list_ellps(); /* Search through internal ellipsoid list for name */ for (i = 0; (s = ellps[i].id) && strcmp(name, s) ; ++i); - if (0==s) - return 0; + if (nullptr==s) + return nullptr; return ellps + i; } diff --git a/src/pj_ellps.cpp b/src/pj_ellps.cpp index 8b3b8f0a..f548d30d 100644 --- a/src/pj_ellps.cpp +++ b/src/pj_ellps.cpp @@ -53,7 +53,7 @@ pj_ellps[] = { {"WGS72", "a=6378135.0", "rf=298.26", "WGS 72"}, {"WGS84", "a=6378137.0", "rf=298.257223563", "WGS 84"}, {"sphere", "a=6370997.0", "b=6370997.0", "Normal Sphere (r=6370997)"}, -{NULL, NULL, NULL, NULL} +{nullptr, nullptr, nullptr, nullptr} }; const PJ_ELLPS *proj_list_ellps(void) diff --git a/src/pj_factors.cpp b/src/pj_factors.cpp index e4b871a1..768bf585 100644 --- a/src/pj_factors.cpp +++ b/src/pj_factors.cpp @@ -21,10 +21,10 @@ int pj_factors(LP lp, const PJ *P, double h, struct FACTORS *fac) { /* Failing the 3 initial checks will most likely be due to */ /* earlier errors, so we leave errno alone */ - if (0==fac) + if (nullptr==fac) return 1; - if (0==P) + if (nullptr==P) return 1; if (HUGE_VAL==lp.lam) diff --git a/src/pj_fileapi.cpp b/src/pj_fileapi.cpp index eba96afd..3df73236 100644 --- a/src/pj_fileapi.cpp +++ b/src/pj_fileapi.cpp @@ -75,9 +75,9 @@ static PAFile stdio_fopen(projCtx ctx, const char *filename, FILE *fp; fp = fopen(filename, access); - if (fp == NULL) + if (fp == nullptr) { - return NULL; + return nullptr; } pafile = (stdio_pafile *) malloc(sizeof(stdio_pafile)); @@ -85,7 +85,7 @@ static PAFile stdio_fopen(projCtx ctx, const char *filename, { pj_ctx_set_errno(ctx, ENOMEM); fclose(fp); - return NULL; + return nullptr; } pafile->fp = fp; @@ -193,7 +193,7 @@ char *pj_ctx_fgets(projCtx ctx, char *line, int size, PAFile file) line[size-1] = '\0'; bytes_read = pj_ctx_fread(ctx, line, 1, size-1, file); if(bytes_read == 0) - return NULL; + return nullptr; if(bytes_read < (size_t)size) { line[bytes_read] = '\0'; diff --git a/src/pj_fwd.cpp b/src/pj_fwd.cpp index 38443f07..e8f73999 100644 --- a/src/pj_fwd.cpp +++ b/src/pj_fwd.cpp @@ -74,7 +74,7 @@ static PJ_COORD fwd_prepare (PJ *P, PJ_COORD coo) { if (P->hgridshift) coo = proj_trans (P->hgridshift, PJ_INV, coo); - else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { + else if (P->helmert || (P->cart_wgs84 != nullptr && P->cart != nullptr)) { coo = proj_trans (P->cart_wgs84, PJ_FWD, coo); /* Go cartesian in WGS84 frame */ if( P->helmert ) coo = proj_trans (P->helmert, PJ_INV, coo); /* Step into local frame */ diff --git a/src/pj_gauss.cpp b/src/pj_gauss.cpp index 45b93f59..2db713ad 100644 --- a/src/pj_gauss.cpp +++ b/src/pj_gauss.cpp @@ -50,8 +50,8 @@ void *pj_gauss_ini(double e, double phi0, double *chi, double *rc) { double sphi, cphi, es; struct GAUSS *en; - if ((en = (struct GAUSS *)malloc(sizeof(struct GAUSS))) == NULL) - return (NULL); + if ((en = (struct GAUSS *)malloc(sizeof(struct GAUSS))) == nullptr) + return (nullptr); es = e * e; en->e = e; sphi = sin(phi0); @@ -60,7 +60,7 @@ void *pj_gauss_ini(double e, double phi0, double *chi, double *rc) { en->C = sqrt(1. + es * cphi * cphi / (1. - es)); if (en->C == 0.0) { free(en); - return NULL; + return nullptr; } *chi = asin(sphi / en->C); en->ratexp = 0.5 * en->C * e; diff --git a/src/pj_gc_reader.cpp b/src/pj_gc_reader.cpp index 493fc075..118aadf6 100644 --- a/src/pj_gc_reader.cpp +++ b/src/pj_gc_reader.cpp @@ -50,8 +50,8 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) char line[302]; fid = pj_open_lib( ctx, catalog_name, "r" ); - if (fid == NULL) - return NULL; + if (fid == nullptr) + return nullptr; /* discard title line */ pj_ctx_fgets(ctx, line, sizeof(line)-1, fid); @@ -61,7 +61,7 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) { pj_ctx_set_errno(ctx, ENOMEM); pj_ctx_fclose(ctx, fid); - return NULL; + return nullptr; } catalog->catalog_name = pj_strdup(catalog_name); @@ -69,7 +69,7 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) pj_ctx_set_errno(ctx, ENOMEM); free(catalog); pj_ctx_fclose(ctx, fid); - return NULL; + return nullptr; } entry_max = 10; @@ -80,7 +80,7 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) free(catalog->catalog_name); free(catalog); pj_ctx_fclose(ctx, fid); - return NULL; + return nullptr; } while( gc_readentry( ctx, fid, @@ -95,7 +95,7 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) new_entries = (PJ_GridCatalogEntry *) realloc(catalog->entries, entry_max * sizeof(PJ_GridCatalogEntry)); - if (new_entries == NULL ) + if (new_entries == nullptr ) { int i; for( i = 0; i < catalog->entry_count; i++ ) @@ -104,7 +104,7 @@ PJ_GridCatalog *pj_gc_readcatalog( projCtx ctx, const char *catalog_name ) free( catalog->catalog_name ); free( catalog ); pj_ctx_fclose(ctx, fid); - return NULL; + return nullptr; } catalog->entries = new_entries; } @@ -127,7 +127,7 @@ static int gc_read_csv_line( projCtx ctx, PAFile fid, { char line[302]; - while( pj_ctx_fgets(ctx, line, sizeof(line)-1, fid) != NULL ) + while( pj_ctx_fgets(ctx, line, sizeof(line)-1, fid) != nullptr ) { char *next = line; int token_count = 0; @@ -226,11 +226,11 @@ static int gc_readentry(projCtx ctx, PAFile fid, PJ_GridCatalogEntry *entry) else { entry->definition = tokens[0]; - tokens[0] = NULL; /* We take ownership of tokens[0] */ - entry->region.ll_long = dmstor_ctx( ctx, tokens[1], NULL ); - entry->region.ll_lat = dmstor_ctx( ctx, tokens[2], NULL ); - entry->region.ur_long = dmstor_ctx( ctx, tokens[3], NULL ); - entry->region.ur_lat = dmstor_ctx( ctx, tokens[4], NULL ); + tokens[0] = nullptr; /* We take ownership of tokens[0] */ + entry->region.ll_long = dmstor_ctx( ctx, tokens[1], nullptr ); + entry->region.ll_lat = dmstor_ctx( ctx, tokens[2], nullptr ); + entry->region.ur_long = dmstor_ctx( ctx, tokens[3], nullptr ); + entry->region.ur_lat = dmstor_ctx( ctx, tokens[4], nullptr ); if( token_count > 5 ) entry->priority = atoi( tokens[5] ); /* defaults to zero */ if( token_count > 6 ) diff --git a/src/pj_gridcatalog.cpp b/src/pj_gridcatalog.cpp index ea9c4aa1..fef2df56 100644 --- a/src/pj_gridcatalog.cpp +++ b/src/pj_gridcatalog.cpp @@ -34,7 +34,7 @@ #include "projects.h" -static PJ_GridCatalog *grid_catalog_list = NULL; +static PJ_GridCatalog *grid_catalog_list = nullptr; /************************************************************************/ /* pj_gc_unloadall() */ @@ -47,7 +47,7 @@ void pj_gc_unloadall( projCtx ctx ) { (void) ctx; - while( grid_catalog_list != NULL ) + while( grid_catalog_list != nullptr ) { int i; PJ_GridCatalog *catalog = grid_catalog_list; @@ -75,7 +75,7 @@ PJ_GridCatalog *pj_gc_findcatalog( projCtx ctx, const char *name ) pj_acquire_lock(); - for( catalog=grid_catalog_list; catalog != NULL; catalog = catalog->next ) + for( catalog=grid_catalog_list; catalog != nullptr; catalog = catalog->next ) { if( strcmp(catalog->catalog_name, name) == 0 ) { @@ -87,8 +87,8 @@ PJ_GridCatalog *pj_gc_findcatalog( projCtx ctx, const char *name ) pj_release_lock(); catalog = pj_gc_readcatalog( ctx, name ); - if( catalog == NULL ) - return NULL; + if( catalog == nullptr ) + return nullptr; pj_acquire_lock(); catalog->next = grid_catalog_list; @@ -110,10 +110,10 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, int i; (void) z; - if( defn->catalog == NULL ) + if( defn->catalog == nullptr ) { defn->catalog = pj_gc_findcatalog( defn->ctx, defn->catalog_name ); - if( defn->catalog == NULL ) + if( defn->catalog == nullptr ) return defn->ctx->last_errno; } @@ -130,7 +130,7 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, input.lam = x[io]; /* make sure we have appropriate "after" shift file available */ - if( defn->last_after_grid == NULL + if( defn->last_after_grid == nullptr || input.lam < defn->last_after_region.ll_long || input.lam > defn->last_after_region.ur_long || input.phi < defn->last_after_region.ll_lat @@ -140,17 +140,17 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, 1, input, defn->datum_date, &(defn->last_after_region), &(defn->last_after_date)); - if( defn->last_after_grid == NULL ) + if( defn->last_after_grid == nullptr ) { pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return PJD_ERR_FAILED_TO_LOAD_GRID; } } gi = defn->last_after_grid; - assert( gi->child == NULL ); + assert( gi->child == nullptr ); /* load the grid shift info if we don't have it. */ - if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) + if( gi->ct->cvs == nullptr && !pj_gridinfo_load( defn->ctx, gi ) ) { pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return PJD_ERR_FAILED_TO_LOAD_GRID; @@ -178,7 +178,7 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, } /* make sure we have appropriate "before" shift file available */ - if( defn->last_before_grid == NULL + if( defn->last_before_grid == nullptr || input.lam < defn->last_before_region.ll_long || input.lam > defn->last_before_region.ur_long || input.phi < defn->last_before_region.ll_lat @@ -188,7 +188,7 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, 0, input, defn->datum_date, &(defn->last_before_region), &(defn->last_before_date)); - if( defn->last_before_grid == NULL ) + if( defn->last_before_grid == nullptr ) { pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return PJD_ERR_FAILED_TO_LOAD_GRID; @@ -196,10 +196,10 @@ int pj_gc_apply_gridshift( PJ *defn, int inverse, } gi = defn->last_before_grid; - assert( gi->child == NULL ); + assert( gi->child == nullptr ); /* load the grid shift info if we don't have it. */ - if( gi->ct->cvs == NULL && !pj_gridinfo_load( defn->ctx, gi ) ) + if( gi->ct->cvs == nullptr && !pj_gridinfo_load( defn->ctx, gi ) ) { pj_ctx_set_errno( defn->ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); return PJD_ERR_FAILED_TO_LOAD_GRID; @@ -241,7 +241,7 @@ PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after, double *grid_date ) { int iEntry; - PJ_GridCatalogEntry *entry = NULL; + PJ_GridCatalogEntry *entry = nullptr; for( iEntry = 0; iEntry < catalog->entry_count; iEntry++ ) { @@ -263,13 +263,13 @@ PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after, break; } - if( entry == NULL ) + if( entry == nullptr ) { if( grid_date ) *grid_date = 0.0; - if( optional_region != NULL ) + if( optional_region != nullptr ) memset( optional_region, 0, sizeof(PJ_Region)); - return NULL; + return nullptr; } if( grid_date ) @@ -280,9 +280,9 @@ PJ_GRIDINFO *pj_gc_findgrid( projCtx ctx, PJ_GridCatalog *catalog, int after, } - if( entry->gridinfo == NULL ) + if( entry->gridinfo == nullptr ) { - PJ_GRIDINFO **gridlist = NULL; + PJ_GRIDINFO **gridlist = nullptr; int grid_count = 0; gridlist = pj_gridlist_from_nadgrids( ctx, entry->definition, &grid_count); diff --git a/src/pj_gridinfo.cpp b/src/pj_gridinfo.cpp index de0e8d31..046abfcc 100644 --- a/src/pj_gridinfo.cpp +++ b/src/pj_gridinfo.cpp @@ -89,25 +89,25 @@ static double to_double( unsigned char* data ) void pj_gridinfo_free( projCtx ctx, PJ_GRIDINFO *gi ) { - if( gi == NULL ) + if( gi == nullptr ) return; - if( gi->child != NULL ) + if( gi->child != nullptr ) { PJ_GRIDINFO *child, *next; - for( child = gi->child; child != NULL; child=next) + for( child = gi->child; child != nullptr; child=next) { next=child->next; pj_gridinfo_free( ctx, child ); } } - if( gi->ct != NULL ) + if( gi->ct != nullptr ) nad_free( gi->ct ); free( gi->gridname ); - if( gi->filename != NULL ) + if( gi->filename != nullptr ) free( gi->filename ); pj_dalloc( gi ); @@ -126,11 +126,11 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) { struct CTABLE ct_tmp; - if( gi == NULL || gi->ct == NULL ) + if( gi == nullptr || gi->ct == nullptr ) return 0; pj_acquire_lock(); - if( gi->ct->cvs != NULL ) + if( gi->ct->cvs != nullptr ) { pj_release_lock(); return 1; @@ -148,7 +148,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) fid = pj_open_lib( ctx, gi->filename, "rb" ); - if( fid == NULL ) + if( fid == nullptr ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); @@ -175,7 +175,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) fid = pj_open_lib( ctx, gi->filename, "rb" ); - if( fid == NULL ) + if( fid == nullptr ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); @@ -207,7 +207,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) fid = pj_open_lib( ctx, gi->filename, "rb" ); - if( fid == NULL ) + if( fid == nullptr ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); @@ -218,7 +218,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) row_buf = (double *) pj_malloc(gi->ct->lim.lam * sizeof(double) * 2); ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); - if( row_buf == NULL || ct_tmp.cvs == NULL ) + if( row_buf == nullptr || ct_tmp.cvs == nullptr ) { pj_dalloc( row_buf ); pj_dalloc( ct_tmp.cvs ); @@ -288,7 +288,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) fid = pj_open_lib( ctx, gi->filename, "rb" ); - if( fid == NULL ) + if( fid == nullptr ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); @@ -299,7 +299,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) row_buf = (float *) pj_malloc(gi->ct->lim.lam * sizeof(float) * 4); ct_tmp.cvs = (FLP *) pj_malloc(gi->ct->lim.lam*gi->ct->lim.phi*sizeof(FLP)); - if( row_buf == NULL || ct_tmp.cvs == NULL ) + if( row_buf == nullptr || ct_tmp.cvs == nullptr ) { pj_dalloc( row_buf ); pj_dalloc( ct_tmp.cvs ); @@ -363,7 +363,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) fid = pj_open_lib( ctx, gi->filename, "rb" ); - if( fid == NULL ) + if( fid == nullptr ) { pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); @@ -373,7 +373,7 @@ int pj_gridinfo_load( projCtx ctx, PJ_GRIDINFO *gi ) pj_ctx_fseek( ctx, fid, gi->grid_offset, SEEK_SET ); ct_tmp.cvs = (FLP *) pj_malloc(words*sizeof(float)); - if( ct_tmp.cvs == NULL ) + if( ct_tmp.cvs == nullptr ) { pj_ctx_set_errno( ctx, ENOMEM ); pj_release_lock(); @@ -563,7 +563,7 @@ static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) return 0; } - ct->cvs = NULL; + ct->cvs = nullptr; /* -------------------------------------------------------------------- */ /* Create a new gridinfo for this if we aren't processing the */ @@ -590,7 +590,7 @@ static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) pj_ctx_set_errno(ctx, ENOMEM); return 0; } - gi->next = NULL; + gi->next = nullptr; } gi->must_swap = must_swap; @@ -607,7 +607,7 @@ static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) { PJ_GRIDINFO *lnk; - for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} + for( lnk = gilist; lnk->next != nullptr; lnk = lnk->next ) {} lnk->next = gi; } } @@ -618,25 +618,25 @@ static int pj_gridinfo_init_ntv2( projCtx ctx, PAFile fid, PJ_GRIDINFO *gilist ) PJ_GRIDINFO *gp = gridinfo_parent(gilist, (const char*)header+24,8); - if( gp == NULL ) + if( gp == nullptr ) { pj_log( ctx, PJ_LOG_ERROR, "pj_gridinfo_init_ntv2(): " "failed to find parent %8.8s for %s.", (const char *) header+24, gi->ct->id ); - for( lnk = gilist; lnk->next != NULL; lnk = lnk->next ) {} + for( lnk = gilist; lnk->next != nullptr; lnk = lnk->next ) {} lnk->next = gi; } else { - if( gp->child == NULL ) + if( gp->child == nullptr ) { gp->child = gi; } else { - for( lnk = gp->child; lnk->next != NULL; lnk = lnk->next ) {} + for( lnk = gp->child; lnk->next != nullptr; lnk = lnk->next ) {} lnk->next = gi; } } @@ -728,7 +728,7 @@ static int pj_gridinfo_init_ntv1( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) ct->ll.phi *= DEG_TO_RAD; ct->del.lam *= DEG_TO_RAD; ct->del.phi *= DEG_TO_RAD; - ct->cvs = NULL; + ct->cvs = nullptr; gi->ct = ct; gi->grid_offset = (long) sizeof(header); @@ -830,7 +830,7 @@ static int pj_gridinfo_init_gtx( projCtx ctx, PAFile fid, PJ_GRIDINFO *gi ) ct->ll.phi *= DEG_TO_RAD; ct->del.lam *= DEG_TO_RAD; ct->del.phi *= DEG_TO_RAD; - ct->cvs = NULL; + ct->cvs = nullptr; gi->ct = ct; gi->grid_offset = 40; @@ -867,20 +867,20 @@ PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) gilist = (PJ_GRIDINFO *) pj_calloc(1, sizeof(PJ_GRIDINFO)); if (!gilist) { pj_ctx_set_errno(ctx, ENOMEM); - return NULL; + return nullptr; } gilist->gridname = pj_strdup( gridname ); if (!gilist->gridname) { pj_dalloc(gilist); pj_ctx_set_errno(ctx, ENOMEM); - return NULL; + return nullptr; } - gilist->filename = NULL; + gilist->filename = nullptr; gilist->format = "missing"; gilist->grid_offset = 0; - gilist->ct = NULL; - gilist->next = NULL; + gilist->ct = nullptr; + gilist->next = nullptr; /* -------------------------------------------------------------------- */ /* Open the file using the usual search rules. */ @@ -896,7 +896,7 @@ PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) pj_dalloc(gilist->gridname); pj_dalloc(gilist); pj_ctx_set_errno(ctx, ENOMEM); - return NULL; + return nullptr; } /* -------------------------------------------------------------------- */ @@ -946,7 +946,7 @@ PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) gilist->format = "ctable2"; gilist->ct = ct; - if (ct == NULL) + if (ct == nullptr) { pj_log( ctx, PJ_LOG_DEBUG_MAJOR, "CTABLE V2 ct is NULL."); @@ -966,7 +966,7 @@ PJ_GRIDINFO *pj_gridinfo_init( projCtx ctx, const char *gridname ) else { struct CTABLE *ct = nad_ctable_init( ctx, fp ); - if (ct == NULL) + if (ct == nullptr) { pj_log( ctx, PJ_LOG_DEBUG_MAJOR, "CTABLE ct is NULL."); diff --git a/src/pj_gridlist.cpp b/src/pj_gridlist.cpp index 332de8dd..169abcb9 100644 --- a/src/pj_gridlist.cpp +++ b/src/pj_gridlist.cpp @@ -34,7 +34,7 @@ #include "projects.h" -static PJ_GRIDINFO *grid_list = NULL; +static PJ_GRIDINFO *grid_list = nullptr; #define PJ_MAX_PATH_LENGTH 1024 /************************************************************************/ @@ -46,11 +46,11 @@ static PJ_GRIDINFO *grid_list = NULL; void pj_deallocate_grids() { - while( grid_list != NULL ) + while( grid_list != nullptr ) { PJ_GRIDINFO *item = grid_list; grid_list = grid_list->next; - item->next = NULL; + item->next = nullptr; pj_gridinfo_free( pj_get_default_ctx(), item ); } @@ -71,21 +71,21 @@ static int pj_gridlist_merge_gridfile( projCtx ctx, { int got_match=0; - PJ_GRIDINFO *this_grid, *tail = NULL; + PJ_GRIDINFO *this_grid, *tail = nullptr; /* -------------------------------------------------------------------- */ /* Try to find in the existing list of loaded grids. Add all */ /* matching grids as with NTv2 we can get many grids from one */ /* file (one shared gridname). */ /* -------------------------------------------------------------------- */ - for( this_grid = grid_list; this_grid != NULL; this_grid = this_grid->next) + for( this_grid = grid_list; this_grid != nullptr; this_grid = this_grid->next) { if( strcmp(this_grid->gridname,gridname) == 0 ) { got_match = 1; /* don't add to the list if it is invalid. */ - if( this_grid->ct == NULL ) + if( this_grid->ct == nullptr ) return 0; /* do we need to grow the list? */ @@ -99,7 +99,7 @@ static int pj_gridlist_merge_gridfile( projCtx ctx, pj_ctx_set_errno( ctx, ENOMEM ); return 0; } - if( *p_gridlist != NULL ) + if( *p_gridlist != nullptr ) { memcpy( new_list, *p_gridlist, sizeof(void *) * (*p_gridmax) ); @@ -112,7 +112,7 @@ static int pj_gridlist_merge_gridfile( projCtx ctx, /* add to the list */ (*p_gridlist)[(*p_gridcount)++] = this_grid; - (*p_gridlist)[*p_gridcount] = NULL; + (*p_gridlist)[*p_gridcount] = nullptr; } tail = this_grid; @@ -126,12 +126,12 @@ static int pj_gridlist_merge_gridfile( projCtx ctx, /* -------------------------------------------------------------------- */ this_grid = pj_gridinfo_init( ctx, gridname ); - if( this_grid == NULL ) + if( this_grid == nullptr ) { return 0; } - if( tail != NULL ) + if( tail != nullptr ) tail->next = this_grid; else grid_list = this_grid; @@ -158,7 +158,7 @@ PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids, { const char *s; - PJ_GRIDINFO **gridlist = NULL; + PJ_GRIDINFO **gridlist = nullptr; int grid_max = 0; pj_errno = 0; @@ -190,7 +190,7 @@ PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids, pj_dalloc( gridlist ); pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); - return NULL; + return nullptr; } strncpy( name, s, end_char ); @@ -207,7 +207,7 @@ PJ_GRIDINFO **pj_gridlist_from_nadgrids( projCtx ctx, const char *nadgrids, pj_dalloc( gridlist ); pj_ctx_set_errno( ctx, PJD_ERR_FAILED_TO_LOAD_GRID ); pj_release_lock(); - return NULL; + return nullptr; } else pj_errno = 0; diff --git a/src/pj_init.cpp b/src/pj_init.cpp index b09d70ed..5710031c 100644 --- a/src/pj_init.cpp +++ b/src/pj_init.cpp @@ -49,7 +49,7 @@ static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { Convert a string (presumably originating from get_init_string) to a paralist. ***************************************************************************************/ char *c = definition; - paralist *first = 0, *next = 0; + paralist *first = nullptr, *next = nullptr; while (*c) { /* Find start of next substring */ @@ -57,11 +57,11 @@ static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { c++; /* Keep a handle to the start of the list, so we have something to return */ - if (0==first) + if (nullptr==first) first = next = pj_mkparam_ws (c); else next = next->next = pj_mkparam_ws (c); - if (0==next) { + if (nullptr==next) { pj_dealloc_params (ctx, first, ENOMEM); return nullptr; } @@ -71,11 +71,11 @@ static paralist *string_to_paralist (PJ_CONTEXT *ctx, char *definition) { c++; } - if( next == 0 ) - return 0; + if( next == nullptr ) + return nullptr; /* Terminate list and return */ - next->next = 0; + next->next = nullptr; return first; } @@ -91,42 +91,42 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { size_t current_buffer_size = 5 * (MAX_LINE_LENGTH + 1); char *fname, *section; const char *key; - char *buffer = 0; - char *line = 0; + char *buffer = nullptr; + char *line = nullptr; PAFile fid; size_t n; line = static_cast(pj_malloc (MAX_LINE_LENGTH + 1)); - if (0==line) - return 0; + if (nullptr==line) + return nullptr; fname = static_cast(pj_malloc (MAX_PATH_FILENAME+ID_TAG_MAX+3)); - if (0==fname) { + if (nullptr==fname) { pj_dealloc (line); - return 0; + return nullptr; } /* Support "init=file:section", "+init=file:section", and "file:section" format */ key = strstr (name, "init="); - if (0==key) + if (nullptr==key) key = name; else key += 5; if (MAX_PATH_FILENAME + ID_TAG_MAX + 2 < strlen (key)) { pj_dealloc (fname); pj_dealloc (line); - return 0; + return nullptr; } memmove (fname, key, strlen (key) + 1); /* Locate the name of the section we search for */ section = strrchr(fname, ':'); - if (0==section) { + if (nullptr==section) { proj_context_errno_set (ctx, PJD_ERR_NO_COLON_IN_INIT_STRING); pj_dealloc (fname); pj_dealloc (line); - return 0; + return nullptr; } *section = 0; section++; @@ -136,24 +136,24 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { section, fname); fid = pj_open_lib (ctx, fname, "rt"); - if (0==fid) { + if (nullptr==fid) { pj_dealloc (fname); pj_dealloc (line); proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); - return 0; + return nullptr; } /* Search for section in init file */ for (;;) { /* End of file? */ - if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) { + if (nullptr==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) { pj_dealloc (buffer); pj_dealloc (fname); pj_dealloc (line); pj_ctx_fclose (ctx, fid); proj_context_errno_set (ctx, PJD_ERR_NO_OPTION_IN_INIT_FILE); - return 0; + return nullptr; } /* At start of right section? */ @@ -170,11 +170,11 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { /* We're at the first line of the right section - copy line to buffer */ buffer = static_cast(pj_malloc (current_buffer_size)); - if (0==buffer) { + if (nullptr==buffer) { pj_dealloc (fname); pj_dealloc (line); pj_ctx_fclose (ctx, fid); - return 0; + return nullptr; } /* Skip the "
" indicator, and copy the rest of the line over */ @@ -193,7 +193,7 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { } /* End of file? - done! */ - if (0==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) + if (nullptr==pj_ctx_fgets (ctx, line, MAX_LINE_LENGTH, fid)) break; /* Otherwise, handle the line. It MAY be the start of the next section, */ @@ -203,9 +203,9 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { next_length = strlen (line) + buffer_length + 2; if (next_length > current_buffer_size) { char *b = static_cast(pj_malloc (2 * current_buffer_size)); - if (0==b) { + if (nullptr==b) { pj_dealloc (buffer); - buffer = 0; + buffer = nullptr; break; } strcpy (b, buffer); @@ -220,8 +220,8 @@ static char *get_init_string (PJ_CONTEXT *ctx, const char *name) { pj_ctx_fclose (ctx, fid); pj_dealloc (fname); pj_dealloc (line); - if (0==buffer) - return 0; + if (nullptr==buffer) + return nullptr; pj_shrink (buffer); pj_log (ctx, PJ_LOG_TRACE, "key=%s, value: [%s]", key, buffer); return buffer; @@ -235,12 +235,12 @@ static paralist *get_init(PJ_CONTEXT *ctx, const char *key, int allow_init_epsg) Expand key from buffer or (if not in buffer) from init file *************************************************************************/ const char *xkey; - char *definition = 0; - paralist *init_items = 0; + char *definition = nullptr; + paralist *init_items = nullptr; /* support "init=file:section", "+init=file:section", and "file:section" format */ xkey = strstr (key, "init="); - if (0==xkey) + if (nullptr==xkey) xkey = key; else xkey += 5; @@ -270,7 +270,7 @@ Expand key from buffer or (if not in buffer) from init file } if( !exists ) { - const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; + const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", nullptr }; char szInitStr[7 + 64]; PJ_OBJ* src; const char* proj_string; @@ -279,23 +279,23 @@ Expand key from buffer or (if not in buffer) from init file if( !allow_init_epsg ) { pj_log (ctx, PJ_LOG_TRACE, "%s expansion disallowed", xkey); - return 0; + return nullptr; } if( strlen(xkey) > 64 ) { - return 0; + return nullptr; } strcpy(szInitStr, "+init="); strcat(szInitStr, xkey); src = proj_obj_create_from_user_input(ctx, szInitStr, optionsProj4Mode); if( !src ) { - return 0; + return nullptr; } - proj_string = proj_obj_as_proj_string(ctx, src, PJ_PROJ_4, NULL); + proj_string = proj_obj_as_proj_string(ctx, src, PJ_PROJ_4, nullptr); if( !proj_string ) { proj_obj_destroy(src); - return 0; + return nullptr; } definition = (char*)calloc(1, strlen(proj_string)+1); if( definition ) { @@ -313,8 +313,8 @@ Expand key from buffer or (if not in buffer) from init file definition = get_init_string (ctx, xkey); } - if (0==definition) - return 0; + if (nullptr==definition) + return nullptr; init_items = string_to_paralist (ctx, definition); if (init_items) pj_log (ctx, PJ_LOG_TRACE, "get_init: got [%s], paralist[0,1]: [%s,%s]", @@ -322,8 +322,8 @@ Expand key from buffer or (if not in buffer) from init file init_items->param, init_items->next ? init_items->next->param : "(empty)"); pj_dealloc (definition); - if (0==init_items) - return 0; + if (nullptr==init_items) + return nullptr; /* We found it in file - now insert into the cache, before returning */ pj_insert_initcache (xkey, init_items); @@ -333,23 +333,23 @@ Expand key from buffer or (if not in buffer) from init file static paralist *append_defaults_to_paralist (PJ_CONTEXT *ctx, paralist *start, const char *key, int allow_init_epsg) { - paralist *defaults, *last = 0; + paralist *defaults, *last = nullptr; char keystring[ID_TAG_MAX + 20]; paralist *next, *proj; int err; - if (0==start) - return 0; + if (nullptr==start) + return nullptr; if (strlen(key) > ID_TAG_MAX) - return 0; + return nullptr; /* Set defaults, unless inhibited (either explicitly through a "no_defs" token */ /* or implicitly, because we are initializing a pipeline) */ if (pj_param_exists (start, "no_defs")) return start; proj = pj_param_exists (start, "proj"); - if (0==proj) + if (nullptr==proj) return start; if (strlen (proj->param) < 6) return start; @@ -394,7 +394,7 @@ static paralist *append_defaults_to_paralist (PJ_CONTEXT *ctx, paralist *start, /* If we're here, it's OK to append the current default item */ last = last->next = pj_mkparam(next->param); } - last->next = 0; + last->next = nullptr; pj_dealloc_params (ctx, defaults, 0); return last; @@ -425,14 +425,14 @@ Note that 'init=foo:bar' stays in the list. It is ignored after expansion. paralist *expn; /* Nowhere to start? */ - if (0==init) - return 0; + if (nullptr==init) + return nullptr; expn = get_init(ctx, init->param, allow_init_epsg); /* Nothing in expansion? */ - if (0==expn) - return 0; + if (nullptr==expn) + return nullptr; /* Locate the end of the list */ for (last = init; last && last->next; last = last->next); @@ -469,12 +469,12 @@ pj_init_plus_ctx( projCtx ctx, const char *definition ) char *argv[MAX_ARG]; char *defn_copy; int argc = 0, i, blank_count = 0; - PJ *result = NULL; + PJ *result = nullptr; /* make a copy that we can manipulate */ defn_copy = (char *) pj_malloc( strlen(definition)+1 ); if (!defn_copy) - return NULL; + return nullptr; strcpy( defn_copy, definition ); /* split into arguments based on '+' and trim white space */ @@ -497,7 +497,7 @@ pj_init_plus_ctx( projCtx ctx, const char *definition ) { pj_dalloc( defn_copy ); pj_ctx_set_errno( ctx, PJD_ERR_UNPARSEABLE_CS_DEF ); - return 0; + return nullptr; } argv[argc++] = defn_copy + i + 1; @@ -552,8 +552,8 @@ static PJ_CONSTRUCTOR locate_constructor (const char *name) { const PJ_OPERATIONS *operations; operations = proj_list_operations(); for (i = 0; (s = operations[i].id) && strcmp(name, s) ; ++i) ; - if (0==s) - return 0; + if (nullptr==s) + return nullptr; return (PJ_CONSTRUCTOR) operations[i].proj; } @@ -574,20 +574,20 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i paralist *curr, *init, *start; int i; int err; - PJ *PIN = 0; + PJ *PIN = nullptr; int n_pipelines = 0; int n_inits = 0; const PJ_UNITS *units; const PJ_PRIME_MERIDIANS *prime_meridians; - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx (); ctx->last_errno = 0; if (argc <= 0) { pj_ctx_set_errno (ctx, PJD_ERR_NO_ARGS); - return 0; + return nullptr; } /* count occurrences of pipelines and inits */ @@ -601,13 +601,13 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i /* can't have nested pipelines directly */ if (n_pipelines > 1) { pj_ctx_set_errno (ctx, PJD_ERR_MALFORMED_PIPELINE); - return 0; + return nullptr; } /* don't allow more than one +init in non-pipeline operations */ if (n_pipelines == 0 && n_inits > 1) { pj_ctx_set_errno (ctx, PJD_ERR_TOO_MANY_INITS); - return 0; + return nullptr; } @@ -647,7 +647,7 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i /* Find projection selection */ curr = pj_param_exists (start, "proj"); - if (0==curr) { + if (nullptr==curr) { pj_dealloc_params (ctx, start, PJD_ERR_PROJ_NOT_NAMED); return nullptr; } @@ -659,7 +659,7 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i name += 5; proj = locate_constructor (name); - if (0==proj) { + if (nullptr==proj) { pj_dealloc_params (ctx, start, PJD_ERR_UNKNOWN_PROJECTION_ID); return nullptr; } @@ -671,8 +671,8 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i /* Allocate projection structure */ - PIN = proj(0); - if (0==PIN) { + PIN = proj(nullptr); + if (nullptr==PIN) { pj_dealloc_params (ctx, start, ENOMEM); return nullptr; } @@ -686,10 +686,10 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i PIN->long_wrap_center = 0.0; strcpy( PIN->axis, "enu" ); - PIN->gridlist = NULL; + PIN->gridlist = nullptr; PIN->gridlist_count = 0; - PIN->vgridlist_geoid = NULL; + PIN->vgridlist_geoid = nullptr; PIN->vgridlist_geoid_count = 0; /* Set datum parameters. Similarly to +init parameters we want to expand */ @@ -755,16 +755,16 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i } /* Axis orientation */ - if( (pj_param(ctx, start,"saxis").s) != NULL ) + if( (pj_param(ctx, start,"saxis").s) != nullptr ) { const char *axis_legal = "ewnsud"; const char *axis_arg = pj_param(ctx, start,"saxis").s; if( strlen(axis_arg) != 3 ) return pj_default_destructor (PIN, PJD_ERR_AXIS); - if( strchr( axis_legal, axis_arg[0] ) == NULL - || strchr( axis_legal, axis_arg[1] ) == NULL - || strchr( axis_legal, axis_arg[2] ) == NULL) + if( strchr( axis_legal, axis_arg[0] ) == nullptr + || strchr( axis_legal, axis_arg[1] ) == nullptr + || strchr( axis_legal, axis_arg[2] ) == nullptr) return pj_default_destructor (PIN, PJD_ERR_AXIS); /* TODO: it would be nice to validate we don't have on axis repeated */ @@ -795,8 +795,8 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i /* Set units */ units = proj_list_units(); - s = 0; - if ((name = pj_param(ctx, start, "sunits").s) != NULL) { + s = nullptr; + if ((name = pj_param(ctx, start, "sunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; if (!s) return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); @@ -812,7 +812,7 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i s += 2; } - factor = pj_strtod(s, 0); + factor = pj_strtod(s, nullptr); if ((factor <= 0.0) || (1/factor==0)) return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); @@ -823,17 +823,17 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i PIN->to_meter = PIN->fr_meter = 1.; /* Set vertical units */ - s = 0; - if ((name = pj_param(ctx, start, "svunits").s) != NULL) { + s = nullptr; + if ((name = pj_param(ctx, start, "svunits").s) != nullptr) { for (i = 0; (s = units[i].id) && strcmp(name, s) ; ++i) ; if (!s) return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_UNIT_ID); s = units[i].to_meter; } if (s || (s = pj_param(ctx, start, "svto_meter").s)) { - PIN->vto_meter = pj_strtod(s, 0); + PIN->vto_meter = pj_strtod(s, nullptr); if (*s == '/') /* ratio number */ - PIN->vto_meter /= pj_strtod(++s, 0); + PIN->vto_meter /= pj_strtod(++s, nullptr); if (PIN->vto_meter <= 0.0) return pj_default_destructor (PIN, PJD_ERR_UNIT_FACTOR_LESS_THAN_0); PIN->vfr_meter = 1. / PIN->vto_meter; @@ -844,12 +844,12 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i /* Prime meridian */ prime_meridians = proj_list_prime_meridians(); - s = 0; - if ((name = pj_param(ctx, start, "spm").s) != NULL) { - const char *value = NULL; - char *next_str = NULL; + s = nullptr; + if ((name = pj_param(ctx, start, "spm").s) != nullptr) { + const char *value = nullptr; + char *next_str = nullptr; - for (i = 0; prime_meridians[i].id != NULL; ++i ) + for (i = 0; prime_meridians[i].id != nullptr; ++i ) { if( strcmp(name,prime_meridians[i].id) == 0 ) { @@ -858,21 +858,21 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i } } - if( value == NULL + if( value == nullptr && (dmstor_ctx(ctx,name,&next_str) != 0.0 || *name == '0') && *next_str == '\0' ) value = name; if (!value) return pj_default_destructor (PIN, PJD_ERR_UNKNOWN_PRIME_MERIDIAN); - PIN->from_greenwich = dmstor_ctx(ctx,value,NULL); + PIN->from_greenwich = dmstor_ctx(ctx,value,nullptr); } else PIN->from_greenwich = 0.0; /* Private object for the geodesic functions */ PIN->geod = static_cast(pj_calloc (1, sizeof (struct geod_geodesic))); - if (0==PIN->geod) + if (nullptr==PIN->geod) return pj_default_destructor (PIN, ENOMEM); geod_init(PIN->geod, PIN->a, (1 - sqrt (1 - PIN->es))); @@ -881,7 +881,7 @@ pj_init_ctx_with_allow_init_epsg(projCtx ctx, int argc, char **argv, int allow_i PIN = proj(PIN); if (proj_errno (PIN)) { pj_free(PIN); - return 0; + return nullptr; } proj_errno_restore (PIN, err); return PIN; diff --git a/src/pj_initcache.cpp b/src/pj_initcache.cpp index 3c347e4b..052a016c 100644 --- a/src/pj_initcache.cpp +++ b/src/pj_initcache.cpp @@ -31,8 +31,8 @@ static int cache_count = 0; static int cache_alloc = 0; -static char **cache_key = NULL; -static paralist **cache_paralist = NULL; +static char **cache_key = nullptr; +static paralist **cache_paralist = nullptr; /************************************************************************/ /* pj_clone_paralist() */ @@ -42,18 +42,18 @@ static paralist **cache_paralist = NULL; paralist *pj_clone_paralist( const paralist *list) { - paralist *list_copy = NULL, *next_copy = NULL; + paralist *list_copy = nullptr, *next_copy = nullptr; - for( ; list != NULL; list = list->next ) + for( ; list != nullptr; list = list->next ) { paralist *newitem = (paralist *) pj_malloc(sizeof(paralist) + strlen(list->param)); newitem->used = 0; - newitem->next = 0; + newitem->next = nullptr; strcpy( newitem->param, list->param ); - if( list_copy == NULL ) + if( list_copy == nullptr ) list_copy = newitem; else next_copy->next = newitem; @@ -85,7 +85,7 @@ void pj_clear_initcache() pj_dalloc( cache_key[i] ); /* free parameter list elements */ - for (; t != NULL; t = n) { + for (; t != nullptr; t = n) { n = t->next; pj_dalloc(t); } @@ -95,8 +95,8 @@ void pj_clear_initcache() pj_dalloc( cache_paralist ); cache_count = 0; cache_alloc= 0; - cache_key = NULL; - cache_paralist = NULL; + cache_key = nullptr; + cache_paralist = nullptr; pj_release_lock(); } @@ -112,11 +112,11 @@ paralist *pj_search_initcache( const char *filekey ) { int i; - paralist *result = NULL; + paralist *result = nullptr; pj_acquire_lock(); - for( i = 0; result == NULL && i < cache_count; i++) + for( i = 0; result == nullptr && i < cache_count; i++) { if( strcmp(filekey,cache_key[i]) == 0 ) { diff --git a/src/pj_internal.cpp b/src/pj_internal.cpp index ac9fe1e0..3f3d191e 100644 --- a/src/pj_internal.cpp +++ b/src/pj_internal.cpp @@ -73,7 +73,7 @@ Behave mostly as proj_trans, but attempt to use 2D interfaces only. Used in gie.c, to enforce testing 2D code, and by PJ_pipeline.c to implement chained calls starting out with a call to its 2D interface. ***************************************************************************************/ - if (0==P) + if (nullptr==P) return coo; if (P->inverted) direction = static_cast(-direction); @@ -103,7 +103,7 @@ Behave mostly as proj_trans, but attempt to use 3D interfaces only. Used in gie.c, to enforce testing 3D code, and by PJ_pipeline.c to implement chained calls starting out with a call to its 3D interface. ***************************************************************************************/ - if (0==P) + if (nullptr==P) return coo; if (P->inverted) direction = static_cast(-direction); @@ -135,14 +135,14 @@ Check if a a PJ has an inverse. /* Move P to a new context - or to the default context if 0 is specified */ void proj_context_set (PJ *P, PJ_CONTEXT *ctx) { - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx (); pj_set_ctx (P, ctx); } void proj_context_inherit (PJ *parent, PJ *child) { - if (0==parent) + if (nullptr==parent) pj_set_ctx (child, pj_get_default_ctx()); else pj_set_ctx (child, pj_get_ctx(parent)); @@ -160,8 +160,8 @@ considered whitespace. char *comment; char *start = c; - if (0==c) - return 0; + if (nullptr==c) + return nullptr; comment = strchr (c, '#'); if (comment) @@ -202,8 +202,8 @@ consuming their surrounding whitespace. /* Flag showing that a whitespace (ws) has been written after last non-ws */ size_t ws; - if (0==c) - return 0; + if (nullptr==c) + return nullptr; pj_chomp (c); n = strlen (c); @@ -303,16 +303,16 @@ It is the duty of the caller to free this array. size_t i, j; char **argv; - if (0==args) - return 0; + if (nullptr==args) + return nullptr; if (0==argc) - return 0; + return nullptr; /* turn the input string into an array of strings */ argv = (char **) calloc (argc, sizeof (char *)); - if (0==argv) - return 0; + if (nullptr==argv) + return nullptr; argv[0] = args; j = 1; for (i = 0; ; i++) { @@ -344,8 +344,8 @@ array. n += strlen (argv[i]); p = static_cast(pj_calloc (n + argc + 1, sizeof (char))); - if (0==p) - return 0; + if (nullptr==p) + return nullptr; if (0==argc) return p; @@ -364,7 +364,7 @@ void proj_context_errno_set (PJ_CONTEXT *ctx, int err) { Raise an error directly on a context, without going through a PJ belonging to that context. ******************************************************************************/ - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx(); pj_ctx_set_errno (ctx, err); } @@ -381,9 +381,9 @@ PJ_LOG_LEVEL proj_log_level (PJ_CONTEXT *ctx, PJ_LOG_LEVEL log_level) { Set logging level 0-3. Higher number means more debug info. 0 turns it off ****************************************************************************************/ PJ_LOG_LEVEL previous; - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx(); - if (0==ctx) + if (nullptr==ctx) return PJ_LOG_TELL; previous = static_cast(abs (ctx->debug_level)); if (PJ_LOG_TELL==log_level) @@ -435,11 +435,11 @@ void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf) { Put a new logging function into P's context. The opaque object app_data is passed as first arg at each call to the logger ******************************************************************************/ - if (0==ctx) + if (nullptr==ctx) pj_get_default_ctx (); - if (0==ctx) + if (nullptr==ctx) return; ctx->app_data = app_data; - if (0!=logf) + if (nullptr!=logf) ctx->logger = logf; } diff --git a/src/pj_inv.cpp b/src/pj_inv.cpp index aaa8fea9..ba7e6722 100644 --- a/src/pj_inv.cpp +++ b/src/pj_inv.cpp @@ -114,7 +114,7 @@ static PJ_COORD inv_finalize (PJ *P, PJ_COORD coo) { return coo; if (P->hgridshift) coo = proj_trans (P->hgridshift, PJ_FWD, coo); - else if (P->helmert || (P->cart_wgs84 != 0 && P->cart != 0)) { + else if (P->helmert || (P->cart_wgs84 != nullptr && P->cart != nullptr)) { coo = proj_trans (P->cart, PJ_FWD, coo); /* Go cartesian in local frame */ if( P->helmert ) coo = proj_trans (P->helmert, PJ_FWD, coo); /* Step into WGS84 */ diff --git a/src/pj_list.cpp b/src/pj_list.cpp index 55ea36c2..73ca5f86 100644 --- a/src/pj_list.cpp +++ b/src/pj_list.cpp @@ -22,7 +22,7 @@ #define PROJ_HEAD(id, name) {#id, pj_##id, &pj_s_##id}, const struct PJ_LIST pj_list[] = { #include "pj_list.h" - {0, 0, 0}, + {nullptr, nullptr, nullptr}, }; #undef PROJ_HEAD diff --git a/src/pj_log.cpp b/src/pj_log.cpp index 6654691c..0f81dc13 100644 --- a/src/pj_log.cpp +++ b/src/pj_log.cpp @@ -68,7 +68,7 @@ void pj_vlog( projCtx ctx, int level, const char *fmt, va_list args ) return; msg_buf = (char *) malloc(100000); - if( msg_buf == NULL ) + if( msg_buf == nullptr ) return; /* we should use vsnprintf where available once we add configure detect.*/ diff --git a/src/pj_malloc.cpp b/src/pj_malloc.cpp index 66977cf4..c8681570 100644 --- a/src/pj_malloc.cpp +++ b/src/pj_malloc.cpp @@ -95,8 +95,8 @@ It allocates space for an array of elements of size . The array is initialized to zeros. ***********************************************************************/ void *res = pj_malloc (n*size); - if (0==res) - return 0; + if (nullptr==res) + return nullptr; memset (res, 0, n*size); return res; } @@ -128,10 +128,10 @@ pointer" to signal an error in a multi level allocation: return p; // success ***********************************************************************/ - if (0==ptr) - return 0; + if (nullptr==ptr) + return nullptr; pj_dalloc (ptr); - return 0; + return nullptr; } /**********************************************************************/ @@ -161,7 +161,7 @@ void *pj_dealloc_params (PJ_CONTEXT *ctx, paralist *start, int errlev) { pj_dealloc(t); } pj_ctx_set_errno (ctx, errlev); - return (void *) 0; + return (void *) nullptr; } @@ -178,7 +178,7 @@ void *pj_dealloc_params (PJ_CONTEXT *ctx, paralist *start, int errlev) { /************************************************************************/ void pj_free(PJ *P) { - if (0==P) + if (nullptr==P) return; /* free projection parameters - all the hard work is done by */ /* pj_default_destructor, which is supposed */ @@ -205,8 +205,8 @@ PJ *pj_default_destructor (PJ *P, int errlev) { /* Destructor */ if (0!=errlev) pj_ctx_set_errno (pj_get_ctx(P), errlev); - if (0==P) - return 0; + if (nullptr==P) + return nullptr; /* free grid lists */ pj_dealloc( P->gridlist ); diff --git a/src/pj_mlfn.cpp b/src/pj_mlfn.cpp index 02e05c3a..e032e642 100644 --- a/src/pj_mlfn.cpp +++ b/src/pj_mlfn.cpp @@ -27,8 +27,8 @@ double *pj_enfn(double es) { double t, *en; en = (double *) pj_malloc(EN_SIZE * sizeof (double)); - if (0==en) - return 0; + if (nullptr==en) + return nullptr; en[0] = C00 - es * (C02 + es * (C04 + es * (C06 + es * C08))); en[1] = es * (C22 - es * (C04 + es * (C06 + es * C08))); diff --git a/src/pj_mutex.cpp b/src/pj_mutex.cpp index dc4a441b..61487cec 100644 --- a/src/pj_mutex.cpp +++ b/src/pj_mutex.cpp @@ -106,7 +106,14 @@ void pj_cleanup_lock() #include "pthread.h" #ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif static pthread_mutex_t core_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif #else static pthread_mutex_t core_lock; diff --git a/src/pj_open_lib.cpp b/src/pj_open_lib.cpp index 31a2c2e1..c75b4af6 100644 --- a/src/pj_open_lib.cpp +++ b/src/pj_open_lib.cpp @@ -39,14 +39,14 @@ #include "proj_internal.h" #include "projects.h" -static const char *(*pj_finder)(const char *) = NULL; +static const char *(*pj_finder)(const char *) = nullptr; static int path_count = 0; -static char **search_path = NULL; +static char **search_path = nullptr; static const char * proj_lib_name = #ifdef PROJ_LIB PROJ_LIB; #else -0; +nullptr; #endif /************************************************************************/ @@ -71,7 +71,7 @@ void pj_set_searchpath ( int count, const char **path ) { int i; - if (path_count > 0 && search_path != NULL) + if (path_count > 0 && search_path != nullptr) { for (i = 0; i < path_count; i++) { @@ -79,7 +79,7 @@ void pj_set_searchpath ( int count, const char **path ) } pj_dalloc(search_path); path_count = 0; - search_path = NULL; + search_path = nullptr; } if( count > 0 ) @@ -122,15 +122,15 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, static const char dir_chars[] = "/"; #endif - if( out_full_filename != NULL && out_full_filename_size > 0 ) + if( out_full_filename != nullptr && out_full_filename_size > 0 ) out_full_filename[0] = '\0'; /* check if ~/name */ if (*name == '~' && strchr(dir_chars,name[1]) ) - if ((sysname = getenv("HOME")) != NULL) { + if ((sysname = getenv("HOME")) != nullptr) { if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) { - return NULL; + return nullptr; } (void)strcpy(fname, sysname); fname[n = (int)strlen(fname)] = DIR_CHAR; @@ -138,7 +138,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, (void)strcpy(fname+n, name + 1); sysname = fname; } else - return NULL; + return nullptr; /* or fixed path: /name, ./name or ../name */ else if (strchr(dir_chars,*name) @@ -148,14 +148,14 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, sysname = name; /* or try to use application provided file finder */ - else if( pj_finder != NULL && pj_finder( name ) != NULL ) + else if( pj_finder != nullptr && pj_finder( name ) != nullptr ) sysname = pj_finder( name ); /* or is environment PROJ_LIB defined */ else if ((sysname = getenv("PROJ_LIB")) || (sysname = proj_lib_name)) { if( strlen(sysname) + 1 + strlen(name) + 1 > sizeof(fname) ) { - return NULL; + return nullptr; } (void)strcpy(fname, sysname); fname[n = (int)strlen(fname)] = DIR_CHAR; @@ -165,9 +165,9 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, } else /* just try it bare bones */ sysname = name; - if ((fid = pj_ctx_fopen(ctx, sysname, mode)) != NULL) + if ((fid = pj_ctx_fopen(ctx, sysname, mode)) != nullptr) { - if( out_full_filename != NULL && out_full_filename_size > 0 ) + if( out_full_filename != nullptr && out_full_filename_size > 0 ) { strncpy(out_full_filename, sysname, out_full_filename_size); out_full_filename[out_full_filename_size-1] = '\0'; @@ -178,7 +178,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, /* If none of those work and we have a search path, try it */ if (!fid && path_count > 0) { - for (i = 0; fid == NULL && i < path_count; i++) + for (i = 0; fid == nullptr && i < path_count; i++) { if( strlen(search_path[i]) + 1 + strlen(name) + 1 <= sizeof(fname) ) { @@ -189,7 +189,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, } if (fid) { - if( out_full_filename != NULL && out_full_filename_size > 0 ) + if( out_full_filename != nullptr && out_full_filename_size > 0 ) { strncpy(out_full_filename, sysname, out_full_filename_size); out_full_filename[out_full_filename_size-1] = '\0'; @@ -204,7 +204,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, pj_log( ctx, PJ_LOG_DEBUG_MAJOR, "pj_open_lib(%s): call fopen(%s) - %s", name, sysname, - fid == NULL ? "failed" : "succeeded" ); + fid == nullptr ? "failed" : "succeeded" ); return(fid); } @@ -215,7 +215,7 @@ pj_open_lib_ex(projCtx ctx, const char *name, const char *mode, PAFile pj_open_lib(projCtx ctx, const char *name, const char *mode) { - return pj_open_lib_ex(ctx, name, mode, NULL, 0); + return pj_open_lib_ex(ctx, name, mode, nullptr, 0); } /************************************************************************/ @@ -238,7 +238,7 @@ int pj_find_file(projCtx ctx, const char *short_filename, { PAFile f = pj_open_lib_ex(ctx, short_filename, "rb", out_full_filename, out_full_filename_size); - if( f != NULL ) + if( f != nullptr ) { pj_ctx_fclose(ctx, f); return 1; diff --git a/src/pj_param.cpp b/src/pj_param.cpp index 1887afe9..74247b72 100644 --- a/src/pj_param.cpp +++ b/src/pj_param.cpp @@ -12,9 +12,9 @@ paralist *pj_mkparam(const char *str) { paralist *newitem; - if((newitem = (paralist *)pj_malloc(sizeof(paralist) + strlen(str))) != NULL) { + if((newitem = (paralist *)pj_malloc(sizeof(paralist) + strlen(str))) != nullptr) { newitem->used = 0; - newitem->next = 0; + newitem->next = nullptr; if (*str == '+') ++str; (void)strcpy(newitem->param, str); @@ -28,8 +28,8 @@ paralist *pj_mkparam_ws (const char *str) { paralist *newitem; size_t len = 0; - if (0==str) - return 0; + if (nullptr==str) + return nullptr; /* Find start and length of string */ while (isspace (*str)) @@ -43,12 +43,12 @@ paralist *pj_mkparam_ws (const char *str) { /* Use calloc to automagically 0-terminate the copy */ newitem = (paralist *) pj_calloc (1, sizeof(paralist) + len + 1); - if (0==newitem) - return 0; + if (nullptr==newitem) + return nullptr; memmove(newitem->param, str, len); newitem->used = 0; - newitem->next = 0; + newitem->next = nullptr; return newitem; } @@ -74,8 +74,8 @@ paralist *pj_param_exists (paralist *list, const char *parameter) { size_t len = strlen (parameter); if (c) len = c - parameter; - if (list==0) - return 0; + if (list==nullptr) + return nullptr; for (next = list; next; next = next->next) { if (0==strncmp (parameter, next->param, len) && (next->param[len]=='=' || next->param[len]==0)) { @@ -83,10 +83,10 @@ paralist *pj_param_exists (paralist *list, const char *parameter) { return next; } if (0==strcmp (parameter, "step")) - return 0; + return nullptr; } - return 0; + return nullptr; } @@ -116,24 +116,24 @@ PROJVALUE pj_param (projCtx ctx, paralist *pl, const char *opt) { unsigned l; PROJVALUE value = {0}; - if ( ctx == NULL ) + if ( ctx == nullptr ) ctx = pj_get_default_ctx(); type = *opt++; - if (0==strchr ("tbirds", type)) { + if (nullptr==strchr ("tbirds", type)) { fprintf(stderr, "invalid request to pj_param, fatal\n"); exit(1); } pl = pj_param_exists (pl, opt); if (type == 't') { - value.i = pl != 0; + value.i = pl != nullptr; return value; } /* Not found */ - if (0==pl) { + if (nullptr==pl) { /* Return value after the switch, so that the return path is */ /* taken in all cases */ switch (type) { @@ -144,7 +144,7 @@ PROJVALUE pj_param (projCtx ctx, paralist *pl, const char *opt) { value.f = 0.; break; case 's': - value.s = 0; + value.s = nullptr; break; } return value; @@ -165,7 +165,7 @@ PROJVALUE pj_param (projCtx ctx, paralist *pl, const char *opt) { value.f = pj_atof(opt); break; case 'r': /* degrees input */ - value.f = dmstor_ctx(ctx, opt, 0); + value.f = dmstor_ctx(ctx, opt, nullptr); break; case 's': /* char string */ value.s = (char *) opt; diff --git a/src/pj_pr_list.cpp b/src/pj_pr_list.cpp index 4e71e471..b8ad2c04 100644 --- a/src/pj_pr_list.cpp +++ b/src/pj_pr_list.cpp @@ -67,7 +67,7 @@ char *pj_get_def( PJ *P, int options ) definition = (char *) pj_malloc(def_max); if (!definition) - return NULL; + return nullptr; definition[0] = '\0'; for (t = P->params; t; t = t->next) @@ -91,7 +91,7 @@ char *pj_get_def( PJ *P, int options ) } else { pj_dalloc( definition ); - return NULL; + return nullptr; } } diff --git a/src/pj_strerrno.cpp b/src/pj_strerrno.cpp index 16042f79..18ed0d33 100644 --- a/src/pj_strerrno.cpp +++ b/src/pj_strerrno.cpp @@ -79,7 +79,7 @@ char *pj_strerrno(int err) { size_t adjusted_err; if (0==err) - return 0; + return nullptr; /* System error codes are positive */ if (err > 0) { diff --git a/src/pj_strtod.cpp b/src/pj_strtod.cpp index f604a013..5a360c2e 100644 --- a/src/pj_strtod.cpp +++ b/src/pj_strtod.cpp @@ -65,7 +65,7 @@ */ double pj_atof( const char* nptr ) { - return pj_strtod(nptr, NULL); + return pj_strtod(nptr, nullptr); } @@ -134,7 +134,7 @@ static char* replace_point_by_locale_point(const char* pszNumber, char point, else { pszNew = pj_strdup(pszNumber); if (!pszNew) - return NULL; + return nullptr; } if( pszLocalePoint ) pszNew[pszLocalePoint - pszNumber] = ' '; diff --git a/src/pj_transform.cpp b/src/pj_transform.cpp index 53429967..7a73a65e 100644 --- a/src/pj_transform.cpp +++ b/src/pj_transform.cpp @@ -140,7 +140,7 @@ static int geographic_to_cartesian (PJ *P, PJ_DIRECTION dir, long n, int dist, d if (!P->is_geocent) return 0; - if ( z == NULL ) { + if ( z == nullptr ) { pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); return PJD_ERR_GEOCENTRIC; } @@ -195,10 +195,10 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * if (P->is_geocent) return 0; - if(P->fwd3d != NULL && !(z == NULL && P->is_latlong)) + if(P->fwd3d != nullptr && !(z == nullptr && P->is_latlong)) { /* Three dimensions must be defined */ - if ( z == NULL) + if ( z == nullptr) { pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); return PJD_ERR_GEOCENTRIC; @@ -296,7 +296,7 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * return 0; /* Check first if projection is invertible. */ - if( (P->inv3d == NULL) && (P->inv == NULL)) + if( (P->inv3d == nullptr) && (P->inv == nullptr)) { pj_ctx_set_errno(pj_get_ctx(P), PJD_ERR_NON_CONV_INV_MERI_DIST); pj_log( pj_get_ctx(P), PJ_LOG_ERROR, @@ -305,10 +305,10 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * } /* If invertible - First try inv3d if defined */ - if (P->inv3d != NULL && !(z == NULL && P->is_latlong)) + if (P->inv3d != nullptr && !(z == nullptr && P->is_latlong)) { /* Three dimensions must be defined */ - if ( z == NULL) + if ( z == nullptr) { pj_ctx_set_errno( pj_get_ctx(P), PJD_ERR_GEOCENTRIC); return PJD_ERR_GEOCENTRIC; @@ -430,7 +430,7 @@ static int height_unit (PJ *P, PJ_DIRECTION dir, long n, int dist, double *z) { /* Nothing to do? */ if (fac==1.0) return 0; - if (0==z) + if (nullptr==z) return 0; if (P->is_latlong) return 0; /* done in pj_inv3d() / pj_fwd3d() */ @@ -451,7 +451,7 @@ static int geometric_to_orthometric (PJ *P, PJ_DIRECTION dir, long n, int dist, int err; if (0==P->has_geoid_vgrids) return 0; - if (z==0) + if (z==nullptr) return PJD_ERR_GEOCENTRIC; err = pj_apply_vgridshift (P, "sgeoidgrids", &(P->vgridlist_geoid), @@ -713,7 +713,7 @@ int pj_compare_datums( PJ *srcdefn, PJ *dstdefn ) pj_param(srcdefn->ctx, srcdefn->params,"snadgrids").s; const char* dstnadgrids = pj_param(dstdefn->ctx, dstdefn->params,"snadgrids").s; - return srcnadgrids != 0 && dstnadgrids != 0 && + return srcnadgrids != nullptr && dstnadgrids != nullptr && strcmp( srcnadgrids, dstnadgrids ) == 0; } else @@ -859,7 +859,7 @@ int pj_datum_transform( PJ *src, PJ *dst, /* -------------------------------------------------------------------- */ /* Create a temporary Z array if one is not provided. */ /* -------------------------------------------------------------------- */ - if( z == NULL ) + if( z == nullptr ) { size_t bytes = sizeof(double) * point_count * point_offset; z = (double *) pj_malloc(bytes); @@ -1024,7 +1024,7 @@ static int adjust_axis( projCtx ctx, { double *target; - if( i_axis == 2 && z == NULL ) + if( i_axis == 2 && z == nullptr ) continue; if( i_axis == 0 ) diff --git a/src/pj_units.cpp b/src/pj_units.cpp index 877758a3..50f11396 100644 --- a/src/pj_units.cpp +++ b/src/pj_units.cpp @@ -33,7 +33,7 @@ pj_units[] = { {"ind-yd", "0.91439523", "Indian Yard", 0.91439523}, {"ind-ft", "0.30479841", "Indian Foot", 0.30479841}, {"ind-ch", "20.11669506", "Indian Chain", 20.11669506}, - {NULL, NULL, NULL, 0.0} + {nullptr, nullptr, nullptr, 0.0} }; const PJ_UNITS *proj_list_units() @@ -49,7 +49,7 @@ pj_angular_units[] = { {"rad", "1.0", "Radian", 1.0}, {"deg", "0.017453292519943296", "Degree", DEG_TO_RAD}, {"grad", "0.015707963267948967", "Grad", GRAD_TO_RAD}, - {NULL, NULL, NULL, 0.0} + {nullptr, nullptr, nullptr, 0.0} }; const PJ_UNITS *proj_list_angular_units() diff --git a/src/pj_utils.cpp b/src/pj_utils.cpp index 81a80b45..8587dc30 100644 --- a/src/pj_utils.cpp +++ b/src/pj_utils.cpp @@ -43,7 +43,7 @@ int pj_is_latlong( PJ *pj ) { - return pj == NULL || pj->is_latlong; + return pj == nullptr || pj->is_latlong; } /************************************************************************/ @@ -55,7 +55,7 @@ int pj_is_latlong( PJ *pj ) int pj_is_geocent( PJ *pj ) { - return pj != NULL && pj->is_geocent; + return pj != nullptr && pj->is_geocent; } /************************************************************************/ @@ -118,7 +118,7 @@ PJ *pj_latlong_from_proj( PJ *pj_in ) { pj_ctx_set_errno( pj_in->ctx, PJD_ERR_MAJOR_AXIS_NOT_GIVEN ); - return NULL; + return nullptr; } if( !got_datum ) diff --git a/src/proj.cpp b/src/proj.cpp index 2405781c..8bb29c31 100644 --- a/src/proj.cpp +++ b/src/proj.cpp @@ -44,7 +44,7 @@ static int static char *cheby_str, /* string controlling Chebychev evaluation */ - *oform = (char *)0, /* output format for x-y or decimal degrees */ + *oform = (char *)nullptr, /* output format for x-y or decimal degrees */ oform_buffer[16]; /* Buffer for oform when using -d */ static const char @@ -74,7 +74,7 @@ static projUV int_proj(projUV data) { /* file processing function */ static void process(FILE *fid) { - char line[MAX_LINE+3], *s = 0, pline[40]; + char line[MAX_LINE+3], *s = nullptr, pline[40]; PJ_COORD data; for (;;) { @@ -313,7 +313,7 @@ int main(int argc, char **argv) { FILE *fid; int pargc = 0, iargc = argc, eargc = 0, mon = 0; - if ( (emess_dat.Prog_name = strrchr(*argv,DIR_CHAR)) != NULL) + 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); @@ -410,7 +410,7 @@ int main(int argc, char **argv) { { printf("%12s %-12s %-30s\n", ld->id, ld->ellipse_id, ld->defn); - if( ld->comments != NULL && strlen(ld->comments) > 0 ) + if( ld->comments != nullptr && strlen(ld->comments) > 0 ) printf( "%25s %s\n", " ", ld->comments ); } } else @@ -559,7 +559,7 @@ int main(int argc, char **argv) { } } else { - if ((fid = fopen(*eargv, "rb")) == NULL) { + if ((fid = fopen(*eargv, "rb")) == nullptr) { emess(-2, *eargv, "input file"); continue; } @@ -571,7 +571,7 @@ int main(int argc, char **argv) { else process(fid); (void)fclose(fid); - emess_dat.File_name = 0; + emess_dat.File_name = nullptr; } if( Proj ) diff --git a/src/proj.h b/src/proj.h index 5c0e8fdc..8ec48088 100644 --- a/src/proj.h +++ b/src/proj.h @@ -331,7 +331,11 @@ typedef struct projCtx_t PJ_CONTEXT; /* Functionality for handling thread contexts */ +#ifdef __cplusplus +#define PJ_DEFAULT_CTX nullptr +#else #define PJ_DEFAULT_CTX 0 +#endif PJ_CONTEXT PROJ_DLL *proj_context_create (void); PJ_CONTEXT PROJ_DLL *proj_context_destroy (PJ_CONTEXT *ctx); diff --git a/src/proj_4D_api.cpp b/src/proj_4D_api.cpp index c2a37a49..da2ba28d 100644 --- a/src/proj_4D_api.cpp +++ b/src/proj_4D_api.cpp @@ -130,7 +130,7 @@ double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { int i; PJ_COORD t, org; - if (0==P) + if (nullptr==P) return HUGE_VAL; if (n < 1) { @@ -169,7 +169,7 @@ available. See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which work similarly, but prefers the 2D resp. 3D interfaces if available. ***************************************************************************************/ - if (0==P) + if (nullptr==P) return coord; if (P->inverted) direction = opposite_direction(direction); @@ -274,17 +274,17 @@ size_t proj_trans_generic ( size_t i, nmin; double null_broadcast = 0; - if (0==P) + if (nullptr==P) return 0; if (P->inverted) direction = opposite_direction(direction); /* ignore lengths of null arrays */ - if (0==x) nx = 0; - if (0==y) ny = 0; - if (0==z) nz = 0; - if (0==t) nt = 0; + if (nullptr==x) nx = 0; + if (nullptr==y) ny = 0; + if (nullptr==z) nz = 0; + if (nullptr==t) nt = 0; /* and make the nullities point to some real world memory for broadcasting nulls */ if (0==nx) x = &null_broadcast; @@ -433,7 +433,7 @@ Returns 1 on success, 0 on failure PJ *Q; paralist *p; int do_cart = 0; - if (0==P) + if (nullptr==P) return 0; /* Don't recurse when calling proj_create (which calls us back) */ @@ -446,12 +446,12 @@ Returns 1 on success, 0 on failure /* Don't axisswap if data are already in "enu" order */ if (p && (0!=strcmp ("enu", p->param))) { char *def = static_cast(malloc (100+strlen(P->axis))); - if (0==def) + if (nullptr==def) return 0; sprintf (def, "break_cs2cs_recursion proj=axisswap axis=%s", P->axis); Q = proj_create (P->ctx, def); free (def); - if (0==Q) + if (nullptr==Q) return 0; P->axisswap = skip_prep_fin(Q); } @@ -461,12 +461,12 @@ Returns 1 on success, 0 on failure if (p && strlen (p->param) > strlen ("geoidgrids=")) { char *gridnames = p->param + strlen ("geoidgrids="); char *def = static_cast(malloc (100+strlen(gridnames))); - if (0==def) + if (nullptr==def) return 0; sprintf (def, "break_cs2cs_recursion proj=vgridshift grids=%s", gridnames); Q = proj_create (P->ctx, def); free (def); - if (0==Q) + if (nullptr==Q) return 0; P->vgridshift = skip_prep_fin(Q); } @@ -476,18 +476,18 @@ Returns 1 on success, 0 on failure if (p && strlen (p->param) > strlen ("nadgrids=")) { char *gridnames = p->param + strlen ("nadgrids="); char *def = static_cast(malloc (100+strlen(gridnames))); - if (0==def) + if (nullptr==def) return 0; sprintf (def, "break_cs2cs_recursion proj=hgridshift grids=%s", gridnames); Q = proj_create (P->ctx, def); free (def); - if (0==Q) + if (nullptr==Q) return 0; P->hgridshift = skip_prep_fin(Q); } /* We ignore helmert if we have grid shift */ - p = P->hgridshift ? 0 : pj_param_exists (P->params, "towgs84"); + p = P->hgridshift ? nullptr : pj_param_exists (P->params, "towgs84"); while (p) { char *def; char *s = p->param; @@ -508,12 +508,12 @@ Returns 1 on success, 0 on failure return 0; def = static_cast(malloc (100+n)); - if (0==def) + if (nullptr==def) return 0; sprintf (def, "break_cs2cs_recursion proj=helmert exact %s convention=position_vector", s); Q = proj_create (P->ctx, def); free(def); - if (0==Q) + if (nullptr==Q) return 0; pj_inherit_ellipsoid_def (P, Q); P->helmert = skip_prep_fin (Q); @@ -533,19 +533,19 @@ Returns 1 on success, 0 on failure /* TODO later: use C++ ostringstream with imbue(std::locale::classic()) */ /* to be locale unaware */ char* next_pos; - for (next_pos = def; (next_pos = strchr (next_pos, ',')) != NULL; next_pos++) { + for (next_pos = def; (next_pos = strchr (next_pos, ',')) != nullptr; next_pos++) { *next_pos = '.'; } } Q = proj_create (P->ctx, def); - if (0==Q) + if (nullptr==Q) return 0; P->cart = skip_prep_fin (Q); if (!P->is_geocent) { sprintf (def, "break_cs2cs_recursion proj=cart ellps=WGS84"); Q = proj_create (P->ctx, def); - if (0==Q) + if (nullptr==Q) return 0; P->cart_wgs84 = skip_prep_fin (Q); } @@ -573,15 +573,15 @@ PJ *proj_create (PJ_CONTEXT *ctx, const char *definition) { int ret; int allow_init_epsg; - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx (); /* Make a copy that we can manipulate */ n = strlen (definition); args = (char *) malloc (n + 1); - if (0==args) { + if (nullptr==args) { proj_context_errno_set(ctx, ENOMEM); - return 0; + return nullptr; } strcpy (args, definition); @@ -589,7 +589,7 @@ PJ *proj_create (PJ_CONTEXT *ctx, const char *definition) { if (argc==0) { pj_dealloc (args); proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); - return 0; + return nullptr; } argv = pj_trim_argv (argc, args); @@ -624,18 +624,18 @@ indicator, as in {"+proj=utm", "+zone=32"}, or leave it out, as in {"proj=utm", PJ *P; const char *c; - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx (); - if (0==argv) { + if (nullptr==argv) { proj_context_errno_set(ctx, PJD_ERR_NO_ARGS); - return 0; + return nullptr; } /* We assume that free format is used, and build a full proj_create compatible string */ c = pj_make_args (argc, argv); - if (0==c) { + if (nullptr==c) { proj_context_errno_set(ctx, ENOMEM); - return 0; + return nullptr; } P = proj_create (ctx, c); @@ -672,7 +672,7 @@ void proj_area_destroy(PJ_AREA* area) { /************************************************************************/ void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) { - if( ctx == NULL ) { + if( ctx == nullptr ) { ctx = pj_get_default_ctx(); } ctx->use_proj4_init_rules = enable; @@ -697,7 +697,7 @@ static int EQUAL(const char* a, const char* b) { int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_legacy_code_path) { const char* val = getenv("PROJ_USE_PROJ4_INIT_RULES"); - if( ctx == NULL ) { + if( ctx == nullptr ) { ctx = pj_get_default_ctx(); } @@ -751,26 +751,26 @@ PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char PJ_OBJ_LIST* op_list; PJ_OBJ* op; const char* proj_string; - const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", NULL }; + const char* const optionsProj4Mode[] = { "USE_PROJ4_INIT_RULES=YES", nullptr }; const char* const* optionsImportCRS = - proj_context_get_use_proj4_init_rules(ctx, FALSE) ? optionsProj4Mode : NULL; + proj_context_get_use_proj4_init_rules(ctx, FALSE) ? optionsProj4Mode : nullptr; src = proj_obj_create_from_user_input(ctx, source_crs, optionsImportCRS); if( !src ) { - return NULL; + return nullptr; } dst = proj_obj_create_from_user_input(ctx, target_crs, optionsImportCRS); if( !dst ) { proj_obj_destroy(src); - return NULL; + return nullptr; } - operation_ctx = proj_create_operation_factory_context(ctx, NULL); + operation_ctx = proj_create_operation_factory_context(ctx, nullptr); if( !operation_ctx ) { proj_obj_destroy(src); proj_obj_destroy(dst); - return NULL; + return nullptr; } if( area && area->bbox_set ) { @@ -793,24 +793,24 @@ PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char proj_obj_destroy(dst); if( !op_list ) { - return NULL; + return nullptr; } if( proj_obj_list_get_count(op_list) == 0 ) { proj_obj_list_destroy(op_list); - return NULL; + return nullptr; } op = proj_obj_list_get(ctx, op_list, 0); proj_obj_list_destroy(op_list); if( !op ) { - return NULL; + return nullptr; } - proj_string = proj_obj_as_proj_string(ctx, op, PJ_PROJ_5, NULL); + proj_string = proj_obj_as_proj_string(ctx, op, PJ_PROJ_5, nullptr); if( !proj_string) { proj_obj_destroy(op); - return NULL; + return nullptr; } if( proj_string[0] == '\0' ) { @@ -827,7 +827,7 @@ PJ *proj_create_crs_to_crs (PJ_CONTEXT *ctx, const char *source_crs, const char PJ *proj_destroy (PJ *P) { pj_free (P); - return 0; + return nullptr; } /*****************************************************************************/ @@ -844,7 +844,7 @@ int proj_context_errno (PJ_CONTEXT *ctx) { Read an error directly from a context, without going through a PJ belonging to that context. ******************************************************************************/ - if (0==ctx) + if (nullptr==ctx) ctx = pj_get_default_ctx(); return pj_ctx_get_errno (ctx); } @@ -925,15 +925,15 @@ PJ_CONTEXT *proj_context_create (void) { PJ_CONTEXT *proj_context_destroy (PJ_CONTEXT *ctx) { - if (0==ctx) - return 0; + if (nullptr==ctx) + return nullptr; /* Trying to free the default context is a no-op (since it is statically allocated) */ if (pj_get_default_ctx ()==ctx) - return 0; + return nullptr; pj_ctx_free (ctx); - return 0; + return nullptr; } @@ -958,26 +958,26 @@ static char *path_append (char *buf, const char *app, size_t *buf_size) { #endif /* Nothing to do? */ - if (0 == app) + if (nullptr == app) return buf; applen = strlen (app); if (0 == applen) return buf; /* Start checking whether buf is long enough */ - if (0 != buf) + if (nullptr != buf) buflen = strlen (buf); len = buflen+applen+strlen (delim) + 1; /* "pj_realloc", so to speak */ if (*buf_size < len) { p = static_cast(pj_calloc (2 * len, sizeof (char))); - if (0==p) { + if (nullptr==p) { pj_dealloc (buf); - return 0; + return nullptr; } *buf_size = 2 * len; - if (buf != 0) + if (buf != nullptr) strcpy (p, buf); pj_dealloc (buf); buf = p; @@ -992,7 +992,7 @@ static char *path_append (char *buf, const char *app, size_t *buf_size) { static const char *empty = {""}; static char version[64] = {""}; -static PJ_INFO info = {0, 0, 0, 0, 0, 0, 0, 0}; +static PJ_INFO info = {0, 0, 0, nullptr, nullptr, nullptr, nullptr, 0}; static volatile int info_initialized = 0; /*****************************************************************************/ @@ -1006,7 +1006,7 @@ PJ_INFO proj_info (void) { size_t i, n; size_t buf_size = 0; - char *buf = 0; + char *buf = nullptr; pj_acquire_lock (); @@ -1065,7 +1065,7 @@ PJ_PROJ_INFO proj_pj_info(PJ *P) { /* proj_create_crs_to_crs in a future version that leverages the EPSG database. */ pjinfo.accuracy = -1.0; - if (0==P) + if (nullptr==P) return pjinfo; /* projection id */ @@ -1080,7 +1080,7 @@ PJ_PROJ_INFO proj_pj_info(PJ *P) { def = P->def_full; else def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */ - if (0==def) + if (nullptr==def) pjinfo.definition = empty; else pjinfo.definition = pj_shrink (def); @@ -1107,7 +1107,7 @@ PJ_GRID_INFO proj_grid_info(const char *gridname) { memset(&grinfo, 0, sizeof(PJ_GRID_INFO)); /* in case the grid wasn't found */ - if (gridinfo->filename == NULL) { + if (gridinfo->filename == nullptr) { pj_gridinfo_free(ctx, gridinfo); strcpy(grinfo.format, "missing"); return grinfo; @@ -1258,7 +1258,7 @@ PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { PJ_FACTORS factors = {0,0,0, 0,0,0, 0,0, 0,0,0,0}; struct FACTORS f; - if (0==P) + if (nullptr==P) return factors; if (pj_factors(lp.lp, P, 0.0, &f)) diff --git a/src/proj_etmerc.cpp b/src/proj_etmerc.cpp index b521c329..05f86f37 100644 --- a/src/proj_etmerc.cpp +++ b/src/proj_etmerc.cpp @@ -309,7 +309,7 @@ static PJ *setup(PJ *P) { /* general initialization */ PJ *PROJECTION(etmerc) { struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; return setup (P); @@ -323,7 +323,7 @@ PJ *PROJECTION(etmerc) { PJ *PROJECTION(utm) { long zone; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor (P, ENOMEM); P->opaque = Q; diff --git a/src/proj_mdist.cpp b/src/proj_mdist.cpp index c645d117..e3f0f5c6 100644 --- a/src/proj_mdist.cpp +++ b/src/proj_mdist.cpp @@ -73,8 +73,8 @@ proj_mdist_ini(double es) { El = Es; } if ((b = (struct MDIST *)malloc(sizeof(struct MDIST)+ - (i*sizeof(double)))) == NULL) - return(NULL); + (i*sizeof(double)))) == nullptr) + return(nullptr); b->nb = i - 1; b->es = es; b->E = Es; diff --git a/src/proj_rouss.cpp b/src/proj_rouss.cpp index f4fa084f..3b4428bc 100644 --- a/src/proj_rouss.cpp +++ b/src/proj_rouss.cpp @@ -86,10 +86,10 @@ static LP e_inverse (XY xy, PJ *P) { /* Ellipsoidal, inverse */ static PJ *destructor (PJ *P, int errlev) { - if (0==P) - return 0; + if (nullptr==P) + return nullptr; - if (0==P->opaque) + if (nullptr==P->opaque) return pj_default_destructor (P, errlev); if (static_cast(P->opaque)->en) @@ -103,7 +103,7 @@ PJ *PROJECTION(rouss) { double N0, es2, t, t2, R_R0_2, R_R0_4; struct pj_opaque *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque))); - if (0==Q) + if (nullptr==Q) return pj_default_destructor(P, ENOMEM); P->opaque = Q; diff --git a/src/proj_strtod.cpp b/src/proj_strtod.cpp index 05d448ec..b8edc6a3 100644 --- a/src/proj_strtod.cpp +++ b/src/proj_strtod.cpp @@ -106,7 +106,7 @@ double proj_strtod(const char *str, char **endptr) { int num_digits_after_comma = 0; int num_prefixed_zeros = 0; - if (0==str) { + if (nullptr==str) { errno = EFAULT; if (endptr) *endptr = p; @@ -125,7 +125,7 @@ double proj_strtod(const char *str, char **endptr) { } /* non-numeric? */ - if (0==strchr("0123456789+-._", *p)) { + if (nullptr==strchr("0123456789+-._", *p)) { if (endptr) *endptr = (char *) str; return 0; @@ -150,7 +150,7 @@ double proj_strtod(const char *str, char **endptr) { } /* stray sign, as in "+/-"? */ - if (0!=sign && (0==strchr ("0123456789._", *p) || 0==*p)) { + if (0!=sign && (nullptr==strchr ("0123456789._", *p) || 0==*p)) { if (endptr) *endptr = (char *) str; return 0; @@ -161,7 +161,7 @@ double proj_strtod(const char *str, char **endptr) { p++; /* zero? */ - if ((0==*p) || 0==strchr ("0123456789eE.", *p) || isspace(*p)) { + if ((0==*p) || nullptr==strchr ("0123456789eE.", *p) || isspace(*p)) { if (endptr) *endptr = p; return sign==-1? -0: 0; @@ -202,7 +202,7 @@ double proj_strtod(const char *str, char **endptr) { } /* if the next character is nonnumeric, we have reached the end */ - if (0==*p || 0==strchr ("_0123456789eE+-", *p)) { + if (0==*p || nullptr==strchr ("_0123456789eE+-", *p)) { if (endptr) *endptr = p; if (sign==-1) @@ -249,7 +249,7 @@ double proj_strtod(const char *str, char **endptr) { p++; /* Just a stray "e", as in 100elephants? */ - if (0==*p || 0==strchr ("0123456789+-_", *p)) { + if (0==*p || nullptr==strchr ("0123456789+-_", *p)) { p--; break; } diff --git a/src/projects.h b/src/projects.h index ac1a2152..099684d3 100644 --- a/src/projects.h +++ b/src/projects.h @@ -627,8 +627,8 @@ C_NAMESPACE PJ *pj_##name (PJ *P) { \ if (P) \ return pj_projection_specific_setup_##name (P); \ P = (PJ*) pj_calloc (1, sizeof(PJ)); \ - if (0==P) \ - return 0; \ + if (nullptr==P) \ + return nullptr; \ P->destructor = pj_default_destructor; \ P->descr = des_##name; \ P->need_ellps = NEED_ELLPS; \ diff --git a/src/test228.cpp b/src/test228.cpp index 83d29f8f..fcacd7c9 100644 --- a/src/test228.cpp +++ b/src/test228.cpp @@ -49,7 +49,7 @@ static void* thread_main(void* unused) x = -5.2*DEG_TO_RAD; y = 50*DEG_TO_RAD; proj_ret = pj_transform(p_WGS84_proj, - p_OSGB36_proj, 1, 1, &x, &y, NULL ); + p_OSGB36_proj, 1, 1, &x, &y, nullptr ); x *= RAD_TO_DEG; y *= RAD_TO_DEG; /*printf("%.18f %.18f\n", x, y); */ @@ -60,7 +60,7 @@ static void* thread_main(void* unused) pj_free (p_OSGB36_proj); pj_free (p_WGS84_proj); - return NULL; + return nullptr; } int main() @@ -73,8 +73,8 @@ int main() pthread_attr_init(&attr1); pthread_attr_init(&attr2); - pthread_create(&tid1, &attr1, thread_main, NULL); - pthread_create(&tid2, &attr2, thread_main, NULL); + pthread_create(&tid1, &attr1, thread_main, nullptr); + pthread_create(&tid2, &attr2, thread_main, nullptr); while(started != 2); run = 1; for(i=0;i<2;i++) diff --git a/src/vector1.cpp b/src/vector1.cpp index 22e1f5d0..869dd76a 100644 --- a/src/vector1.cpp +++ b/src/vector1.cpp @@ -15,14 +15,14 @@ freev2(void **v, int nrows) { vector2(int nrows, int ncols, int size) { void **s; - if ((s = (void **)pj_malloc(sizeof(void *) * nrows)) != NULL) { + if ((s = (void **)pj_malloc(sizeof(void *) * nrows)) != nullptr) { int rsize, i; rsize = size * ncols; for (i = 0; i < nrows; ++i) if (!(s[i] = pj_malloc(rsize))) { freev2(s, i); - return (void **)0; + return (void **)nullptr; } } return s; -- cgit v1.2.3 From 8198bb580a5640415e09b8fd3533ffaa11317ca6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Dec 2018 22:02:44 +0100 Subject: cpp conversion: fix remaining warnings --- src/PJ_horner.cpp | 2 +- src/PJ_pipeline.cpp | 4 ++-- src/PJ_unitconvert.cpp | 6 +++--- src/emess.h | 10 +--------- src/gen_cheb.cpp | 42 +++++++++++++++++++++++++++++++++++------- src/geod.cpp | 18 ++++++++++-------- src/geodesic.cpp | 36 ++++++++++++++++++------------------ src/gie.cpp | 4 ++-- src/optargpm.h | 12 ++++++------ src/pj_ell_set.cpp | 14 +++++++------- src/pj_errno.cpp | 2 +- src/pj_mutex.cpp | 2 ++ src/proj.cpp | 17 +++++++++-------- src/proj_4D_api.cpp | 4 ++-- src/projects.h | 8 ++++---- 15 files changed, 103 insertions(+), 78 deletions(-) (limited to 'src') diff --git a/src/PJ_horner.cpp b/src/PJ_horner.cpp index 3a1b7cca..73977de6 100644 --- a/src/PJ_horner.cpp +++ b/src/PJ_horner.cpp @@ -408,7 +408,7 @@ static PJ *horner_freeup (PJ *P, int errlev) { /* Destruc } -static int parse_coefs (PJ *P, double *coefs, char *param, int ncoefs) { +static int parse_coefs (PJ *P, double *coefs, const char *param, int ncoefs) { char *buf, *init, *next = nullptr; int i; diff --git a/src/PJ_pipeline.cpp b/src/PJ_pipeline.cpp index 6d409690..76fc58a5 100644 --- a/src/PJ_pipeline.cpp +++ b/src/PJ_pipeline.cpp @@ -256,7 +256,7 @@ static size_t argc_params (paralist *params) { } /* Sentinel for argument list */ -static char *argv_sentinel = "step"; +static const char *argv_sentinel = "step"; /* turn paralist into argc/argv style argument list */ static char **argv_params (paralist *params, size_t argc) { @@ -267,7 +267,7 @@ static char **argv_params (paralist *params, size_t argc) { return nullptr; for (; params != nullptr; params = params->next) argv[i++] = params->param; - argv[i++] = argv_sentinel; + argv[i++] = const_cast(argv_sentinel); return argv; } diff --git a/src/PJ_unitconvert.cpp b/src/PJ_unitconvert.cpp index f7545680..b25fd5d2 100644 --- a/src/PJ_unitconvert.cpp +++ b/src/PJ_unitconvert.cpp @@ -80,10 +80,10 @@ typedef double (*tconvert)(double); namespace { // anonymous namespace struct TIME_UNITS { - char *id; /* units keyword */ + const char *id; /* units keyword */ tconvert t_in; /* unit -> mod. julian date function pointer */ tconvert t_out; /* mod. julian date > unit function pointer */ - char *name; /* comments */ + const char *name; /* comments */ }; } // anonymous namespace @@ -436,7 +436,7 @@ static double get_unit_conversion_factor(const char* name, PJ *CONVERSION(unitconvert,0) { /***********************************************************************/ struct pj_opaque_unitconvert *Q = static_cast(pj_calloc (1, sizeof (struct pj_opaque_unitconvert))); - char *s, *name; + const char *s, *name; int i; double f; int xy_in_is_linear = -1; /* unknown */ diff --git a/src/emess.h b/src/emess.h index cb6b38f4..d552ec90 100644 --- a/src/emess.h +++ b/src/emess.h @@ -2,10 +2,6 @@ #ifndef EMESS_H #define EMESS_H -#ifdef __cplusplus -extern "C" { -#endif - struct EMESS { char *File_name, /* input file name */ *Prog_name; /* name of program */ @@ -15,7 +11,7 @@ struct EMESS { #ifdef EMESS_ROUTINE /* use type */ /* for emess procedure */ -struct EMESS PROJ_DLL emess_dat = { (char *)0, (char *)0, 0 }; +struct EMESS PROJ_DLL emess_dat = { nullptr, nullptr, 0 }; #ifdef sun /* Archaic SunOs 4.1.1, etc. */ extern char *sys_errlist[]; @@ -30,8 +26,4 @@ extern struct EMESS PROJ_DLL emess_dat; void PROJ_DLL emess(int, const char *, ...); -#ifdef __cplusplus -} -#endif - #endif /* end EMESS_H */ diff --git a/src/gen_cheb.cpp b/src/gen_cheb.cpp index ab16b409..4ba514d4 100644 --- a/src/gen_cheb.cpp +++ b/src/gen_cheb.cpp @@ -9,12 +9,40 @@ #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), char *s, PJ *P, +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), char *s, PJ *P, +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; @@ -22,18 +50,18 @@ void gen_cheb(int inverse, projUV (*proj)(projUV), char *s, PJ *P, char *arg, fmt[32]; projUV low, upp, resid; Tseries *F; - double (*input)(const char *, char **); + double (*input)(const char *, const char **); - input = inverse ? strtod : dmstor; + 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(s, &s, 10); - if (*s == ',') if (*++s != ',') NU = strtol(s, &s, 10); - if (*s == ',') if (*++s != ',') NV = strtol(s, &s, 10); + 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"); diff --git a/src/geod.cpp b/src/geod.cpp index d7741679..7b6367c6 100644 --- a/src/geod.cpp +++ b/src/geod.cpp @@ -16,12 +16,14 @@ 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 char -*oform = (char *)nullptr, /* output format for decimal degrees */ -*osform = "%.3f", /* output format for S */ -pline[50], /* work string */ -*usage = + +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) { @@ -146,7 +148,7 @@ int main(int argc, char **argv) { if(**++argv == '-') for(arg = *argv;;) { switch(*++arg) { case '\0': /* position of "stdin" */ - if (arg[-1] == '-') eargv[eargc++] = "-"; + if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); break; case 'a': /* output full set of values */ fullout = 1; @@ -217,11 +219,11 @@ noargument: emess(1,"missing argument for -%c",*arg); do_geod(); else { /* process input file list */ if (eargc == 0) /* if no specific files force sysin */ - eargv[eargc++] = "-"; + eargv[eargc++] = const_cast("-"); for ( ; eargc-- ; ++eargv) { if (**eargv == '-') { fid = stdin; - emess_dat.File_name = ""; + emess_dat.File_name = const_cast(""); } else { if ((fid = fopen(*eargv, "r")) == nullptr) { emess(-2, *eargv, "input file"); diff --git a/src/geodesic.cpp b/src/geodesic.cpp index badfc9fa..705056b6 100644 --- a/src/geodesic.cpp +++ b/src/geodesic.cpp @@ -507,13 +507,13 @@ real geod_genposition(const struct geod_geodesicline* l, real omg12, lam12, lon12; real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2; unsigned outmask = - (plat2 ? GEOD_LATITUDE : 0U) | - (plon2 ? GEOD_LONGITUDE : 0U) | - (pazi2 ? GEOD_AZIMUTH : 0U) | - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); + (plat2 ? GEOD_LATITUDE : GEOD_NONE) | + (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | + (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | + (ps12 ? GEOD_DISTANCE : GEOD_NONE) | + (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | + (pS12 ? GEOD_AREA : GEOD_NONE); outmask &= l->caps & OUT_ALL; if (!( TRUE /*Init()*/ && @@ -721,13 +721,13 @@ real geod_gendirect(const struct geod_geodesic* g, real* pS12) { struct geod_geodesicline l; unsigned outmask = - (plat2 ? GEOD_LATITUDE : 0U) | - (plon2 ? GEOD_LONGITUDE : 0U) | - (pazi2 ? GEOD_AZIMUTH : 0U) | - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); + (plat2 ? GEOD_LATITUDE : GEOD_NONE) | + (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | + (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | + (ps12 ? GEOD_DISTANCE : GEOD_NONE) | + (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | + (pS12 ? GEOD_AREA : GEOD_NONE); geod_lineinit(&l, g, lat1, lon1, azi1, /* Automatically supply GEOD_DISTANCE_IN if necessary */ @@ -764,10 +764,10 @@ static real geod_geninverse_int(const struct geod_geodesic* g, real omg12 = 0, somg12 = 2, comg12 = 0; unsigned outmask = - (ps12 ? GEOD_DISTANCE : 0U) | - (pm12 ? GEOD_REDUCEDLENGTH : 0U) | - (pM12 || pM21 ? GEOD_GEODESICSCALE : 0U) | - (pS12 ? GEOD_AREA : 0U); + (ps12 ? GEOD_DISTANCE : GEOD_NONE) | + (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | + (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | + (pS12 ? GEOD_AREA : GEOD_NONE); outmask &= OUT_ALL; /* Compute longitude difference (AngDiff does this carefully). Result is diff --git a/src/gie.cpp b/src/gie.cpp index 21a3a279..3e4770a2 100644 --- a/src/gie.cpp +++ b/src/gie.cpp @@ -604,7 +604,7 @@ either a conversion or a transformation) static PJ_COORD torad_coord (PJ *P, PJ_DIRECTION dir, PJ_COORD a) { size_t i, n; - char *axis = "enut"; + const char *axis = "enut"; paralist *l = pj_param_exists (P->params, "axis"); if (l && dir==PJ_INV) axis = l->param + strlen ("axis="); @@ -618,7 +618,7 @@ static PJ_COORD torad_coord (PJ *P, PJ_DIRECTION dir, PJ_COORD a) { static PJ_COORD todeg_coord (PJ *P, PJ_DIRECTION dir, PJ_COORD a) { size_t i, n; - char *axis = "enut"; + const char *axis = "enut"; paralist *l = pj_param_exists (P->params, "axis"); if (l && dir==PJ_FWD) axis = l->param + strlen ("axis="); diff --git a/src/optargpm.h b/src/optargpm.h index edd9fbee..035c6f92 100644 --- a/src/optargpm.h +++ b/src/optargpm.h @@ -201,9 +201,9 @@ int opt_record (OPTARGS *opt); int opt_input_loop (OPTARGS *opt, int binary); static int opt_is_flag (OPTARGS *opt, int ordinal); static int opt_raise_flag (OPTARGS *opt, int ordinal); -static int opt_ordinal (OPTARGS *opt, char *option); -int opt_given (OPTARGS *opt, char *option); -char *opt_arg (OPTARGS *opt, char *option); +static int opt_ordinal (OPTARGS *opt, const char *option); +int opt_given (OPTARGS *opt, const char *option); +char *opt_arg (OPTARGS *opt, const char *option); const char *opt_strip_path (const char *full_name); OPTARGS *opt_parse (int argc, char **argv, const char *flags, const char *keys, const char **longflags, const char **longkeys); @@ -315,7 +315,7 @@ static int opt_raise_flag (OPTARGS *opt, int ordinal) { } /* Find the ordinal value of any (short or long) option */ -static int opt_ordinal (OPTARGS *opt, char *option) { +static int opt_ordinal (OPTARGS *opt, const char *option) { int i; if (nullptr==opt) return 0; @@ -379,7 +379,7 @@ static int opt_ordinal (OPTARGS *opt, char *option) { /* Returns 0 if option was not given on command line, non-0 otherwise */ -int opt_given (OPTARGS *opt, char *option) { +int opt_given (OPTARGS *opt, const char *option) { int ordinal = opt_ordinal (opt, option); if (0==ordinal) return 0; @@ -391,7 +391,7 @@ int opt_given (OPTARGS *opt, char *option) { /* Returns the argument to a given option */ -char *opt_arg (OPTARGS *opt, char *option) { +char *opt_arg (OPTARGS *opt, const char *option) { int ordinal = opt_ordinal (opt, option); if (0==ordinal) return nullptr; diff --git a/src/pj_ell_set.cpp b/src/pj_ell_set.cpp index 9d7fae0a..486230a5 100644 --- a/src/pj_ell_set.cpp +++ b/src/pj_ell_set.cpp @@ -15,9 +15,9 @@ static int ellps_size (PJ *P); static int ellps_shape (PJ *P); static int ellps_spherification (PJ *P); -static paralist *pj_get_param (paralist *list, char *key); +static paralist *pj_get_param (paralist *list, const char *key); static char *pj_param_value (paralist *list); -static const PJ_ELLPS *pj_find_ellps (char *name); +static const PJ_ELLPS *pj_find_ellps (const char *name); /***************************************************************************************/ @@ -75,7 +75,7 @@ int pj_ellipsoid (PJ *P) { ****************************************************************************************/ int err = proj_errno_reset (P); - char *empty = {""}; + const char *empty = {""}; P->def_size = P->def_shape = P->def_spherification = P->def_ellps = nullptr; @@ -211,7 +211,7 @@ static int ellps_size (PJ *P) { /***************************************************************************************/ static int ellps_shape (PJ *P) { /***************************************************************************************/ - char *keys[] = {"rf", "f", "es", "e", "b"}; + const char *keys[] = {"rf", "f", "es", "e", "b"}; paralist *par = nullptr; char *def = nullptr; size_t i, len; @@ -317,7 +317,7 @@ static const double RV6 = 55/1296.; /***************************************************************************************/ static int ellps_spherification (PJ *P) { /***************************************************************************************/ - char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g"}; + const char *keys[] = {"R_A", "R_V", "R_a", "R_g", "R_h", "R_lat_a", "R_lat_g"}; size_t len, i; paralist *par = nullptr; char *def = nullptr; @@ -400,7 +400,7 @@ static int ellps_spherification (PJ *P) { /* locate parameter in list */ -static paralist *pj_get_param (paralist *list, char *key) { +static paralist *pj_get_param (paralist *list, const char *key) { size_t l = strlen(key); while (list && !(0==strncmp(list->param, key, l) && (0==list->param[l] || list->param[l] == '=') ) ) list = list->next; @@ -421,7 +421,7 @@ static char *pj_param_value (paralist *list) { } -static const PJ_ELLPS *pj_find_ellps (char *name) { +static const PJ_ELLPS *pj_find_ellps (const char *name) { int i; const char *s; const PJ_ELLPS *ellps; diff --git a/src/pj_errno.cpp b/src/pj_errno.cpp index 6e98cd73..f6ea9bfc 100644 --- a/src/pj_errno.cpp +++ b/src/pj_errno.cpp @@ -2,7 +2,7 @@ #include "projects.h" -C_NAMESPACE_VAR int pj_errno = 0; +int pj_errno = 0; /************************************************************************/ /* pj_get_errno_ref() */ diff --git a/src/pj_mutex.cpp b/src/pj_mutex.cpp index 61487cec..3752324c 100644 --- a/src/pj_mutex.cpp +++ b/src/pj_mutex.cpp @@ -34,7 +34,9 @@ #endif /* For PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #ifndef _WIN32 #include "proj_config.h" diff --git a/src/proj.cpp b/src/proj.cpp index 8bb29c31..b93fb04d 100644 --- a/src/proj.cpp +++ b/src/proj.cpp @@ -20,7 +20,7 @@ #define MAX_PARGS 100 #define PJ_INVERS(P) (P->inv ? 1 : 0) -extern void gen_cheb(int, projUV(*)(projUV), char *, PJ *, int, char **); +extern void gen_cheb(int, projUV(*)(projUV), const char *, PJ *, int, char **); static PJ *Proj; static union { @@ -42,10 +42,10 @@ static int very_verby = 0, /* very verbose mode */ postscale = 0; -static char +static const char *cheby_str, /* string controlling Chebychev evaluation */ - *oform = (char *)nullptr, /* output format for x-y or decimal degrees */ - oform_buffer[16]; /* Buffer for oform when using -d */ + *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 */ @@ -309,7 +309,8 @@ static void vprocess(FILE *fid) { } int main(int argc, char **argv) { - char *arg, **eargv = argv, *pargv[MAX_PARGS], **iargv = argv; + char *arg, *pargv[MAX_PARGS], **iargv = argv; + char **eargv = argv; FILE *fid; int pargc = 0, iargc = argc, eargc = 0, mon = 0; @@ -327,7 +328,7 @@ int main(int argc, char **argv) { if(**++argv == '-') for(arg = *argv;;) { switch(*++arg) { case '\0': /* position of "stdin" */ - if (arg[-1] == '-') eargv[eargc++] = "-"; + if (arg[-1] == '-') eargv[eargc++] = const_cast("-"); break; case 'b': /* binary I/O */ bin_in = bin_out = 1; @@ -482,7 +483,7 @@ int main(int argc, char **argv) { eargv[eargc++] = *argv; } if (eargc == 0 && !cheby_str) /* if no specific files force sysin */ - eargv[eargc++] = "-"; + eargv[eargc++] = const_cast("-"); else if (eargc > 0 && cheby_str) /* warning */ emess(4, "data files when generating Chebychev prohibited"); /* done with parameter and control input */ @@ -551,7 +552,7 @@ int main(int argc, char **argv) { for ( ; eargc-- ; ++eargv) { if (**eargv == '-') { fid = stdin; - emess_dat.File_name = ""; + emess_dat.File_name = const_cast(""); if (bin_in) { diff --git a/src/proj_4D_api.cpp b/src/proj_4D_api.cpp index da2ba28d..88210348 100644 --- a/src/proj_4D_api.cpp +++ b/src/proj_4D_api.cpp @@ -952,9 +952,9 @@ static char *path_append (char *buf, const char *app, size_t *buf_size) { char *p; size_t len, applen = 0, buflen = 0; #ifdef _WIN32 - char *delim = ";"; + const char *delim = ";"; #else - char *delim = ":"; + const char *delim = ":"; #endif /* Nothing to do? */ diff --git a/src/projects.h b/src/projects.h index 099684d3..b76205a5 100644 --- a/src/projects.h +++ b/src/projects.h @@ -492,10 +492,10 @@ typedef union { double f; int i; char *s; } PROJVALUE; struct PJ_DATUMS { - char *id; /* datum keyword */ - char *defn; /* ie. "to_wgs84=..." */ - char *ellipse_id; /* ie from ellipse table */ - char *comments; /* EPSG code, etc */ + const char *id; /* datum keyword */ + const char *defn; /* ie. "to_wgs84=..." */ + const char *ellipse_id; /* ie from ellipse table */ + const char *comments; /* EPSG code, etc */ }; -- cgit v1.2.3 From 30686ee04c5b897e2293828f07785820cf68d67d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Dec 2018 22:28:57 +0100 Subject: pj_transform cleanup: do not redefine PJ_DIRECTION and proj_errno_reset, but include proj.h instead --- src/pj_transform.cpp | 85 ++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/pj_transform.cpp b/src/pj_transform.cpp index 7a73a65e..433fc017 100644 --- a/src/pj_transform.cpp +++ b/src/pj_transform.cpp @@ -31,23 +31,10 @@ #include #include +#include "proj.h" #include "projects.h" #include "geocent.h" - -/* Apply transformation to observation - in forward or inverse direction */ -/* Copied from proj.h */ -enum PJ_DIRECTION { - PJ_FWD = 1, /* Forward */ - PJ_IDENT = 0, /* Do nothing */ - PJ_INV = -1 /* Inverse */ -}; -typedef enum PJ_DIRECTION PJ_DIRECTION; - -/* Copied from proj.h FIXME */ -extern "C" int proj_errno_reset (const PJ *P); - - static int adjust_axis( projCtx ctx, const char *axis, int denormalize_flag, long point_count, int point_offset, double *x, double *y, double *z ); @@ -209,11 +196,11 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * XYZ projected_loc; LPZ geodetic_loc; - geodetic_loc.u = x[dist*i]; - geodetic_loc.v = y[dist*i]; - geodetic_loc.w = z[dist*i]; + geodetic_loc.lam = x[dist*i]; + geodetic_loc.phi = y[dist*i]; + geodetic_loc.z = z[dist*i]; - if (geodetic_loc.u == HUGE_VAL) + if (geodetic_loc.lam == HUGE_VAL) continue; proj_errno_reset( P ); @@ -230,15 +217,15 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * } else { - projected_loc.u = HUGE_VAL; - projected_loc.v = HUGE_VAL; - projected_loc.w = HUGE_VAL; + projected_loc.x = HUGE_VAL; + projected_loc.y = HUGE_VAL; + projected_loc.z = HUGE_VAL; } } - x[dist*i] = projected_loc.u; - y[dist*i] = projected_loc.v; - z[dist*i] = projected_loc.w; + x[dist*i] = projected_loc.x; + y[dist*i] = projected_loc.y; + z[dist*i] = projected_loc.z; } return 0; } @@ -248,10 +235,10 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * XY projected_loc; LP geodetic_loc; - geodetic_loc.u = x[dist*i]; - geodetic_loc.v = y[dist*i]; + geodetic_loc.lam = x[dist*i]; + geodetic_loc.phi = y[dist*i]; - if( geodetic_loc.u == HUGE_VAL ) + if( geodetic_loc.lam == HUGE_VAL ) continue; proj_errno_reset( P ); @@ -268,13 +255,13 @@ static int geographic_to_projected (PJ *P, long n, int dist, double *x, double * } else { - projected_loc.u = HUGE_VAL; - projected_loc.v = HUGE_VAL; + projected_loc.x = HUGE_VAL; + projected_loc.y = HUGE_VAL; } } - x[dist*i] = projected_loc.u; - y[dist*i] = projected_loc.v; + x[dist*i] = projected_loc.x; + y[dist*i] = projected_loc.y; } return 0; } @@ -317,13 +304,13 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * for (i=0; i < n; i++) { XYZ projected_loc; - XYZ geodetic_loc; + LPZ geodetic_loc; - projected_loc.u = x[dist*i]; - projected_loc.v = y[dist*i]; - projected_loc.w = z[dist*i]; + projected_loc.x = x[dist*i]; + projected_loc.y = y[dist*i]; + projected_loc.z = z[dist*i]; - if (projected_loc.u == HUGE_VAL) + if (projected_loc.x == HUGE_VAL) continue; proj_errno_reset( P ); @@ -340,15 +327,15 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * } else { - geodetic_loc.u = HUGE_VAL; - geodetic_loc.v = HUGE_VAL; - geodetic_loc.w = HUGE_VAL; + geodetic_loc.lam = HUGE_VAL; + geodetic_loc.phi = HUGE_VAL; + geodetic_loc.z = HUGE_VAL; } } - x[dist*i] = geodetic_loc.u; - y[dist*i] = geodetic_loc.v; - z[dist*i] = geodetic_loc.w; + x[dist*i] = geodetic_loc.lam; + y[dist*i] = geodetic_loc.phi; + z[dist*i] = geodetic_loc.z; } return 0; @@ -359,10 +346,10 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * XY projected_loc; LP geodetic_loc; - projected_loc.u = x[dist*i]; - projected_loc.v = y[dist*i]; + projected_loc.x = x[dist*i]; + projected_loc.y = y[dist*i]; - if( projected_loc.u == HUGE_VAL ) + if( projected_loc.x == HUGE_VAL ) continue; proj_errno_reset( P ); @@ -379,13 +366,13 @@ static int projected_to_geographic (PJ *P, long n, int dist, double *x, double * } else { - geodetic_loc.u = HUGE_VAL; - geodetic_loc.v = HUGE_VAL; + geodetic_loc.lam = HUGE_VAL; + geodetic_loc.phi = HUGE_VAL; } } - x[dist*i] = geodetic_loc.u; - y[dist*i] = geodetic_loc.v; + x[dist*i] = geodetic_loc.lam; + y[dist*i] = geodetic_loc.phi; } return 0; } -- cgit v1.2.3 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