summaryrefslogtreecommitdiffstats
path: root/server/src/api.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/api.cpp')
-rw-r--r--server/src/api.cpp383
1 files changed, 226 insertions, 157 deletions
diff --git a/server/src/api.cpp b/server/src/api.cpp
index b3c8e31..a6eef23 100644
--- a/server/src/api.cpp
+++ b/server/src/api.cpp
@@ -17,190 +17,259 @@ import :trace;
17 17
18namespace { 18namespace {
19 19
20 namespace chrono = std::chrono; 20namespace chrono = std::chrono;
21 namespace json = boost::json; 21namespace json = boost::json;
22 namespace views = std::views; 22namespace views = std::views;
23 23
24} // namespace <anonymous> 24} // namespace
25 25
26namespace routemon::api { 26namespace routemon::api {
27 27
28 auto json_value_from_point(geo::point const& p) -> json::value { 28auto json_value_from_point(geo::point const& p) -> json::value
29 return json::array{bgeo::get<1>(p), bgeo::get<0>(p)}; 29{
30 } 30 return json::array{bgeo::get<1>(p), bgeo::get<0>(p)};
31 auto json_value_from_linestring(geo::linestring const& ls) -> json::value { 31}
32 json::array a;
33 for (auto const& p : ls)
34 a.push_back(json_value_from_point(p));
35 return a;
36 }
37 auto json_value_from_linestrings(std::vector<geo::linestring> const& lss) -> json::value {
38 json::array a;
39 for (auto const& ls : lss)
40 a.push_back(json_value_from_linestring(ls));
41 return a;
42 }
43 32
44 auto tag_invoke(json::value_from_tag, json::value& jv, relevant_road_closure const& clo) -> void { 33auto json_value_from_linestring(geo::linestring const& ls) -> json::value
45 jv = json::object{ 34{
35 json::array a;
36 for (auto const& p : ls)
37 a.push_back(json_value_from_point(p));
38 return a;
39}
40
41auto json_value_from_linestrings(std::vector<geo::linestring> const& lss)
42 -> json::value
43{
44 json::array a;
45 for (auto const& ls : lss)
46 a.push_back(json_value_from_linestring(ls));
47 return a;
48}
49
50auto tag_invoke(
51 json::value_from_tag, json::value& jv, relevant_road_closure const& clo)
52 -> void
53{
54 jv = json::object{
46 {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)}, 55 {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)},
47 }; 56 };
48 } 57}
49 auto tag_invoke(json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void { 58
50 jv = json::object{ 59auto tag_invoke(
51 {"id", json::value_from(sit.id)}, 60 json::value_from_tag, json::value& jv, relevant_situation const& sit)
52 {"location", sit.location ? json_value_from_point(*sit.location) : nullptr}, 61 -> void
53 {"comments", json::value_from(sit.comments)}, 62{
63 jv = json::object{
64 {"id", json::value_from(sit.id)},
65 {"location",
66 sit.location ? json_value_from_point(*sit.location) : nullptr},
67 {"comments", json::value_from(sit.comments)},
54 {"relevant_road_closures", json::value_from(sit.relevant_road_closures)}, 68 {"relevant_road_closures", json::value_from(sit.relevant_road_closures)},
55 }; 69 };
56 } 70}
57 auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) -> void { 71
58 jv = json::object{ 72auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
73 -> void
74{
75 jv = json::object{
59 {"points", json_value_from_linestring(seg.points)}, 76 {"points", json_value_from_linestring(seg.points)},
60 }; 77 };
61 } 78}
62 auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) -> void { 79
63 jv = json::object{ 80auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
81 -> void
82{
83 jv = json::object{
64 {"segments", json::value_from(track.segments)}, 84 {"segments", json::value_from(track.segments)},
65 }; 85 };
66 } 86}
67 auto tag_invoke(json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void { 87
68 jv = json::object{ 88auto tag_invoke(
69 {"tracks", json::value_from(res.tracks)}, 89 json::value_from_tag, json::value& jv, process_gpx_result const& res)
90 -> void
91{
92 jv = json::object{
93 {"tracks", json::value_from(res.tracks)},
70 {"relevant_situations", json::value_from(res.relevant_situations)}, 94 {"relevant_situations", json::value_from(res.relevant_situations)},
71 }; 95 };
72 } 96}
73 auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) -> void {
74 jv = json::object{
75 {"using_publication_of", std::format("{:%FT%TZ}", info.using_publication_of)},
76 };
77 }
78 97
79 handler::handler(log::logger const& l, datex2::situation_publication pub) 98auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info)
80 : l_{l.sub("handler")}, pub_{std::move(pub)} 99 -> void
100{
101 jv = json::object{
102 {"using_publication_of",
103 std::format("{:%FT%TZ}", info.using_publication_of)},
104 };
105}
106
107handler::handler(log::logger const& l, datex2::situation_publication pub)
108 : l_{l.sub("handler")}, pub_{std::move(pub)}
109{
110 l_.info("Building indices");
111 auto const before_build = chrono::steady_clock::now();
112 for (auto const& sit : pub_.situations)
81 { 113 {
82 l_.info("Building indices"); 114 for (auto const& rc : sit->road_closures)
83 auto const before_build = chrono::steady_clock::now(); 115 {
84 for (auto const& sit : pub_.situations) { 116 for (auto const& ls : rc->relevant_line_strings)
85 for (auto const& rc : sit->road_closures) { 117 {
86 for (auto const& ls : rc->relevant_line_strings) { 118 auto box = geo::box{};
87 auto box = geo::box{}; 119 bgeo::envelope(*ls, box);
88 bgeo::envelope(*ls, box); 120 lse_index_.insert(std::make_tuple(box, ls, rc));
89 lse_index_.insert(std::make_tuple(box, ls, rc)); 121 }
90 } 122 for (auto p : rc->relevant_points)
91 for (auto p : rc->relevant_points) { 123 {
92 p_index_.insert(std::make_pair(p, rc)); 124 p_index_.insert(std::make_pair(p, rc));
93 }
94 } 125 }
95 } 126 }
96 auto const after_build = chrono::steady_clock::now();
97 auto const dur_build = chrono::duration_cast<chrono::milliseconds>(after_build - before_build);
98 l_.info("Indices built in {}", dur_build);
99 l_.info("LSE index size: {}", lse_index_.size());
100 l_.info("Point index size: {}", p_index_.size());
101 } 127 }
128 auto const after_build = chrono::steady_clock::now();
129 auto const dur_build =
130 chrono::duration_cast<chrono::milliseconds>(after_build - before_build);
131 l_.info("Indices built in {}", dur_build);
132 l_.info("LSE index size: {}", lse_index_.size());
133 l_.info("Point index size: {}", p_index_.size());
134}
102 135
103 auto handler::process_gpx(gpx::file&& gpx_file) -> std::optional<process_gpx_result> { 136auto handler::process_gpx(gpx::file&& gpx_file)
104 auto const now = chrono::utc_clock::now(); 137 -> std::optional<process_gpx_result>
105 auto const relevant = std::initializer_list<time::period>{time::period{now - chrono::days(7), now + chrono::days(7)}}; 138{
106 auto const check_periods = time::period_seq{relevant.begin(), relevant.end()}; 139 auto const now = chrono::utc_clock::now();
140 auto const relevant = std::initializer_list<time::period>{
141 time::period{now - chrono::days(7), now + chrono::days(7)}
142 };
143 auto const check_periods = time::period_seq{relevant.begin(), relevant.end()};
107 144
108 auto splits_with_overlap_segments = std::vector<geo::linestring>{}; 145 auto splits_with_overlap_segments = std::vector<geo::linestring>{};
109 for (auto const& track : gpx_file.tracks) 146 for (auto const& track : gpx_file.tracks)
110 for (auto const& seg : track.segments) 147 for (auto const& seg : track.segments)
111 geo::split_linestring_with_overlap_segments(seg.waypoints, 5000 /* meters max total dist until a new split is forced */, 148 geo::split_linestring_with_overlap_segments(
112 splits_with_overlap_segments); 149 seg.waypoints,
113 auto const before_query = chrono::steady_clock::now(); 150 5000 /* meters max total dist until a new split is forced */,
151 splits_with_overlap_segments);
152 auto const before_query = chrono::steady_clock::now();
114 153
115 l_.debug("Querying for relevant situations"); 154 l_.debug("Querying for relevant situations");
116 auto relevant_road_closures = std::unordered_set<std::shared_ptr<datex2::road_closure>>{}; 155 auto relevant_road_closures =
117 auto ls_checked = 0uz; 156 std::unordered_set<std::shared_ptr<datex2::road_closure>>{};
118 auto p_checked = 0uz; 157 auto ls_checked = 0uz;
119 auto i = 0; 158 auto p_checked = 0uz;
120 for (geo::linestring const& part : splits_with_overlap_segments) { 159 auto i = 0;
121 l_.debug("Checking part [{}/{}]", ++i, splits_with_overlap_segments.size()); 160 for (geo::linestring const& part : splits_with_overlap_segments)
161 {
162 l_.debug("Checking part [{}/{}]", ++i, splits_with_overlap_segments.size());
122 163
123 auto part_box = geo::box{}; 164 auto part_box = geo::box{};
124 bgeo::envelope(part, part_box); 165 bgeo::envelope(part, part_box);
125 166
126 for (auto it = lse_index_.qbegin(bgeo::index::intersects(part_box)); it != lse_index_.qend(); it++) { 167 for (auto it = lse_index_.qbegin(bgeo::index::intersects(part_box));
127 // Cannot use structured bindings here, as boost::geometry::get interferes with ADL. 168 it != lse_index_.qend(); it++)
128 // It is a candidate as the namespace boost::geometry is part of the associated namespace set, 169 {
129 // which happens because geo::linestring ≡ boost::geometry::model::linestring<geo::point> is part 170 // Cannot use structured bindings here, as boost::geometry::get
130 // of the whole tuple type (lse_index_value) that is the value_type of the iterator. 171 // interferes with ADL. It is a candidate as the namespace
131 std::shared_ptr<geo::linestring> const& ls = std::get<1>(*it); 172 // boost::geometry is part of the associated namespace set, which
132 std::shared_ptr<datex2::road_closure> const& rc = std::get<2>(*it); 173 // happens because geo::linestring ≡
133 if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) 174 // boost::geometry::model::linestring<geo::point> is part of the
134 continue; 175 // whole tuple type (lse_index_value) that is the value_type of the
135 if (bgeo::distance(*ls, part, geo::vincenty_strategy{}) < 5.0) 176 // iterator.
136 relevant_road_closures.emplace(rc); 177 std::shared_ptr<geo::linestring> const& ls = std::get<1>(*it);
137 ls_checked++; 178 std::shared_ptr<datex2::road_closure> const& rc = std::get<2>(*it);
138 } 179 if (rc->validity
139 for (auto it = p_index_.qbegin(bgeo::index::intersects(part_box)); it != p_index_.qend(); it++) { 180 && rc->validity->intersect(check_periods).periods().empty())
140 // Cannot use structured bindings here for the same reason as above. 181 continue;
141 geo::point const& p = std::get<0>(*it); 182 if (bgeo::distance(*ls, part, geo::vincenty_strategy{}) < 5.0)
142 std::shared_ptr<datex2::road_closure> const& rc = std::get<1>(*it); 183 relevant_road_closures.emplace(rc);
143 if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) 184 ls_checked++;
144 continue; 185 }
145 if (bgeo::distance(p, part, geo::vincenty_strategy{}) < 5.0) 186 for (auto it = p_index_.qbegin(bgeo::index::intersects(part_box));
146 relevant_road_closures.emplace(rc); 187 it != p_index_.qend(); it++)
147 p_checked++; 188 {
148 } 189 // Cannot use structured bindings here for the same reason as above.
190 geo::point const& p = std::get<0>(*it);
191 std::shared_ptr<datex2::road_closure> const& rc = std::get<1>(*it);
192 if (rc->validity
193 && rc->validity->intersect(check_periods).periods().empty())
194 continue;
195 if (bgeo::distance(p, part, geo::vincenty_strategy{}) < 5.0)
196 relevant_road_closures.emplace(rc);
197 p_checked++;
149 } 198 }
199 }
150 200
151 auto const after_query = chrono::steady_clock::now(); 201 auto const after_query = chrono::steady_clock::now();
152 l_.debug("Done (checked {} line string(s) and {} point(s)) in {}", 202 l_.debug(
153 ls_checked, p_checked, chrono::duration_cast<chrono::milliseconds>(after_query - before_query)); 203 "Done (checked {} line string(s) and {} point(s)) in {}", ls_checked,
204 p_checked,
205 chrono::duration_cast<chrono::milliseconds>(after_query - before_query));
154 206
155 auto relevant_situations = std::unordered_set<std::shared_ptr<datex2::situation>>{}; 207 auto relevant_situations =
156 for (auto const& rc : relevant_road_closures) 208 std::unordered_set<std::shared_ptr<datex2::situation>>{};
157 relevant_situations.emplace(rc->parent); 209 for (auto const& rc : relevant_road_closures)
210 relevant_situations.emplace(rc->parent);
158 211
159 l_.debug("Identified {} relevant road closure(s), part of {} unique situation(s)", 212 l_.debug(
160 relevant_road_closures.size(), relevant_situations.size()); 213 "Identified {} relevant road closure(s), part of {} unique "
161 for (auto const& sit : relevant_situations) 214 "situation(s)",
162 l_.debug("Relevant situation: {}", sit->id); 215 relevant_road_closures.size(), relevant_situations.size());
216 for (auto const& sit : relevant_situations)
217 l_.debug("Relevant situation: {}", sit->id);
163 218
164 return process_gpx_result{ 219 return process_gpx_result{
165 .tracks = gpx_file.tracks 220 .tracks = gpx_file.tracks
166 | views::transform([](auto const& trk) -> track { 221 | views::transform(
167 return { 222 [](auto const& trk) -> track
168 .segments = trk.segments 223 {
169 | views::transform([](auto const& seg) -> track_segment { 224 return {
170 return {.points = seg.waypoints}; 225 .segments =
171 }) 226 trk.segments
172 | std::ranges::to<std::vector<track_segment>>(), 227 | views::transform(
173 }; 228 [](auto const& seg) -> track_segment
174 }) 229 { return {.points = seg.waypoints}; })
175 | std::ranges::to<std::vector<track>>(), 230 | std::ranges::to<std::vector<track_segment>>(),
176 .relevant_situations = relevant_situations 231 };
177 | views::transform([&](std::shared_ptr<datex2::situation> sit) -> relevant_situation { 232 })
178 return { 233 | std::ranges::to<std::vector<track>>(),
179 .id = sit->id, 234 .relevant_situations =
180 .location = sit->location, 235 relevant_situations
181 .comments = sit->comments, 236 | views::transform(
182 .relevant_road_closures = relevant_road_closures 237 [&](std::shared_ptr<datex2::situation> sit) -> relevant_situation
183 | views::filter([&](std::shared_ptr<datex2::road_closure> const& rc) -> bool { 238 {
184 return std::shared_ptr{rc->parent} == sit; 239 return {
185 }) 240 .id = sit->id,
186 | views::transform([](std::shared_ptr<datex2::road_closure> const& rc) -> relevant_road_closure { 241 .location = sit->location,
187 return { 242 .comments = sit->comments,
188 .relevant_lss = rc->relevant_line_strings 243 .relevant_road_closures =
189 | views::transform([](auto const& lsp) -> geo::linestring { 244 relevant_road_closures
190 return *lsp; 245 | views::filter(
246 [&](std::shared_ptr<datex2::road_closure> const& rc)
247 -> bool
248 { return std::shared_ptr{rc->parent} == sit; })
249 | views::transform(
250 [](std::shared_ptr<datex2::road_closure> const& rc)
251 -> relevant_road_closure
252 {
253 return {
254 .relevant_lss =
255 rc->relevant_line_strings
256 | views::transform(
257 [](auto const& lsp) -> geo::linestring
258 { return *lsp; })
259 | std::ranges::to<
260 std::vector<geo::linestring>>(),
261 };
262 })
263 | std::ranges::to<std::vector<relevant_road_closure>>(),
264 };
191 }) 265 })
192 | std::ranges::to<std::vector<geo::linestring>>(), 266 | std::ranges::to<std::vector<relevant_situation>>(),
193 }; 267 };
194 }) 268}
195 | std::ranges::to<std::vector<relevant_road_closure>>(),
196 };
197 })
198 | std::ranges::to<std::vector<relevant_situation>>(),
199 };
200 }
201 269
202 auto handler::sysinfo() -> struct sysinfo { 270auto handler::sysinfo() -> struct sysinfo
203 return {.using_publication_of = pub_.publication_time}; 271{
204 } 272 return {.using_publication_of = pub_.publication_time};
273}
205 274
206} // namespace routemon::api 275} // namespace routemon::api