|
| 1 | +#include "format.hpp" |
| 2 | +#include "reprojection.hpp" |
| 3 | + |
| 4 | +#include <osmium/geom/coordinates.hpp> |
| 5 | +#include <osmium/geom/util.hpp> |
| 6 | + |
| 7 | +#include "proj.h" |
| 8 | + |
| 9 | +namespace { |
| 10 | + |
| 11 | +/** |
| 12 | + * Generic projection using proj library (version 6 and above). |
| 13 | + */ |
| 14 | +class generic_reprojection_t : public reprojection |
| 15 | +{ |
| 16 | +public: |
| 17 | + explicit generic_reprojection_t(int srs) |
| 18 | + : m_target_srs(srs), m_context(proj_context_create()) |
| 19 | + { |
| 20 | + assert(m_context); |
| 21 | + |
| 22 | + m_transformation = create_transformation(PROJ_LATLONG, srs); |
| 23 | + |
| 24 | + m_transformation.reset(proj_normalize_for_visualization( |
| 25 | + m_context.get(), m_transformation.get())); |
| 26 | + |
| 27 | + if (!m_transformation) { |
| 28 | + throw std::runtime_error{ |
| 29 | + "Invalid projection '{}': {}"_format(srs, errormsg())}; |
| 30 | + } |
| 31 | + |
| 32 | + m_transformation_tile = create_transformation(PROJ_SPHERE_MERC, srs); |
| 33 | + } |
| 34 | + |
| 35 | + osmium::geom::Coordinates reproject(osmium::Location loc) const override |
| 36 | + { |
| 37 | + return transform(m_transformation.get(), |
| 38 | + osmium::geom::Coordinates{loc.lon_without_check(), |
| 39 | + loc.lat_without_check()}); |
| 40 | + } |
| 41 | + |
| 42 | + osmium::geom::Coordinates |
| 43 | + target_to_tile(osmium::geom::Coordinates coords) const override |
| 44 | + { |
| 45 | + return transform(m_transformation_tile.get(), coords); |
| 46 | + } |
| 47 | + |
| 48 | + int target_srs() const noexcept override { return m_target_srs; } |
| 49 | + |
| 50 | + char const *target_desc() const noexcept override { return ""; } |
| 51 | + |
| 52 | +private: |
| 53 | + struct pj_context_deleter_t |
| 54 | + { |
| 55 | + void operator()(PJ_CONTEXT *ctx) const noexcept |
| 56 | + { |
| 57 | + proj_context_destroy(ctx); |
| 58 | + } |
| 59 | + }; |
| 60 | + |
| 61 | + struct pj_deleter_t |
| 62 | + { |
| 63 | + void operator()(PJ *p) const noexcept { proj_destroy(p); } |
| 64 | + }; |
| 65 | + |
| 66 | + char const *errormsg() const noexcept |
| 67 | + { |
| 68 | + return proj_errno_string(proj_context_errno(m_context.get())); |
| 69 | + } |
| 70 | + |
| 71 | + std::unique_ptr<PJ, pj_deleter_t> create_transformation(int from, |
| 72 | + int to) const |
| 73 | + { |
| 74 | + std::string const source = "epsg:{}"_format(from); |
| 75 | + std::string const target = "epsg:{}"_format(to); |
| 76 | + |
| 77 | + std::unique_ptr<PJ, pj_deleter_t> trans{proj_create_crs_to_crs( |
| 78 | + m_context.get(), source.c_str(), target.c_str(), nullptr)}; |
| 79 | + |
| 80 | + if (!trans) { |
| 81 | + throw std::runtime_error{ |
| 82 | + "Invalid projection from {} to {}: {}"_format(from, to, |
| 83 | + errormsg())}; |
| 84 | + } |
| 85 | + return trans; |
| 86 | + } |
| 87 | + |
| 88 | + osmium::geom::Coordinates transform(PJ *transformation, |
| 89 | + osmium::geom::Coordinates coords) const |
| 90 | + noexcept |
| 91 | + { |
| 92 | + PJ_COORD c_in; |
| 93 | + c_in.lpzt.z = 0.0; |
| 94 | + c_in.lpzt.t = HUGE_VAL; |
| 95 | + c_in.lpzt.lam = osmium::geom::deg_to_rad(coords.x); |
| 96 | + c_in.lpzt.phi = osmium::geom::deg_to_rad(coords.y); |
| 97 | + |
| 98 | + auto const c_out = proj_trans(transformation, PJ_FWD, c_in); |
| 99 | + |
| 100 | + return osmium::geom::Coordinates{c_out.xy.x, c_out.xy.y}; |
| 101 | + } |
| 102 | + |
| 103 | + int m_target_srs; |
| 104 | + std::unique_ptr<PJ_CONTEXT, pj_context_deleter_t> m_context; |
| 105 | + std::unique_ptr<PJ, pj_deleter_t> m_transformation; |
| 106 | + |
| 107 | + /** |
| 108 | + * The projection used for tiles. Currently this is fixed to be Spherical |
| 109 | + * Mercator. You will usually have tiles in the same projection as used |
| 110 | + * for PostGIS, but it is theoretically possible to have your PostGIS data |
| 111 | + * in, say, lat/lon but still create tiles in Spherical Mercator. |
| 112 | + */ |
| 113 | + std::unique_ptr<PJ, pj_deleter_t> m_transformation_tile; |
| 114 | +}; |
| 115 | + |
| 116 | +} // anonymous namespace |
| 117 | + |
| 118 | +std::shared_ptr<reprojection> reprojection::make_generic_projection(int srs) |
| 119 | +{ |
| 120 | + return std::make_shared<generic_reprojection_t>(srs); |
| 121 | +} |
| 122 | + |
| 123 | +std::string get_proj_version() |
| 124 | +{ |
| 125 | + return "[API 6] {}"_format(proj_info().version); |
| 126 | +} |
| 127 | + |
0 commit comments