diff options
| author | Rutger Broekhoff | 2026-09-08 13:54:40 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-09-08 13:54:40 +0200 |
| commit | 1417e6cf41d27c594d2bdc92f87746d2383a1b1d (patch) | |
| tree | b7c5b811ed9da30fb74ea620407cf455e694c8a0 /server | |
| parent | 5f9eee3f8562836e6a76b0b03a7b9a3592a4fbee (diff) | |
| download | routemon-1417e6cf41d27c594d2bdc92f87746d2383a1b1d.tar.gz routemon-1417e6cf41d27c594d2bdc92f87746d2383a1b1d.zip | |
Some cleanups
Diffstat (limited to 'server')
| -rw-r--r-- | server/CMakeLists.txt | 2 | ||||
| -rwxr-xr-x | server/cf.sh | 4 | ||||
| -rw-r--r-- | server/src/api.cpp | 3 | ||||
| -rw-r--r-- | server/src/api.cppm | 6 | ||||
| -rw-r--r-- | server/src/geo/multizonal.cppm | 307 | ||||
| -rw-r--r-- | server/src/geo/utm.cppm | 140 | ||||
| -rw-r--r-- | server/src/geo/utm_multizonal.cppm | 343 | ||||
| -rw-r--r-- | server/src/geo/utm_zone_local.cppm | 95 | ||||
| -rw-r--r-- | server/src/geo/wgs84.cppm | 51 |
9 files changed, 494 insertions, 457 deletions
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 53cda76..fad973f 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt | |||
| @@ -64,8 +64,8 @@ target_sources(routemon_lib | |||
| 64 | src/xml.cppm | 64 | src/xml.cppm |
| 65 | src/xml.cpp | 65 | src/xml.cpp |
| 66 | src/geo/geo.cppm | 66 | src/geo/geo.cppm |
| 67 | src/geo/multizonal.cppm | ||
| 68 | src/geo/utm.cppm | 67 | src/geo/utm.cppm |
| 68 | src/geo/utm_multizonal.cppm | ||
| 69 | src/geo/utm_zone_local.cppm | 69 | src/geo/utm_zone_local.cppm |
| 70 | src/geo/wgs84.cppm | 70 | src/geo/wgs84.cppm |
| 71 | ) | 71 | ) |
diff --git a/server/cf.sh b/server/cf.sh index b8bd392..5c5ec3a 100755 --- a/server/cf.sh +++ b/server/cf.sh | |||
| @@ -1,3 +1,5 @@ | |||
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | 2 | ||
| 3 | clang-format -i src/*.cpp src/*.cppm | 3 | shopt -s globstar |
| 4 | |||
| 5 | clang-format -i src/**/*.cpp src/**/*.cppm | ||
diff --git a/server/src/api.cpp b/server/src/api.cpp index e35e1b3..41c49c8 100644 --- a/server/src/api.cpp +++ b/server/src/api.cpp | |||
| @@ -172,7 +172,8 @@ auto handler::process_gpx(gpx::file&& gpx_file) | |||
| 172 | { | 172 | { |
| 173 | l_.debug("Checking part [{}/{}]", ++i, segments.size()); | 173 | l_.debug("Checking part [{}/{}]", ++i, segments.size()); |
| 174 | 174 | ||
| 175 | auto part_zone_lss = geo::multizonal::zoned_linestring_seg_seq{part}; | 175 | auto part_zone_lss = |
| 176 | geo::utm::multizonal::split_linestring_across_zones(part); | ||
| 176 | // TODO: consider buffering with min_distance_ | 177 | // TODO: consider buffering with min_distance_ |
| 177 | auto part_box = geo::wgs84::box{}; | 178 | auto part_box = geo::wgs84::box{}; |
| 178 | bgeo::envelope(part, part_box); | 179 | bgeo::envelope(part, part_box); |
diff --git a/server/src/api.cppm b/server/src/api.cppm index 0bb0868..7166ad0 100644 --- a/server/src/api.cppm +++ b/server/src/api.cppm | |||
| @@ -8,7 +8,7 @@ export module routemon:api; | |||
| 8 | import std; | 8 | import std; |
| 9 | import :datex2; | 9 | import :datex2; |
| 10 | import :geo.wgs84; | 10 | import :geo.wgs84; |
| 11 | import :geo.multizonal; | 11 | import :geo.utm.multizonal; |
| 12 | import :gpx; | 12 | import :gpx; |
| 13 | import :log; | 13 | import :log; |
| 14 | import :time; | 14 | import :time; |
| @@ -81,9 +81,9 @@ class handler | |||
| 81 | constexpr static auto const min_distance_ = 5.0; | 81 | constexpr static auto const min_distance_ = 5.0; |
| 82 | 82 | ||
| 83 | using blse_index_value = std::shared_ptr<datex2::road_closure>; | 83 | using blse_index_value = std::shared_ptr<datex2::road_closure>; |
| 84 | using blse_index = geo::multizonal::linestring_rtree<blse_index_value>; | 84 | using blse_index = geo::utm::multizonal::linestring_rtree<blse_index_value>; |
| 85 | using bpe_index_value = std::shared_ptr<datex2::road_closure>; | 85 | using bpe_index_value = std::shared_ptr<datex2::road_closure>; |
| 86 | using bpe_index = geo::multizonal::point_rtree<bpe_index_value>; | 86 | using bpe_index = geo::utm::multizonal::point_rtree<bpe_index_value>; |
| 87 | 87 | ||
| 88 | log::logger l_; | 88 | log::logger l_; |
| 89 | datex2::situation_publication pub_; | 89 | datex2::situation_publication pub_; |
diff --git a/server/src/geo/multizonal.cppm b/server/src/geo/multizonal.cppm deleted file mode 100644 index 7930367..0000000 --- a/server/src/geo/multizonal.cppm +++ /dev/null | |||
| @@ -1,307 +0,0 @@ | |||
| 1 | module; | ||
| 2 | |||
| 3 | #include <boost/geometry.hpp> | ||
| 4 | #include <boost/geometry/srs/epsg.hpp> | ||
| 5 | #include <boost/geometry/srs/transformation.hpp> | ||
| 6 | |||
| 7 | export module routemon:geo.multizonal; | ||
| 8 | |||
| 9 | import std; | ||
| 10 | import :geo; | ||
| 11 | import :geo.utm; | ||
| 12 | import :geo.utm.zone_local; | ||
| 13 | import :geo.wgs84; | ||
| 14 | |||
| 15 | namespace routemon::geo::multizonal { | ||
| 16 | |||
| 17 | template<std::default_initializable T> | ||
| 18 | class multi_zone | ||
| 19 | { | ||
| 20 | std::array<T, utm::zone::max().as_index() + 1> zones_; | ||
| 21 | |||
| 22 | public: | ||
| 23 | auto operator[](utm::zone z) -> T& | ||
| 24 | { | ||
| 25 | return zones_[z.as_index()]; | ||
| 26 | } | ||
| 27 | |||
| 28 | auto operator[](utm::zone z) const -> T const& | ||
| 29 | { | ||
| 30 | return zones_[z.as_index()]; | ||
| 31 | } | ||
| 32 | }; | ||
| 33 | |||
| 34 | namespace wgs84 | ||
| 35 | { | ||
| 36 | |||
| 37 | // Transformations from WGS 84 (EPSG:4326) | ||
| 38 | using transform_from_t = bgeo::srs::transformation<bgeo::srs::static_epsg<4326>>; | ||
| 39 | |||
| 40 | class utm_transform : public transform_from_t | ||
| 41 | { | ||
| 42 | utm::zone to_zone_; | ||
| 43 | |||
| 44 | public: | ||
| 45 | explicit utm_transform(utm::zone to_zone) | ||
| 46 | : transform_from_t{{}, bgeo::srs::epsg{to_zone.wgs84_proj_epsg()}}, | ||
| 47 | to_zone_{to_zone} | ||
| 48 | { | ||
| 49 | } | ||
| 50 | |||
| 51 | utm_transform() | ||
| 52 | : utm_transform{utm::zone::min()} | ||
| 53 | {} | ||
| 54 | |||
| 55 | auto apply(utm::zonable_wgs84_point p) -> utm::zone_local::point | ||
| 56 | { | ||
| 57 | auto local_p = utm::zone_local::point{to_zone_, 0.0, 0.0}; | ||
| 58 | forward(p, local_p); | ||
| 59 | return local_p; | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | |||
| 63 | class utm_transforms : public multi_zone<utm_transform> | ||
| 64 | { | ||
| 65 | utm_transforms() | ||
| 66 | { | ||
| 67 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 68 | { | ||
| 69 | (*this)[z] = utm_transform{z}; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | public: | ||
| 74 | static utm_transforms const& instance() | ||
| 75 | { | ||
| 76 | static utm_transforms inst; | ||
| 77 | return inst; | ||
| 78 | } | ||
| 79 | }; | ||
| 80 | |||
| 81 | auto neighbor_utm_zone(utm::zonable_wgs84_point p) -> std::pair<utm::zone, double> | ||
| 82 | { | ||
| 83 | auto separating_meridian_lon = std::round(p.lon() / 6.0) * 6.0; | ||
| 84 | auto closest_zone_middle = separating_meridian_lon < p.lon() | ||
| 85 | ? separating_meridian_lon - 3.0 | ||
| 86 | : separating_meridian_lon + 3.0; | ||
| 87 | auto closest_zone = utm::zone::for_wgs84_point(utm::zonable_wgs84_point::from(geo::wgs84::normalized_point{geo::wgs84::from_lat_lon(p.lat(), closest_zone_middle)}).value()); | ||
| 88 | |||
| 89 | auto separating_meridian_ls = geo::wgs84::linestring{ | ||
| 90 | geo::wgs84::from_lat_lon(90.0, 0.0), | ||
| 91 | geo::wgs84::from_lat_lon(0.0, separating_meridian_lon), | ||
| 92 | geo::wgs84::from_lat_lon(-90.0, 0.0), | ||
| 93 | }; | ||
| 94 | auto closest_zone_dist = bgeo::distance(p, separating_meridian_ls, geo::wgs84::vincenty_strategy{}); | ||
| 95 | |||
| 96 | return std::make_pair(closest_zone, closest_zone_dist); | ||
| 97 | } | ||
| 98 | |||
| 99 | } // namespace wgs84 | ||
| 100 | |||
| 101 | struct multizone_linestring | ||
| 102 | { | ||
| 103 | multi_zone<std::vector<utm::zone_local::prim::linestring>> segments; | ||
| 104 | |||
| 105 | // TODO: consider taking an input range instead | ||
| 106 | explicit multizone_linestring(utm::zonable_wgs84_linestring const& ls) | ||
| 107 | { | ||
| 108 | auto to_utm = wgs84::utm_transforms::instance(); | ||
| 109 | auto working = multi_zone<utm::zone_local::prim::linestring>{}; | ||
| 110 | auto mprev_p_zone = std::optional<utm::zone>{}; | ||
| 111 | auto mprev_p_alt_zone = std::optional<utm::zone>{}; | ||
| 112 | |||
| 113 | auto push = [&](utm::zonable_wgs84_point p, utm::zone z) | ||
| 114 | { working[z].push_back(to_utm[z].apply(p)); }; | ||
| 115 | auto flush = [&](utm::zone z) | ||
| 116 | { | ||
| 117 | segments[z].push_back(std::move(working[z])); | ||
| 118 | working[z] = {}; | ||
| 119 | }; | ||
| 120 | |||
| 121 | for (auto const& p : ls) | ||
| 122 | { | ||
| 123 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 124 | auto mp_alt_zone = std::optional<utm::zone>{}; | ||
| 125 | if (auto [neighbor_zone, neighbor_zone_dist] = wgs84::neighbor_utm_zone(p); | ||
| 126 | neighbor_zone_dist < 30.0 /* m */) | ||
| 127 | mp_alt_zone = neighbor_zone; | ||
| 128 | assert(!mp_alt_zone || *mp_alt_zone != p_zone); | ||
| 129 | |||
| 130 | push(p, p_zone); | ||
| 131 | if (mp_alt_zone) | ||
| 132 | push(p, *mp_alt_zone); | ||
| 133 | if (mprev_p_zone && *mprev_p_zone != p_zone && mprev_p_zone != mp_alt_zone) | ||
| 134 | { | ||
| 135 | push(p, *mprev_p_zone); | ||
| 136 | flush(*mprev_p_zone); | ||
| 137 | } | ||
| 138 | if (mprev_p_alt_zone && *mprev_p_alt_zone != p_zone && mprev_p_alt_zone != mp_alt_zone) | ||
| 139 | { | ||
| 140 | push(p, *mprev_p_alt_zone); | ||
| 141 | flush(*mprev_p_alt_zone); | ||
| 142 | } | ||
| 143 | |||
| 144 | mprev_p_zone = p_zone; | ||
| 145 | mprev_p_alt_zone = mp_alt_zone; | ||
| 146 | } | ||
| 147 | |||
| 148 | if (mprev_p_zone && !working[*mprev_p_zone].empty()) | ||
| 149 | { | ||
| 150 | flush(*mprev_p_zone); | ||
| 151 | } | ||
| 152 | if (mprev_p_alt_zone && !working[*mprev_p_alt_zone].empty()) | ||
| 153 | { | ||
| 154 | flush(*mprev_p_alt_zone); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | }; | ||
| 158 | |||
| 159 | struct zoned_linestring_seg_seq | ||
| 160 | { | ||
| 161 | std::vector<utm::zone_local::linestring> segments; | ||
| 162 | |||
| 163 | explicit zoned_linestring_seg_seq(utm::zonable_wgs84_linestring ls) | ||
| 164 | { | ||
| 165 | auto to_utm = wgs84::utm_transforms::instance(); | ||
| 166 | auto mworking_seg = std::optional<utm::zone_local::linestring>{}; | ||
| 167 | |||
| 168 | for (auto const& p : ls) | ||
| 169 | { | ||
| 170 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 171 | if (mworking_seg && mworking_seg->zone != p_zone) | ||
| 172 | { | ||
| 173 | mworking_seg->push_back(to_utm[mworking_seg->zone].apply(p)); | ||
| 174 | segments.push_back(std::move(*mworking_seg)); | ||
| 175 | mworking_seg = std::nullopt; | ||
| 176 | } | ||
| 177 | if (!mworking_seg) | ||
| 178 | mworking_seg = utm::zone_local::linestring{p_zone}; | ||
| 179 | mworking_seg->push_back(to_utm[p_zone].apply(p)); | ||
| 180 | } | ||
| 181 | |||
| 182 | if (mworking_seg && !mworking_seg->empty()) | ||
| 183 | { | ||
| 184 | segments.push_back(std::move(*mworking_seg)); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | }; | ||
| 188 | |||
| 189 | template<class T> | ||
| 190 | class linestring_rtree | ||
| 191 | { | ||
| 192 | public: | ||
| 193 | using index_value = std::tuple<utm::zone_local::prim::box, utm::zone_local::prim::linestring, T>; | ||
| 194 | |||
| 195 | private: | ||
| 196 | multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>> local_rtrees_; | ||
| 197 | |||
| 198 | public: | ||
| 199 | template<std::convertible_to<T> U> | ||
| 200 | auto insert(utm::zonable_wgs84_linestring const& ls, U&& arg) -> void | ||
| 201 | { | ||
| 202 | auto zone_segments = multizone_linestring{ls}.segments; | ||
| 203 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 204 | { | ||
| 205 | for (auto ls : zone_segments[z]) | ||
| 206 | { | ||
| 207 | auto blse = | ||
| 208 | bgeo::return_buffer<utm::zone_local::prim::box>(bgeo::return_envelope<utm::zone_local::prim::box>(ls), 30.0 /* m */); | ||
| 209 | local_rtrees_[z].insert(index_value{blse, std::move(ls), std::forward<U>(arg)}); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | auto size() const -> std::size_t | ||
| 215 | { | ||
| 216 | auto total_size = 0uz; | ||
| 217 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 218 | total_size += local_rtrees_[z].size(); | ||
| 219 | return total_size; | ||
| 220 | } | ||
| 221 | |||
| 222 | // Note: the same T may be generated more than once! | ||
| 223 | auto intersection(zoned_linestring_seg_seq const& lss) const -> std::generator<T const&> | ||
| 224 | { | ||
| 225 | for (auto const& ls : lss.segments) | ||
| 226 | { | ||
| 227 | // auto ls_box = bgeo::return_envelope<utm::zone_local::prim::box>(ls); | ||
| 228 | for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls)); | ||
| 229 | it != local_rtrees_[ls.zone].qend(); it++) | ||
| 230 | { | ||
| 231 | using multi_linestring = bgeo::model::multi_linestring<utm::zone_local::prim::linestring>; | ||
| 232 | |||
| 233 | auto relevant_ls_parts = multi_linestring{}; | ||
| 234 | bgeo::intersection(std::get<0>(*it), static_cast<utm::zone_local::prim::linestring const&>(ls), relevant_ls_parts); | ||
| 235 | if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */) | ||
| 236 | { | ||
| 237 | co_yield std::get<2>(*it); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | }; | ||
| 243 | |||
| 244 | template<class T> | ||
| 245 | class point_rtree | ||
| 246 | { | ||
| 247 | public: | ||
| 248 | using index_value = std::tuple<utm::zone_local::prim::box, utm::zone_local::prim::point, T>; | ||
| 249 | |||
| 250 | private: | ||
| 251 | multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>> local_rtrees_; | ||
| 252 | |||
| 253 | public: | ||
| 254 | template<std::convertible_to<T> U> | ||
| 255 | auto insert(utm::zonable_wgs84_point const& p, U&& arg) -> void | ||
| 256 | { | ||
| 257 | auto to_utm = wgs84::utm_transforms::instance(); | ||
| 258 | |||
| 259 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 260 | auto mp_alt_zone = std::optional<utm::zone>{}; | ||
| 261 | if (auto [neighbor_zone, neighbor_zone_dist] = wgs84::neighbor_utm_zone(p); | ||
| 262 | neighbor_zone_dist < 30.0 /* m */) | ||
| 263 | mp_alt_zone = neighbor_zone; | ||
| 264 | |||
| 265 | auto insert = [&](utm::zone z) -> void | ||
| 266 | { | ||
| 267 | auto p_utm = to_utm[z].apply(p); | ||
| 268 | auto bpe = bgeo::return_buffer<utm::zone_local::prim::box>(bgeo::return_envelope<utm::zone_local::prim::box>(p_utm), 30.0 /* m */); | ||
| 269 | local_rtrees_[z].insert(index_value{bpe, p_utm, arg}); | ||
| 270 | }; | ||
| 271 | |||
| 272 | insert(p_zone); | ||
| 273 | if (mp_alt_zone) | ||
| 274 | insert(*mp_alt_zone); | ||
| 275 | } | ||
| 276 | |||
| 277 | auto size() const -> std::size_t | ||
| 278 | { | ||
| 279 | auto total_size = 0uz; | ||
| 280 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 281 | total_size += local_rtrees_[z].size(); | ||
| 282 | return total_size; | ||
| 283 | } | ||
| 284 | |||
| 285 | // Note: the same T may be generated more than once! | ||
| 286 | auto intersection(zoned_linestring_seg_seq const& lss) const -> std::generator<T const&> | ||
| 287 | { | ||
| 288 | for (auto const& ls : lss.segments) | ||
| 289 | { | ||
| 290 | // auto ls_box = bgeo::return_envelope<utm::zone_local::prim::box>(ls); | ||
| 291 | for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls)); | ||
| 292 | it != local_rtrees_[ls.zone].qend(); it++) | ||
| 293 | { | ||
| 294 | using multi_linestring = bgeo::model::multi_linestring<utm::zone_local::prim::linestring>; | ||
| 295 | |||
| 296 | auto relevant_ls_parts = multi_linestring{}; | ||
| 297 | bgeo::intersection(std::get<0>(*it), static_cast<utm::zone_local::prim::linestring const&>(ls), relevant_ls_parts); | ||
| 298 | if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */) | ||
| 299 | { | ||
| 300 | co_yield std::get<2>(*it); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | }; | ||
| 306 | |||
| 307 | } // namespace routemon::geo::multizonal | ||
diff --git a/server/src/geo/utm.cppm b/server/src/geo/utm.cppm index 83da222..c58ffb4 100644 --- a/server/src/geo/utm.cppm +++ b/server/src/geo/utm.cppm | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | module; | 1 | module; |
| 2 | 2 | ||
| 3 | #include <cassert> | ||
| 4 | #include <boost/geometry/geometries/geometries.hpp> | 3 | #include <boost/geometry/geometries/geometries.hpp> |
| 4 | #include <cassert> | ||
| 5 | 5 | ||
| 6 | export module routemon:geo.utm; | 6 | export module routemon:geo.utm; |
| 7 | 7 | ||
| @@ -9,46 +9,32 @@ import std; | |||
| 9 | import :geo; | 9 | import :geo; |
| 10 | import :geo.wgs84; | 10 | import :geo.wgs84; |
| 11 | 11 | ||
| 12 | namespace routemon::geo::utm | 12 | namespace routemon::geo::utm { |
| 13 | { | ||
| 14 | 13 | ||
| 15 | class zonable_wgs84_point | 14 | class zonable_wgs84_point |
| 16 | { | 15 | { |
| 17 | wgs84::normalized_point p_; | 16 | wgs84::normalized_point p_; |
| 18 | 17 | ||
| 19 | explicit zonable_wgs84_point(wgs84::normalized_point p) | 18 | explicit inline zonable_wgs84_point(wgs84::normalized_point p) : p_{p} {} |
| 20 | : p_{p} | ||
| 21 | {} | ||
| 22 | 19 | ||
| 23 | public: | 20 | public: |
| 24 | zonable_wgs84_point() | 21 | inline zonable_wgs84_point() : p_{} {} |
| 25 | : p_{} | ||
| 26 | { | ||
| 27 | } | ||
| 28 | 22 | ||
| 29 | static auto from(wgs84::normalized_point p) -> std::optional<zonable_wgs84_point> | 23 | inline static auto from(wgs84::normalized_point p) |
| 24 | -> std::optional<zonable_wgs84_point> | ||
| 30 | { | 25 | { |
| 31 | if (p.lat() < -80 || p.lat() > 84) | 26 | if (p.lat() < -80 || p.lat() > 84) |
| 32 | return std::nullopt; | 27 | return std::nullopt; |
| 33 | return zonable_wgs84_point{p}; | 28 | return zonable_wgs84_point{p}; |
| 34 | } | 29 | } |
| 35 | 30 | ||
| 36 | auto lat() const -> double | 31 | inline auto lat() const -> double { return p_.lat(); } |
| 37 | { | ||
| 38 | return p_.lat(); | ||
| 39 | } | ||
| 40 | 32 | ||
| 41 | auto lon() const -> double | 33 | inline auto lon() const -> double { return p_.lon(); } |
| 42 | { | ||
| 43 | return p_.lon(); | ||
| 44 | } | ||
| 45 | 34 | ||
| 46 | auto lon(double lon) -> void | 35 | inline auto lon(double lon) -> void { p_.lon(lon); } |
| 47 | { | ||
| 48 | p_.lon(lon); | ||
| 49 | } | ||
| 50 | 36 | ||
| 51 | auto lat(double lat) -> void | 37 | inline auto lat(double lat) -> void |
| 52 | { | 38 | { |
| 53 | if (lat < -80 || lat > 84) | 39 | if (lat < -80 || lat > 84) |
| 54 | throw std::range_error{"latitude out of range for UTM"}; | 40 | throw std::range_error{"latitude out of range for UTM"}; |
| @@ -56,48 +42,69 @@ public: | |||
| 56 | } | 42 | } |
| 57 | }; | 43 | }; |
| 58 | 44 | ||
| 59 | } | 45 | } // namespace routemon::geo::utm |
| 60 | 46 | ||
| 61 | namespace boost::geometry::traits { | 47 | namespace boost::geometry::traits { |
| 62 | 48 | ||
| 63 | namespace { | 49 | namespace { |
| 64 | 50 | ||
| 65 | using zonable_wgs84_point = routemon::geo::utm::zonable_wgs84_point; | 51 | using zonable_wgs84_point = routemon::geo::utm::zonable_wgs84_point; |
| 66 | 52 | ||
| 67 | } // namespace <anonymous> | 53 | } // namespace |
| 68 | 54 | ||
| 69 | template<> struct tag<zonable_wgs84_point> { using type = point_tag; }; | 55 | template <> |
| 70 | template<> struct dimension<zonable_wgs84_point> : boost::mpl::int_<2> {}; | 56 | struct tag<zonable_wgs84_point> |
| 71 | template<> struct coordinate_type<zonable_wgs84_point> { using type = double; }; | 57 | { |
| 72 | template<> struct coordinate_system<zonable_wgs84_point> { using type = routemon::geo::wgs84::cs; }; | 58 | using type = point_tag; |
| 59 | }; | ||
| 73 | 60 | ||
| 74 | template<std::size_t index> | 61 | template <> |
| 75 | struct access<zonable_wgs84_point, index> { | 62 | struct dimension<zonable_wgs84_point> : boost::mpl::int_<2> |
| 76 | static inline auto get(zonable_wgs84_point const& p) -> double | 63 | { |
| 77 | { | 64 | }; |
| 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 | 65 | ||
| 85 | static inline auto set(zonable_wgs84_point& p, double v) -> void | 66 | template <> |
| 86 | { | 67 | struct coordinate_type<zonable_wgs84_point> |
| 87 | if constexpr (index == 0) | 68 | { |
| 88 | p.lon(v); | 69 | using type = double; |
| 89 | else if constexpr (index == 1) | 70 | }; |
| 90 | p.lat(v); | ||
| 91 | else static_assert(false, "Out of range"); | ||
| 92 | } | ||
| 93 | }; | ||
| 94 | 71 | ||
| 95 | } // namespace boost::geometry::traits | 72 | template <> |
| 73 | struct coordinate_system<zonable_wgs84_point> | ||
| 74 | { | ||
| 75 | using type = routemon::geo::wgs84::cs; | ||
| 76 | }; | ||
| 96 | 77 | ||
| 97 | namespace routemon::geo::utm | 78 | template <std::size_t index> |
| 79 | struct access<zonable_wgs84_point, index> | ||
| 98 | { | 80 | { |
| 81 | static inline auto get(zonable_wgs84_point const& p) -> double | ||
| 82 | { | ||
| 83 | if constexpr (index == 0) | ||
| 84 | return p.lon(); | ||
| 85 | else if constexpr (index == 1) | ||
| 86 | return p.lat(); | ||
| 87 | else | ||
| 88 | static_assert(false, "Out of range"); | ||
| 89 | } | ||
| 90 | |||
| 91 | static inline auto set(zonable_wgs84_point& p, double v) -> void | ||
| 92 | { | ||
| 93 | if constexpr (index == 0) | ||
| 94 | p.lon(v); | ||
| 95 | else if constexpr (index == 1) | ||
| 96 | p.lat(v); | ||
| 97 | else | ||
| 98 | static_assert(false, "Out of range"); | ||
| 99 | } | ||
| 100 | }; | ||
| 101 | |||
| 102 | } // namespace boost::geometry::traits | ||
| 99 | 103 | ||
| 100 | using zonable_wgs84_linestring = bgeo::model::linestring<zonable_wgs84_point, std::vector>; | 104 | namespace routemon::geo::utm { |
| 105 | |||
| 106 | using zonable_wgs84_linestring = | ||
| 107 | bgeo::model::linestring<zonable_wgs84_point, std::vector>; | ||
| 101 | 108 | ||
| 102 | // In the sense of the common WGS84 subdivisions by simple northing | 109 | // In the sense of the common WGS84 subdivisions by simple northing |
| 103 | // and easting (so no Norway/Svalbard exceptions). | 110 | // and easting (so no Norway/Svalbard exceptions). |
| @@ -130,33 +137,30 @@ class zone | |||
| 130 | public: | 137 | public: |
| 131 | explicit constexpr zone(std::uint8_t zone_no, hemisphere h) | 138 | explicit constexpr zone(std::uint8_t zone_no, hemisphere h) |
| 132 | : zone_{from(zone_no, h)} | 139 | : zone_{from(zone_no, h)} |
| 133 | {} | 140 | { |
| 141 | } | ||
| 134 | 142 | ||
| 135 | auto hemisphere() const -> enum hemisphere | 143 | constexpr auto hemisphere() const -> enum hemisphere |
| 136 | { | 144 | { |
| 137 | return static_cast<enum hemisphere>(zone_ % 2); | 145 | return static_cast<enum hemisphere>(zone_ % 2); |
| 138 | } | 146 | } |
| 139 | 147 | ||
| 140 | // Return value in range [1, 60] | 148 | // Return value in range [1, 60] |
| 141 | auto zone_no() const -> std::uint8_t | 149 | constexpr auto zone_no() const -> std::uint8_t { return 1 + zone_ / 2; } |
| 142 | { | ||
| 143 | return 1 + zone_ / 2; | ||
| 144 | } | ||
| 145 | 150 | ||
| 146 | // Return value in range [min(), max()] | 151 | // Return value in range [min(), max()] |
| 147 | constexpr auto as_index() const -> std::uint8_t | 152 | constexpr auto as_index() const -> std::uint8_t { return zone_; } |
| 148 | { | ||
| 149 | return zone_; | ||
| 150 | } | ||
| 151 | 153 | ||
| 152 | static auto for_wgs84_point(zonable_wgs84_point p) noexcept -> zone | 154 | static auto for_wgs84_point(zonable_wgs84_point p) noexcept -> zone |
| 153 | { | 155 | { |
| 154 | auto zone_no = static_cast<std::uint8_t>(1 + (p.lon() + 180.0) / 6.0); | 156 | auto zone_no = static_cast<std::uint8_t>(1 + (p.lon() + 180.0) / 6.0); |
| 155 | auto northern = p.lat() >= 0.0; | 157 | auto northern = p.lat() >= 0.0; |
| 156 | return zone{zone_no, northern ? hemisphere::northern : hemisphere::southern}; | 158 | return zone{ |
| 159 | zone_no, northern ? hemisphere::northern : hemisphere::southern | ||
| 160 | }; | ||
| 157 | } | 161 | } |
| 158 | 162 | ||
| 159 | auto wgs84_proj_epsg() const -> int | 163 | constexpr auto wgs84_proj_epsg() const -> int |
| 160 | { | 164 | { |
| 161 | switch (hemisphere()) | 165 | switch (hemisphere()) |
| 162 | { | 166 | { |
| @@ -178,7 +182,7 @@ public: | |||
| 178 | return zone{60, hemisphere::southern}; | 182 | return zone{60, hemisphere::southern}; |
| 179 | } | 183 | } |
| 180 | 184 | ||
| 181 | auto next() const -> zone | 185 | constexpr auto next() const -> zone |
| 182 | { | 186 | { |
| 183 | assert(zone_ <= max().as_index()); | 187 | assert(zone_ <= max().as_index()); |
| 184 | if (zone_ == max().as_index()) | 188 | if (zone_ == max().as_index()) |
| @@ -188,7 +192,7 @@ public: | |||
| 188 | return copy; | 192 | return copy; |
| 189 | } | 193 | } |
| 190 | 194 | ||
| 191 | auto operator==(zone rhs) const -> bool | 195 | constexpr auto operator==(zone rhs) const -> bool |
| 192 | { | 196 | { |
| 193 | return zone_ == rhs.zone_; | 197 | return zone_ == rhs.zone_; |
| 194 | } | 198 | } |
diff --git a/server/src/geo/utm_multizonal.cppm b/server/src/geo/utm_multizonal.cppm new file mode 100644 index 0000000..540e309 --- /dev/null +++ b/server/src/geo/utm_multizonal.cppm | |||
| @@ -0,0 +1,343 @@ | |||
| 1 | module; | ||
| 2 | |||
| 3 | #include <boost/geometry.hpp> | ||
| 4 | #include <boost/geometry/srs/epsg.hpp> | ||
| 5 | #include <boost/geometry/srs/transformation.hpp> | ||
| 6 | |||
| 7 | export module routemon:geo.utm.multizonal; | ||
| 8 | |||
| 9 | import std; | ||
| 10 | import :geo; | ||
| 11 | import :geo.utm; | ||
| 12 | import :geo.utm.zone_local; | ||
| 13 | import :geo.wgs84; | ||
| 14 | |||
| 15 | namespace routemon::geo::utm::multizonal { | ||
| 16 | |||
| 17 | template <std::default_initializable T> | ||
| 18 | class multi_zone | ||
| 19 | { | ||
| 20 | std::array<T, zone::max().as_index() + 1> zones_; | ||
| 21 | |||
| 22 | public: | ||
| 23 | auto operator[](zone z) -> T& { return zones_[z.as_index()]; } | ||
| 24 | |||
| 25 | auto operator[](zone z) const -> T const& { return zones_[z.as_index()]; } | ||
| 26 | }; | ||
| 27 | |||
| 28 | // Transformations from WGS 84 (EPSG:4326) | ||
| 29 | using from_wgs84_transform_base = | ||
| 30 | bgeo::srs::transformation<bgeo::srs::static_epsg<4326>>; | ||
| 31 | |||
| 32 | class from_wgs84_transform : public from_wgs84_transform_base | ||
| 33 | { | ||
| 34 | zone to_zone_; | ||
| 35 | |||
| 36 | public: | ||
| 37 | explicit from_wgs84_transform(zone to_zone) | ||
| 38 | : from_wgs84_transform_base{{}, bgeo::srs::epsg{to_zone.wgs84_proj_epsg()}}, | ||
| 39 | to_zone_{to_zone} | ||
| 40 | { | ||
| 41 | } | ||
| 42 | |||
| 43 | from_wgs84_transform() : from_wgs84_transform{zone::min()} {} | ||
| 44 | |||
| 45 | auto apply(zonable_wgs84_point p) -> zone_local::point | ||
| 46 | { | ||
| 47 | auto local_p = zone_local::point{to_zone_, 0.0, 0.0}; | ||
| 48 | forward(p, local_p); | ||
| 49 | return local_p; | ||
| 50 | } | ||
| 51 | }; | ||
| 52 | |||
| 53 | class from_wgs84_transforms : public multi_zone<from_wgs84_transform> | ||
| 54 | { | ||
| 55 | from_wgs84_transforms() | ||
| 56 | { | ||
| 57 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 58 | { | ||
| 59 | (*this)[z] = from_wgs84_transform{z}; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | public: | ||
| 64 | static from_wgs84_transforms const& instance() | ||
| 65 | { | ||
| 66 | static from_wgs84_transforms inst; | ||
| 67 | return inst; | ||
| 68 | } | ||
| 69 | }; | ||
| 70 | |||
| 71 | auto neighbor_utm_zone(utm::zonable_wgs84_point p) | ||
| 72 | -> std::pair<utm::zone, double> | ||
| 73 | { | ||
| 74 | auto separating_meridian_lon = std::round(p.lon() / 6.0) * 6.0; | ||
| 75 | auto closest_zone_middle = separating_meridian_lon < p.lon() | ||
| 76 | ? separating_meridian_lon - 3.0 | ||
| 77 | : separating_meridian_lon + 3.0; | ||
| 78 | auto closest_zone = utm::zone::for_wgs84_point( | ||
| 79 | utm::zonable_wgs84_point::from( | ||
| 80 | geo::wgs84::normalized_point{geo::wgs84::from_lat_lon( | ||
| 81 | p.lat(), closest_zone_middle)}) | ||
| 82 | .value()); | ||
| 83 | |||
| 84 | auto separating_meridian_ls = geo::wgs84::linestring{ | ||
| 85 | geo::wgs84::from_lat_lon(90.0, 0.0), | ||
| 86 | geo::wgs84::from_lat_lon(0.0, separating_meridian_lon), | ||
| 87 | geo::wgs84::from_lat_lon(-90.0, 0.0), | ||
| 88 | }; | ||
| 89 | auto closest_zone_dist = bgeo::distance( | ||
| 90 | p, separating_meridian_ls, geo::wgs84::vincenty_strategy{}); | ||
| 91 | |||
| 92 | return std::make_pair(closest_zone, closest_zone_dist); | ||
| 93 | } | ||
| 94 | |||
| 95 | // Distribute a WGS 84 linestring over the UTM zones that it finds | ||
| 96 | // itself in or near to. | ||
| 97 | // | ||
| 98 | // Since it is not at all unthinkable that some road works will end up | ||
| 99 | // crossing different UTM zones, we need some machinery to work with | ||
| 100 | // such situations. Since we want to report all situations within a | ||
| 101 | // specified distance of the planned route, we must also consider the | ||
| 102 | // situation where this is indeed the case, but the situation is | ||
| 103 | // situated at the opposite side of a delineating UTM zone meridian | ||
| 104 | // w.r.t. the planned route. | ||
| 105 | // | ||
| 106 | // To solve this issue, we create segments of the input line string | ||
| 107 | // for multiple UTM zones when crossing zone boundaries or when very | ||
| 108 | // close to zone boundaries. | ||
| 109 | auto distribute_linestring_over_zones(utm::zonable_wgs84_linestring const& ls) | ||
| 110 | -> multi_zone<std::vector<utm::zone_local::prim::linestring>> | ||
| 111 | { | ||
| 112 | auto result = multi_zone<std::vector<utm::zone_local::prim::linestring>>{}; | ||
| 113 | |||
| 114 | auto to_utm = from_wgs84_transforms::instance(); | ||
| 115 | auto working = multi_zone<utm::zone_local::prim::linestring>{}; | ||
| 116 | auto mprev_p_zone = std::optional<utm::zone>{}; | ||
| 117 | auto mprev_p_alt_zone = std::optional<utm::zone>{}; | ||
| 118 | |||
| 119 | auto push = [&](utm::zonable_wgs84_point p, utm::zone z) | ||
| 120 | { working[z].push_back(to_utm[z].apply(p)); }; | ||
| 121 | auto flush = [&](utm::zone z) | ||
| 122 | { | ||
| 123 | result[z].push_back(std::move(working[z])); | ||
| 124 | working[z] = {}; | ||
| 125 | }; | ||
| 126 | |||
| 127 | for (auto const& p : ls) | ||
| 128 | { | ||
| 129 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 130 | auto mp_alt_zone = std::optional<utm::zone>{}; | ||
| 131 | if (auto [neighbor_zone, neighbor_zone_dist] = neighbor_utm_zone(p); | ||
| 132 | neighbor_zone_dist < 30.0 /* m */) | ||
| 133 | mp_alt_zone = neighbor_zone; | ||
| 134 | assert(!mp_alt_zone || *mp_alt_zone != p_zone); | ||
| 135 | |||
| 136 | push(p, p_zone); | ||
| 137 | if (mp_alt_zone) | ||
| 138 | push(p, *mp_alt_zone); | ||
| 139 | if (mprev_p_zone && *mprev_p_zone != p_zone && mprev_p_zone != mp_alt_zone) | ||
| 140 | { | ||
| 141 | push(p, *mprev_p_zone); | ||
| 142 | flush(*mprev_p_zone); | ||
| 143 | } | ||
| 144 | if (mprev_p_alt_zone && *mprev_p_alt_zone != p_zone | ||
| 145 | && mprev_p_alt_zone != mp_alt_zone) | ||
| 146 | { | ||
| 147 | push(p, *mprev_p_alt_zone); | ||
| 148 | flush(*mprev_p_alt_zone); | ||
| 149 | } | ||
| 150 | |||
| 151 | mprev_p_zone = p_zone; | ||
| 152 | mprev_p_alt_zone = mp_alt_zone; | ||
| 153 | } | ||
| 154 | |||
| 155 | if (mprev_p_zone && !working[*mprev_p_zone].empty()) | ||
| 156 | { | ||
| 157 | flush(*mprev_p_zone); | ||
| 158 | } | ||
| 159 | if (mprev_p_alt_zone && !working[*mprev_p_alt_zone].empty()) | ||
| 160 | { | ||
| 161 | flush(*mprev_p_alt_zone); | ||
| 162 | } | ||
| 163 | |||
| 164 | return result; | ||
| 165 | } | ||
| 166 | |||
| 167 | // Split a WGS 84 line string into a sequence of UTM-zone-local line | ||
| 168 | // strings, starting a new UTM-zone-local line string when the input | ||
| 169 | // line string crosses a UTM zone boundary. The line segment that | ||
| 170 | // crosses the zone boundary can be found in the UTM-zone-local line | ||
| 171 | // strings for both zones which its points are in (we assume that line | ||
| 172 | // segments will be short enough to not cause significant distortion | ||
| 173 | // here, so we do not put in the effort to clip at the | ||
| 174 | // zone-delineating meridian here). | ||
| 175 | auto split_linestring_across_zones(utm::zonable_wgs84_linestring ls) | ||
| 176 | -> std::vector<utm::zone_local::linestring> | ||
| 177 | { | ||
| 178 | auto splits = std::vector<utm::zone_local::linestring>{}; | ||
| 179 | |||
| 180 | auto to_utm = from_wgs84_transforms::instance(); | ||
| 181 | auto mworking_seg = std::optional<utm::zone_local::linestring>{}; | ||
| 182 | |||
| 183 | for (auto const& p : ls) | ||
| 184 | { | ||
| 185 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 186 | if (mworking_seg && mworking_seg->zone != p_zone) | ||
| 187 | { | ||
| 188 | mworking_seg->push_back(to_utm[mworking_seg->zone].apply(p)); | ||
| 189 | splits.push_back(std::move(*mworking_seg)); | ||
| 190 | mworking_seg = std::nullopt; | ||
| 191 | } | ||
| 192 | if (!mworking_seg) | ||
| 193 | mworking_seg = utm::zone_local::linestring{p_zone}; | ||
| 194 | mworking_seg->push_back(to_utm[p_zone].apply(p)); | ||
| 195 | } | ||
| 196 | |||
| 197 | if (mworking_seg && !mworking_seg->empty()) | ||
| 198 | { | ||
| 199 | splits.push_back(std::move(*mworking_seg)); | ||
| 200 | } | ||
| 201 | |||
| 202 | return splits; | ||
| 203 | } | ||
| 204 | |||
| 205 | template <class T> | ||
| 206 | class linestring_rtree | ||
| 207 | { | ||
| 208 | public: | ||
| 209 | using index_value = std:: | ||
| 210 | tuple<utm::zone_local::prim::box, utm::zone_local::prim::linestring, T>; | ||
| 211 | |||
| 212 | private: | ||
| 213 | multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>> | ||
| 214 | local_rtrees_; | ||
| 215 | |||
| 216 | public: | ||
| 217 | template <std::convertible_to<T> U> | ||
| 218 | auto insert(utm::zonable_wgs84_linestring const& ls, U&& arg) -> void | ||
| 219 | { | ||
| 220 | auto zone_segments = distribute_linestring_over_zones(ls); | ||
| 221 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 222 | { | ||
| 223 | for (auto ls : zone_segments[z]) | ||
| 224 | { | ||
| 225 | auto blse = bgeo::return_buffer<utm::zone_local::prim::box>( | ||
| 226 | bgeo::return_envelope<utm::zone_local::prim::box>(ls), | ||
| 227 | 30.0 /* m */); | ||
| 228 | local_rtrees_[z].insert( | ||
| 229 | index_value{blse, std::move(ls), std::forward<U>(arg)}); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | auto size() const -> std::size_t | ||
| 235 | { | ||
| 236 | auto total_size = 0uz; | ||
| 237 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 238 | total_size += local_rtrees_[z].size(); | ||
| 239 | return total_size; | ||
| 240 | } | ||
| 241 | |||
| 242 | // Note: the same T may be generated more than once! | ||
| 243 | auto intersection(std::vector<utm::zone_local::linestring> const& lss) const | ||
| 244 | -> std::generator<T const&> | ||
| 245 | { | ||
| 246 | for (auto const& ls : lss) | ||
| 247 | { | ||
| 248 | for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls)); | ||
| 249 | it != local_rtrees_[ls.zone].qend(); it++) | ||
| 250 | { | ||
| 251 | using multi_linestring = | ||
| 252 | bgeo::model::multi_linestring<utm::zone_local::prim::linestring>; | ||
| 253 | |||
| 254 | auto relevant_ls_parts = multi_linestring{}; | ||
| 255 | bgeo::intersection( | ||
| 256 | std::get<0>(*it), | ||
| 257 | static_cast<utm::zone_local::prim::linestring const&>(ls), | ||
| 258 | relevant_ls_parts); | ||
| 259 | // it is somewhat awkward and arbitrary that this measurement is | ||
| 260 | // performed here | ||
| 261 | if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */) | ||
| 262 | { | ||
| 263 | co_yield std::get<2>(*it); | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | }; | ||
| 269 | |||
| 270 | template <class T> | ||
| 271 | class point_rtree | ||
| 272 | { | ||
| 273 | public: | ||
| 274 | using index_value = | ||
| 275 | std::tuple<utm::zone_local::prim::box, utm::zone_local::prim::point, T>; | ||
| 276 | |||
| 277 | private: | ||
| 278 | multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>> | ||
| 279 | local_rtrees_; | ||
| 280 | |||
| 281 | public: | ||
| 282 | template <std::convertible_to<T> U> | ||
| 283 | auto insert(utm::zonable_wgs84_point const& p, U&& arg) -> void | ||
| 284 | { | ||
| 285 | auto to_utm = from_wgs84_transforms::instance(); | ||
| 286 | |||
| 287 | auto p_zone = utm::zone::for_wgs84_point(p); | ||
| 288 | auto mp_alt_zone = std::optional<utm::zone>{}; | ||
| 289 | if (auto [neighbor_zone, neighbor_zone_dist] = neighbor_utm_zone(p); | ||
| 290 | neighbor_zone_dist < 30.0 /* m */) | ||
| 291 | mp_alt_zone = neighbor_zone; | ||
| 292 | |||
| 293 | auto insert = [&](utm::zone z) -> void | ||
| 294 | { | ||
| 295 | auto p_utm = to_utm[z].apply(p); | ||
| 296 | auto bpe = bgeo::return_buffer<utm::zone_local::prim::box>( | ||
| 297 | bgeo::return_envelope<utm::zone_local::prim::box>(p_utm), | ||
| 298 | 30.0 /* m */); | ||
| 299 | local_rtrees_[z].insert(index_value{bpe, p_utm, arg}); | ||
| 300 | }; | ||
| 301 | |||
| 302 | insert(p_zone); | ||
| 303 | if (mp_alt_zone) | ||
| 304 | insert(*mp_alt_zone); | ||
| 305 | } | ||
| 306 | |||
| 307 | auto size() const -> std::size_t | ||
| 308 | { | ||
| 309 | auto total_size = 0uz; | ||
| 310 | for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next()) | ||
| 311 | total_size += local_rtrees_[z].size(); | ||
| 312 | return total_size; | ||
| 313 | } | ||
| 314 | |||
| 315 | // Note: the same T may be generated more than once! | ||
| 316 | auto intersection(std::vector<utm::zone_local::linestring> const& lss) const | ||
| 317 | -> std::generator<T const&> | ||
| 318 | { | ||
| 319 | for (auto const& ls : lss) | ||
| 320 | { | ||
| 321 | for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls)); | ||
| 322 | it != local_rtrees_[ls.zone].qend(); it++) | ||
| 323 | { | ||
| 324 | using multi_linestring = | ||
| 325 | bgeo::model::multi_linestring<utm::zone_local::prim::linestring>; | ||
| 326 | |||
| 327 | auto relevant_ls_parts = multi_linestring{}; | ||
| 328 | bgeo::intersection( | ||
| 329 | std::get<0>(*it), | ||
| 330 | static_cast<utm::zone_local::prim::linestring const&>(ls), | ||
| 331 | relevant_ls_parts); | ||
| 332 | // it is somewhat awkward and arbitrary that this measurement is | ||
| 333 | // performed here | ||
| 334 | if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */) | ||
| 335 | { | ||
| 336 | co_yield std::get<2>(*it); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | }; | ||
| 342 | |||
| 343 | } // namespace routemon::geo::utm::multizonal | ||
diff --git a/server/src/geo/utm_zone_local.cppm b/server/src/geo/utm_zone_local.cppm index 98a1344..b2147ad 100644 --- a/server/src/geo/utm_zone_local.cppm +++ b/server/src/geo/utm_zone_local.cppm | |||
| @@ -11,35 +11,32 @@ import :geo.utm; | |||
| 11 | // Coordinates that are local to some known UTM zone. | 11 | // Coordinates that are local to some known UTM zone. |
| 12 | namespace routemon::geo::utm::zone_local { | 12 | namespace routemon::geo::utm::zone_local { |
| 13 | 13 | ||
| 14 | using cs = bgeo::cs::cartesian; | 14 | using cs = bgeo::cs::cartesian; |
| 15 | 15 | ||
| 16 | namespace prim { | 16 | namespace prim { |
| 17 | 17 | ||
| 18 | using point = bgeo::model::d2::point_xy<double>; | 18 | using point = bgeo::model::d2::point_xy<double>; |
| 19 | using linestring = bgeo::model::linestring<point>; | 19 | using linestring = bgeo::model::linestring<point>; |
| 20 | using box = bgeo::model::box<point>; | 20 | using box = bgeo::model::box<point>; |
| 21 | 21 | ||
| 22 | } // namespace prim | 22 | } // namespace prim |
| 23 | 23 | ||
| 24 | struct point : public prim::point | 24 | struct point : public prim::point |
| 25 | { | 25 | { |
| 26 | zone zone; | 26 | zone zone; |
| 27 | |||
| 28 | explicit point(class zone zone, double x, double y) | ||
| 29 | : prim::point{x, y}, zone{zone} | ||
| 30 | { | ||
| 31 | } | ||
| 32 | }; | ||
| 33 | 27 | ||
| 34 | struct linestring : public prim::linestring | 28 | explicit inline point(class zone zone, double x, double y) |
| 29 | : prim::point{x, y}, zone{zone} | ||
| 35 | { | 30 | { |
| 36 | zone zone; | 31 | } |
| 32 | }; | ||
| 33 | |||
| 34 | struct linestring : public prim::linestring | ||
| 35 | { | ||
| 36 | zone zone; | ||
| 37 | 37 | ||
| 38 | explicit linestring(class zone zone) | 38 | explicit inline linestring(class zone zone) : zone{zone} {} |
| 39 | : zone{zone} | 39 | }; |
| 40 | { | ||
| 41 | } | ||
| 42 | }; | ||
| 43 | 40 | ||
| 44 | } // namespace routemon::geo::utm::zone_local | 41 | } // namespace routemon::geo::utm::zone_local |
| 45 | 42 | ||
| @@ -47,28 +44,46 @@ BOOST_GEOMETRY_REGISTER_LINESTRING(routemon::geo::utm::zone_local::linestring) | |||
| 47 | 44 | ||
| 48 | namespace boost::geometry::traits { | 45 | namespace boost::geometry::traits { |
| 49 | 46 | ||
| 50 | namespace { | 47 | namespace { |
| 51 | 48 | ||
| 52 | using zone_local_point = routemon::geo::utm::zone_local::point; | 49 | using zone_local_point = routemon::geo::utm::zone_local::point; |
| 53 | 50 | ||
| 54 | } // namespace <anonymous> | 51 | } // namespace |
| 55 | 52 | ||
| 56 | template<> struct tag<zone_local_point> { using type = point_tag; }; | 53 | template <> |
| 57 | template<> struct dimension<zone_local_point> : boost::mpl::int_<2> {}; | 54 | struct tag<zone_local_point> |
| 58 | template<> struct coordinate_type<zone_local_point> { using type = double; }; | 55 | { |
| 59 | template<> struct coordinate_system<zone_local_point> { using type = routemon::geo::utm::zone_local::cs; }; | 56 | using type = point_tag; |
| 57 | }; | ||
| 58 | template <> | ||
| 59 | struct dimension<zone_local_point> : boost::mpl::int_<2> | ||
| 60 | { | ||
| 61 | }; | ||
| 62 | template <> | ||
| 63 | struct coordinate_type<zone_local_point> | ||
| 64 | { | ||
| 65 | using type = double; | ||
| 66 | }; | ||
| 67 | template <> | ||
| 68 | struct coordinate_system<zone_local_point> | ||
| 69 | { | ||
| 70 | using type = routemon::geo::utm::zone_local::cs; | ||
| 71 | }; | ||
| 60 | 72 | ||
| 61 | template<std::size_t index> | 73 | template <std::size_t index> |
| 62 | struct access<zone_local_point, index> { | 74 | struct access<zone_local_point, index> |
| 63 | static inline auto get(zone_local_point const& p) -> double | 75 | { |
| 64 | { | 76 | static inline auto get(zone_local_point const& p) -> double |
| 65 | return bgeo::get<index>(static_cast<routemon::geo::utm::zone_local::prim::point const&>(p)); | 77 | { |
| 66 | } | 78 | return bgeo::get<index>( |
| 79 | static_cast<routemon::geo::utm::zone_local::prim::point const&>(p)); | ||
| 80 | } | ||
| 67 | 81 | ||
| 68 | static inline auto set(zone_local_point& p, double v) -> void | 82 | static inline auto set(zone_local_point& p, double v) -> void |
| 69 | { | 83 | { |
| 70 | return bgeo::set<index>(static_cast<routemon::geo::utm::zone_local::prim::point&>(p), v); | 84 | return bgeo::set<index>( |
| 71 | } | 85 | static_cast<routemon::geo::utm::zone_local::prim::point&>(p), v); |
| 72 | }; | 86 | } |
| 87 | }; | ||
| 73 | 88 | ||
| 74 | } // namespace boost::geometry::traits | 89 | } // namespace boost::geometry::traits |
diff --git a/server/src/geo/wgs84.cppm b/server/src/geo/wgs84.cppm index d574d39..b53a963 100644 --- a/server/src/geo/wgs84.cppm +++ b/server/src/geo/wgs84.cppm | |||
| @@ -13,25 +13,20 @@ using point = bgeo::model::point<double, 2, cs>; | |||
| 13 | // TODO: guarantee that linestring provides non-static member | 13 | // TODO: guarantee that linestring provides non-static member |
| 14 | // auto reserve(std::size_t) -> void | 14 | // auto reserve(std::size_t) -> void |
| 15 | // Perhaps better yet: guarantee that the backing container is a std::vector. | 15 | // Perhaps better yet: guarantee that the backing container is a std::vector. |
| 16 | using linestring = bgeo::model::linestring<point, std::vector /* the default */>; | 16 | using linestring = |
| 17 | bgeo::model::linestring<point, std::vector /* the default */>; | ||
| 17 | using box = bgeo::model::box<point>; | 18 | using box = bgeo::model::box<point>; |
| 18 | using stype = bgeo::srs::spheroid<double>; | 19 | using stype = bgeo::srs::spheroid<double>; |
| 19 | using vincenty_strategy = bgeo::strategy::distance::vincenty<stype>; | 20 | using vincenty_strategy = bgeo::strategy::distance::vincenty<stype>; |
| 20 | 21 | ||
| 21 | auto from_lat_lon(double lat, double lon) -> point | 22 | inline auto from_lat_lon(double lat, double lon) -> point |
| 22 | { | 23 | { |
| 23 | return point{lon, lat}; | 24 | return point{lon, lat}; |
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | auto lat(point p) -> double | 27 | inline auto lat(point p) -> double { return bgeo::get<1>(p); } |
| 27 | { | ||
| 28 | return bgeo::get<1>(p); | ||
| 29 | } | ||
| 30 | 28 | ||
| 31 | auto lon(point p) -> double | 29 | inline auto lon(point p) -> double { return bgeo::get<0>(p); } |
| 32 | { | ||
| 33 | return bgeo::get<0>(p); | ||
| 34 | } | ||
| 35 | 30 | ||
| 36 | // Point p with latitude in [-90, 90] and longitude in [-180, 180) | 31 | // Point p with latitude in [-90, 90] and longitude in [-180, 180) |
| 37 | auto normalize(point p) -> point | 32 | auto normalize(point p) -> point |
| @@ -44,7 +39,7 @@ auto normalize(point p) -> point | |||
| 44 | auto p_lat = nonneg_mod360(lat(p)) - 90.0; | 39 | auto p_lat = nonneg_mod360(lat(p)) - 90.0; |
| 45 | if (90.0 < p_lat) | 40 | if (90.0 < p_lat) |
| 46 | { | 41 | { |
| 47 | assert(p_lat < 270.0); // by nonneg_mod360 | 42 | assert(p_lat < 270.0); // by nonneg_mod360 |
| 48 | p_lon += 180.0; | 43 | p_lon += 180.0; |
| 49 | p_lat -= 180.0; | 44 | p_lat -= 180.0; |
| 50 | } | 45 | } |
| @@ -53,7 +48,7 @@ auto normalize(point p) -> point | |||
| 53 | return from_lat_lon(p_lat, p_lon); | 48 | return from_lat_lon(p_lat, p_lon); |
| 54 | } | 49 | } |
| 55 | 50 | ||
| 56 | auto is_normalized(point p) -> bool | 51 | inline auto is_normalized(point p) -> bool |
| 57 | { | 52 | { |
| 58 | return -90 <= lat(p) && lat(p) <= 90 && -180 <= lon(p) && lon(p) < 180; | 53 | return -90 <= lat(p) && lat(p) <= 90 && -180 <= lon(p) && lon(p) < 180; |
| 59 | } | 54 | } |
| @@ -63,32 +58,22 @@ class normalized_point | |||
| 63 | point p_; | 58 | point p_; |
| 64 | 59 | ||
| 65 | public: | 60 | public: |
| 66 | normalized_point(point p) | 61 | inline normalized_point(point p) : p_{is_normalized(p) ? p : normalize(p)} {} |
| 67 | : p_{is_normalized(p) ? p : normalize(p)} | ||
| 68 | {} | ||
| 69 | 62 | ||
| 70 | normalized_point() | 63 | inline normalized_point() : p_{} {} |
| 71 | : p_{} | ||
| 72 | {} | ||
| 73 | 64 | ||
| 74 | auto lat() const -> double | 65 | inline auto lat() const -> double { return bgeo::get<1>(p_); } |
| 75 | { | ||
| 76 | return bgeo::get<1>(p_); | ||
| 77 | } | ||
| 78 | 66 | ||
| 79 | auto lon() const -> double | 67 | inline auto lon() const -> double { return bgeo::get<0>(p_); } |
| 80 | { | ||
| 81 | return bgeo::get<0>(p_); | ||
| 82 | } | ||
| 83 | 68 | ||
| 84 | auto lat(double lat) -> void | 69 | inline auto lat(double lat) -> void |
| 85 | { | 70 | { |
| 86 | if (lat < -90 || lat > 90) | 71 | if (lat < -90 || lat > 90) |
| 87 | throw std::range_error{"latitude out of range"}; | 72 | throw std::range_error{"latitude out of range"}; |
| 88 | bgeo::set<1>(p_, lat); | 73 | bgeo::set<1>(p_, lat); |
| 89 | } | 74 | } |
| 90 | 75 | ||
| 91 | auto lon(double lon) -> void | 76 | inline auto lon(double lon) -> void |
| 92 | { | 77 | { |
| 93 | if (lon < -180 || lon > 180) | 78 | if (lon < -180 || lon > 180) |
| 94 | throw std::range_error{"longitude out of range"}; | 79 | throw std::range_error{"longitude out of range"}; |
| @@ -101,15 +86,9 @@ class normalized_linestring | |||
| 101 | std::vector<normalized_point> ls_; | 86 | std::vector<normalized_point> ls_; |
| 102 | 87 | ||
| 103 | public: | 88 | public: |
| 104 | auto empty() const -> bool | 89 | inline auto empty() const -> bool { return ls_.empty(); } |
| 105 | { | ||
| 106 | return ls_.empty(); | ||
| 107 | } | ||
| 108 | 90 | ||
| 109 | auto push_back(normalized_point p) -> void | 91 | inline auto push_back(normalized_point p) -> void { ls_.push_back(p); } |
| 110 | { | ||
| 111 | ls_.push_back(p); | ||
| 112 | } | ||
| 113 | }; | 92 | }; |
| 114 | 93 | ||
| 115 | } // namespace routemon::geo::wgs84 | 94 | } // namespace routemon::geo::wgs84 |