summaryrefslogtreecommitdiffstats
path: root/server/src/geo/utm.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/geo/utm.cppm')
-rw-r--r--server/src/geo/utm.cppm199
1 files changed, 199 insertions, 0 deletions
diff --git a/server/src/geo/utm.cppm b/server/src/geo/utm.cppm
new file mode 100644
index 0000000..83da222
--- /dev/null
+++ b/server/src/geo/utm.cppm
@@ -0,0 +1,199 @@
1module;
2
3#include <cassert>
4#include <boost/geometry/geometries/geometries.hpp>
5
6export module routemon:geo.utm;
7
8import std;
9import :geo;
10import :geo.wgs84;
11
12namespace routemon::geo::utm
13{
14
15class zonable_wgs84_point
16{
17 wgs84::normalized_point p_;
18
19 explicit zonable_wgs84_point(wgs84::normalized_point p)
20 : p_{p}
21 {}
22
23public:
24 zonable_wgs84_point()
25 : p_{}
26 {
27 }
28
29 static auto from(wgs84::normalized_point p) -> std::optional<zonable_wgs84_point>
30 {
31 if (p.lat() < -80 || p.lat() > 84)
32 return std::nullopt;
33 return zonable_wgs84_point{p};
34 }
35
36 auto lat() const -> double
37 {
38 return p_.lat();
39 }
40
41 auto lon() const -> double
42 {
43 return p_.lon();
44 }
45
46 auto lon(double lon) -> void
47 {
48 p_.lon(lon);
49 }
50
51 auto lat(double lat) -> void
52 {
53 if (lat < -80 || lat > 84)
54 throw std::range_error{"latitude out of range for UTM"};
55 p_.lat(lat);
56 }
57};
58
59}
60
61namespace boost::geometry::traits {
62
63 namespace {
64
65 using zonable_wgs84_point = routemon::geo::utm::zonable_wgs84_point;
66
67 } // namespace <anonymous>
68
69 template<> struct tag<zonable_wgs84_point> { using type = point_tag; };
70 template<> struct dimension<zonable_wgs84_point> : boost::mpl::int_<2> {};
71 template<> struct coordinate_type<zonable_wgs84_point> { using type = double; };
72 template<> struct coordinate_system<zonable_wgs84_point> { using type = routemon::geo::wgs84::cs; };
73
74 template<std::size_t index>
75 struct access<zonable_wgs84_point, index> {
76 static inline auto get(zonable_wgs84_point const& p) -> double
77 {
78 if constexpr (index == 0)
79 return p.lon();
80 else if constexpr (index == 1)
81 return p.lat();
82 else static_assert(false, "Out of range");
83 }
84
85 static inline auto set(zonable_wgs84_point& p, double v) -> void
86 {
87 if constexpr (index == 0)
88 p.lon(v);
89 else if constexpr (index == 1)
90 p.lat(v);
91 else static_assert(false, "Out of range");
92 }
93 };
94
95} // namespace boost::geometry::traits
96
97namespace routemon::geo::utm
98{
99
100using zonable_wgs84_linestring = bgeo::model::linestring<zonable_wgs84_point, std::vector>;
101
102// In the sense of the common WGS84 subdivisions by simple northing
103// and easting (so no Norway/Svalbard exceptions).
104class zone
105{
106 // Note: calculations heavily depend on the values of the variants.
107 enum class hemisphere : std::uint8_t
108 {
109 northern = 0,
110 southern = 1,
111 };
112
113 // Negative if in the southern hemisphere
114 // Equal to (zone_no - 1) * 2 + hemisphere
115 // In range [0, 119]
116 std::uint8_t zone_;
117
118 static constexpr auto zone_no_valid(std::uint8_t zone_no) -> bool
119 {
120 return 0 < zone_no && zone_no <= 60;
121 }
122
123 static constexpr auto from(std::uint8_t zone_no, hemisphere h) -> std::uint8_t
124 {
125 if (!zone_no_valid(zone_no))
126 throw std::invalid_argument{"invalid UTM zone number"};
127 return (zone_no - 1) * 2 + static_cast<std::uint8_t>(h);
128 }
129
130public:
131 explicit constexpr zone(std::uint8_t zone_no, hemisphere h)
132 : zone_{from(zone_no, h)}
133 {}
134
135 auto hemisphere() const -> enum hemisphere
136 {
137 return static_cast<enum hemisphere>(zone_ % 2);
138 }
139
140 // Return value in range [1, 60]
141 auto zone_no() const -> std::uint8_t
142 {
143 return 1 + zone_ / 2;
144 }
145
146 // Return value in range [min(), max()]
147 constexpr auto as_index() const -> std::uint8_t
148 {
149 return zone_;
150 }
151
152 static auto for_wgs84_point(zonable_wgs84_point p) noexcept -> zone
153 {
154 auto zone_no = static_cast<std::uint8_t>(1 + (p.lon() + 180.0) / 6.0);
155 auto northern = p.lat() >= 0.0;
156 return zone{zone_no, northern ? hemisphere::northern : hemisphere::southern};
157 }
158
159 auto wgs84_proj_epsg() const -> int
160 {
161 switch (hemisphere())
162 {
163 case hemisphere::northern:
164 return 32600 + zone_no();
165 case hemisphere::southern:
166 return 32700 + zone_no();
167 }
168 }
169
170 // Guarantee: min().as_index() == 0.
171 static constexpr auto min() noexcept -> zone
172 {
173 return zone{1, hemisphere::northern};
174 }
175
176 static constexpr auto max() noexcept -> zone
177 {
178 return zone{60, hemisphere::southern};
179 }
180
181 auto next() const -> zone
182 {
183 assert(zone_ <= max().as_index());
184 if (zone_ == max().as_index())
185 throw std::range_error{"cannot take next of greatest UTM zone"};
186 auto copy = zone{*this};
187 copy.zone_++;
188 return copy;
189 }
190
191 auto operator==(zone rhs) const -> bool
192 {
193 return zone_ == rhs.zone_;
194 }
195};
196static_assert(zone::min().as_index() == 0);
197static_assert(zone::max().as_index() == 119);
198
199} // namespace routemon::geo::utm