1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
module;
#include <boost/geometry.hpp>
#include <boost/geometry/srs/epsg.hpp>
#include <boost/geometry/srs/transformation.hpp>
export module routemon:geo.utm.multizonal;
import std;
import :geo;
import :geo.utm;
import :geo.utm.zone_local;
import :geo.wgs84;
namespace routemon::geo::utm::multizonal {
template <std::default_initializable T>
class multi_zone
{
std::array<T, zone::max().as_index() + 1> zones_;
public:
auto operator[](zone z) -> T& { return zones_[z.as_index()]; }
auto operator[](zone z) const -> T const& { return zones_[z.as_index()]; }
};
// Transformations from WGS 84 (EPSG:4326)
using from_wgs84_transform_base =
bgeo::srs::transformation<bgeo::srs::static_epsg<4326>>;
class from_wgs84_transform : public from_wgs84_transform_base
{
zone to_zone_;
public:
explicit from_wgs84_transform(zone to_zone)
: from_wgs84_transform_base{{}, bgeo::srs::epsg{to_zone.wgs84_proj_epsg()}},
to_zone_{to_zone}
{
}
from_wgs84_transform() : from_wgs84_transform{zone::min()} {}
auto apply(zonable_wgs84_point p) -> zone_local::point
{
auto local_p = zone_local::point{to_zone_, 0.0, 0.0};
forward(p, local_p);
return local_p;
}
};
class from_wgs84_transforms : public multi_zone<from_wgs84_transform>
{
from_wgs84_transforms()
{
for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next())
{
(*this)[z] = from_wgs84_transform{z};
}
}
public:
static from_wgs84_transforms const& instance()
{
static from_wgs84_transforms inst;
return inst;
}
};
auto neighbor_utm_zone(utm::zonable_wgs84_point p)
-> std::pair<utm::zone, double>
{
auto separating_meridian_lon = std::round(p.lon() / 6.0) * 6.0;
auto closest_zone_middle = separating_meridian_lon < p.lon()
? separating_meridian_lon - 3.0
: separating_meridian_lon + 3.0;
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());
auto separating_meridian_ls = geo::wgs84::linestring{
geo::wgs84::from_lat_lon(90.0, 0.0),
geo::wgs84::from_lat_lon(0.0, separating_meridian_lon),
geo::wgs84::from_lat_lon(-90.0, 0.0),
};
auto closest_zone_dist = bgeo::distance(
p, separating_meridian_ls, geo::wgs84::vincenty_strategy{});
return std::make_pair(closest_zone, closest_zone_dist);
}
// Distribute a WGS 84 linestring over the UTM zones that it finds
// itself in or near to.
//
// Since it is not at all unthinkable that some road works will end up
// crossing different UTM zones, we need some machinery to work with
// such situations. Since we want to report all situations within a
// specified distance of the planned route, we must also consider the
// situation where this is indeed the case, but the situation is
// situated at the opposite side of a delineating UTM zone meridian
// w.r.t. the planned route.
//
// To solve this issue, we create segments of the input line string
// for multiple UTM zones when crossing zone boundaries or when very
// close to zone boundaries.
auto distribute_linestring_over_zones(utm::zonable_wgs84_linestring const& ls)
-> multi_zone<std::vector<utm::zone_local::prim::linestring>>
{
auto result = multi_zone<std::vector<utm::zone_local::prim::linestring>>{};
auto to_utm = from_wgs84_transforms::instance();
auto working = multi_zone<utm::zone_local::prim::linestring>{};
auto mprev_p_zone = std::optional<utm::zone>{};
auto mprev_p_alt_zone = std::optional<utm::zone>{};
auto push = [&](utm::zonable_wgs84_point p, utm::zone z)
{ working[z].push_back(to_utm[z].apply(p)); };
auto flush = [&](utm::zone z)
{
result[z].push_back(std::move(working[z]));
working[z] = {};
};
for (auto const& p : ls)
{
auto p_zone = utm::zone::for_wgs84_point(p);
auto mp_alt_zone = std::optional<utm::zone>{};
if (auto [neighbor_zone, neighbor_zone_dist] = neighbor_utm_zone(p);
neighbor_zone_dist < 30.0 /* m */)
mp_alt_zone = neighbor_zone;
assert(!mp_alt_zone || *mp_alt_zone != p_zone);
push(p, p_zone);
if (mp_alt_zone)
push(p, *mp_alt_zone);
if (mprev_p_zone && *mprev_p_zone != p_zone && mprev_p_zone != mp_alt_zone)
{
push(p, *mprev_p_zone);
flush(*mprev_p_zone);
}
if (mprev_p_alt_zone && *mprev_p_alt_zone != p_zone
&& mprev_p_alt_zone != mp_alt_zone)
{
push(p, *mprev_p_alt_zone);
flush(*mprev_p_alt_zone);
}
mprev_p_zone = p_zone;
mprev_p_alt_zone = mp_alt_zone;
}
if (mprev_p_zone && !working[*mprev_p_zone].empty())
{
flush(*mprev_p_zone);
}
if (mprev_p_alt_zone && !working[*mprev_p_alt_zone].empty())
{
flush(*mprev_p_alt_zone);
}
return result;
}
// Split a WGS 84 line string into a sequence of UTM-zone-local line
// strings, starting a new UTM-zone-local line string when the input
// line string crosses a UTM zone boundary. The line segment that
// crosses the zone boundary can be found in the UTM-zone-local line
// strings for both zones which its points are in (we assume that line
// segments will be short enough to not cause significant distortion
// here, so we do not put in the effort to clip at the
// zone-delineating meridian here).
auto split_linestring_across_zones(utm::zonable_wgs84_linestring ls)
-> std::vector<utm::zone_local::linestring>
{
auto splits = std::vector<utm::zone_local::linestring>{};
auto to_utm = from_wgs84_transforms::instance();
auto mworking_seg = std::optional<utm::zone_local::linestring>{};
for (auto const& p : ls)
{
auto p_zone = utm::zone::for_wgs84_point(p);
if (mworking_seg && mworking_seg->zone != p_zone)
{
mworking_seg->push_back(to_utm[mworking_seg->zone].apply(p));
splits.push_back(std::move(*mworking_seg));
mworking_seg = std::nullopt;
}
if (!mworking_seg)
mworking_seg = utm::zone_local::linestring{p_zone};
mworking_seg->push_back(to_utm[p_zone].apply(p));
}
if (mworking_seg && !mworking_seg->empty())
{
splits.push_back(std::move(*mworking_seg));
}
return splits;
}
template <class T>
class linestring_rtree
{
public:
using index_value = std::
tuple<utm::zone_local::prim::box, utm::zone_local::prim::linestring, T>;
private:
multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>>
local_rtrees_;
public:
template <std::convertible_to<T> U>
auto insert(utm::zonable_wgs84_linestring const& ls, U&& arg) -> void
{
auto zone_segments = distribute_linestring_over_zones(ls);
for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next())
{
for (auto ls : zone_segments[z])
{
auto blse = bgeo::return_buffer<utm::zone_local::prim::box>(
bgeo::return_envelope<utm::zone_local::prim::box>(ls),
30.0 /* m */);
local_rtrees_[z].insert(
index_value{blse, std::move(ls), std::forward<U>(arg)});
}
}
}
auto size() const -> std::size_t
{
auto total_size = 0uz;
for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next())
total_size += local_rtrees_[z].size();
return total_size;
}
// Note: the same T may be generated more than once!
auto intersection(std::vector<utm::zone_local::linestring> const& lss) const
-> std::generator<T const&>
{
for (auto const& ls : lss)
{
for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls));
it != local_rtrees_[ls.zone].qend(); it++)
{
using multi_linestring =
bgeo::model::multi_linestring<utm::zone_local::prim::linestring>;
auto relevant_ls_parts = multi_linestring{};
bgeo::intersection(
std::get<0>(*it),
static_cast<utm::zone_local::prim::linestring const&>(ls),
relevant_ls_parts);
// it is somewhat awkward and arbitrary that this measurement is
// performed here
if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */)
{
co_yield std::get<2>(*it);
}
}
}
}
};
template <class T>
class point_rtree
{
public:
using index_value =
std::tuple<utm::zone_local::prim::box, utm::zone_local::prim::point, T>;
private:
multi_zone<bgeo::index::rtree<index_value, bgeo::index::quadratic<16>>>
local_rtrees_;
public:
template <std::convertible_to<T> U>
auto insert(utm::zonable_wgs84_point const& p, U&& arg) -> void
{
auto to_utm = from_wgs84_transforms::instance();
auto p_zone = utm::zone::for_wgs84_point(p);
auto mp_alt_zone = std::optional<utm::zone>{};
if (auto [neighbor_zone, neighbor_zone_dist] = neighbor_utm_zone(p);
neighbor_zone_dist < 30.0 /* m */)
mp_alt_zone = neighbor_zone;
auto insert = [&](utm::zone z) -> void
{
auto p_utm = to_utm[z].apply(p);
auto bpe = bgeo::return_buffer<utm::zone_local::prim::box>(
bgeo::return_envelope<utm::zone_local::prim::box>(p_utm),
30.0 /* m */);
local_rtrees_[z].insert(index_value{bpe, p_utm, arg});
};
insert(p_zone);
if (mp_alt_zone)
insert(*mp_alt_zone);
}
auto size() const -> std::size_t
{
auto total_size = 0uz;
for (auto z = utm::zone::min(); z != utm::zone::max(); z = z.next())
total_size += local_rtrees_[z].size();
return total_size;
}
// Note: the same T may be generated more than once!
auto intersection(std::vector<utm::zone_local::linestring> const& lss) const
-> std::generator<T const&>
{
for (auto const& ls : lss)
{
for (auto it = local_rtrees_[ls.zone].qbegin(bgeo::index::intersects(ls));
it != local_rtrees_[ls.zone].qend(); it++)
{
using multi_linestring =
bgeo::model::multi_linestring<utm::zone_local::prim::linestring>;
auto relevant_ls_parts = multi_linestring{};
bgeo::intersection(
std::get<0>(*it),
static_cast<utm::zone_local::prim::linestring const&>(ls),
relevant_ls_parts);
// it is somewhat awkward and arbitrary that this measurement is
// performed here
if (bgeo::distance(relevant_ls_parts, std::get<1>(*it)) < 30.0 /* m */)
{
co_yield std::get<2>(*it);
}
}
}
}
};
} // namespace routemon::geo::utm::multizonal
|