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.cpp206
1 files changed, 206 insertions, 0 deletions
diff --git a/server/src/api.cpp b/server/src/api.cpp
new file mode 100644
index 0000000..b3c8e31
--- /dev/null
+++ b/server/src/api.cpp
@@ -0,0 +1,206 @@
1module;
2
3#include <boost/geometry.hpp>
4#include <boost/json.hpp>
5
6module routemon:api$impl;
7
8import std;
9import :api;
10import :datex2;
11import :geo;
12import :gpx;
13import :log;
14import :req_ctx;
15import :time;
16import :trace;
17
18namespace {
19
20 namespace chrono = std::chrono;
21 namespace json = boost::json;
22 namespace views = std::views;
23
24} // namespace <anonymous>
25
26namespace routemon::api {
27
28 auto json_value_from_point(geo::point const& p) -> json::value {
29 return json::array{bgeo::get<1>(p), bgeo::get<0>(p)};
30 }
31 auto json_value_from_linestring(geo::linestring const& ls) -> json::value {
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
44 auto tag_invoke(json::value_from_tag, json::value& jv, relevant_road_closure const& clo) -> void {
45 jv = json::object{
46 {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)},
47 };
48 }
49 auto tag_invoke(json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void {
50 jv = json::object{
51 {"id", json::value_from(sit.id)},
52 {"location", sit.location ? json_value_from_point(*sit.location) : nullptr},
53 {"comments", json::value_from(sit.comments)},
54 {"relevant_road_closures", json::value_from(sit.relevant_road_closures)},
55 };
56 }
57 auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) -> void {
58 jv = json::object{
59 {"points", json_value_from_linestring(seg.points)},
60 };
61 }
62 auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) -> void {
63 jv = json::object{
64 {"segments", json::value_from(track.segments)},
65 };
66 }
67 auto tag_invoke(json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void {
68 jv = json::object{
69 {"tracks", json::value_from(res.tracks)},
70 {"relevant_situations", json::value_from(res.relevant_situations)},
71 };
72 }
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
79 handler::handler(log::logger const& l, datex2::situation_publication pub)
80 : l_{l.sub("handler")}, pub_{std::move(pub)}
81 {
82 l_.info("Building indices");
83 auto const before_build = chrono::steady_clock::now();
84 for (auto const& sit : pub_.situations) {
85 for (auto const& rc : sit->road_closures) {
86 for (auto const& ls : rc->relevant_line_strings) {
87 auto box = geo::box{};
88 bgeo::envelope(*ls, box);
89 lse_index_.insert(std::make_tuple(box, ls, rc));
90 }
91 for (auto p : rc->relevant_points) {
92 p_index_.insert(std::make_pair(p, rc));
93 }
94 }
95 }
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 }
102
103 auto handler::process_gpx(gpx::file&& gpx_file) -> std::optional<process_gpx_result> {
104 auto const now = chrono::utc_clock::now();
105 auto const relevant = std::initializer_list<time::period>{time::period{now - chrono::days(7), now + chrono::days(7)}};
106 auto const check_periods = time::period_seq{relevant.begin(), relevant.end()};
107
108 auto splits_with_overlap_segments = std::vector<geo::linestring>{};
109 for (auto const& track : gpx_file.tracks)
110 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 */,
112 splits_with_overlap_segments);
113 auto const before_query = chrono::steady_clock::now();
114
115 l_.debug("Querying for relevant situations");
116 auto relevant_road_closures = std::unordered_set<std::shared_ptr<datex2::road_closure>>{};
117 auto ls_checked = 0uz;
118 auto p_checked = 0uz;
119 auto i = 0;
120 for (geo::linestring const& part : splits_with_overlap_segments) {
121 l_.debug("Checking part [{}/{}]", ++i, splits_with_overlap_segments.size());
122
123 auto part_box = geo::box{};
124 bgeo::envelope(part, part_box);
125
126 for (auto it = lse_index_.qbegin(bgeo::index::intersects(part_box)); it != lse_index_.qend(); it++) {
127 // Cannot use structured bindings here, as boost::geometry::get interferes with ADL.
128 // It is a candidate as the namespace boost::geometry is part of the associated namespace set,
129 // which happens because geo::linestring ≡ boost::geometry::model::linestring<geo::point> is part
130 // of the whole tuple type (lse_index_value) that is the value_type of the iterator.
131 std::shared_ptr<geo::linestring> const& ls = std::get<1>(*it);
132 std::shared_ptr<datex2::road_closure> const& rc = std::get<2>(*it);
133 if (rc->validity && rc->validity->intersect(check_periods).periods().empty())
134 continue;
135 if (bgeo::distance(*ls, part, geo::vincenty_strategy{}) < 5.0)
136 relevant_road_closures.emplace(rc);
137 ls_checked++;
138 }
139 for (auto it = p_index_.qbegin(bgeo::index::intersects(part_box)); it != p_index_.qend(); it++) {
140 // Cannot use structured bindings here for the same reason as above.
141 geo::point const& p = std::get<0>(*it);
142 std::shared_ptr<datex2::road_closure> const& rc = std::get<1>(*it);
143 if (rc->validity && rc->validity->intersect(check_periods).periods().empty())
144 continue;
145 if (bgeo::distance(p, part, geo::vincenty_strategy{}) < 5.0)
146 relevant_road_closures.emplace(rc);
147 p_checked++;
148 }
149 }
150
151 auto const after_query = chrono::steady_clock::now();
152 l_.debug("Done (checked {} line string(s) and {} point(s)) in {}",
153 ls_checked, p_checked, chrono::duration_cast<chrono::milliseconds>(after_query - before_query));
154
155 auto relevant_situations = std::unordered_set<std::shared_ptr<datex2::situation>>{};
156 for (auto const& rc : relevant_road_closures)
157 relevant_situations.emplace(rc->parent);
158
159 l_.debug("Identified {} relevant road closure(s), part of {} unique situation(s)",
160 relevant_road_closures.size(), relevant_situations.size());
161 for (auto const& sit : relevant_situations)
162 l_.debug("Relevant situation: {}", sit->id);
163
164 return process_gpx_result{
165 .tracks = gpx_file.tracks
166 | views::transform([](auto const& trk) -> track {
167 return {
168 .segments = trk.segments
169 | views::transform([](auto const& seg) -> track_segment {
170 return {.points = seg.waypoints};
171 })
172 | std::ranges::to<std::vector<track_segment>>(),
173 };
174 })
175 | std::ranges::to<std::vector<track>>(),
176 .relevant_situations = relevant_situations
177 | views::transform([&](std::shared_ptr<datex2::situation> sit) -> relevant_situation {
178 return {
179 .id = sit->id,
180 .location = sit->location,
181 .comments = sit->comments,
182 .relevant_road_closures = relevant_road_closures
183 | views::filter([&](std::shared_ptr<datex2::road_closure> const& rc) -> bool {
184 return std::shared_ptr{rc->parent} == sit;
185 })
186 | views::transform([](std::shared_ptr<datex2::road_closure> const& rc) -> relevant_road_closure {
187 return {
188 .relevant_lss = rc->relevant_line_strings
189 | views::transform([](auto const& lsp) -> geo::linestring {
190 return *lsp;
191 })
192 | std::ranges::to<std::vector<geo::linestring>>(),
193 };
194 })
195 | std::ranges::to<std::vector<relevant_road_closure>>(),
196 };
197 })
198 | std::ranges::to<std::vector<relevant_situation>>(),
199 };
200 }
201
202 auto handler::sysinfo() -> struct sysinfo {
203 return {.using_publication_of = pub_.publication_time};
204 }
205
206} // namespace routemon::api