diff options
| author | Rutger Broekhoff | 2026-08-28 21:12:55 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-08-28 21:12:55 +0200 |
| commit | a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac (patch) | |
| tree | b4e0ec57c3799e9f649c6bfe27cb6c3313b97364 /server/src/api.cpp | |
| parent | 973aec43ea54bbf95b64fbcb636403401d1ca60e (diff) | |
| download | routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.tar.gz routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.zip | |
clang-format C++ sources
Diffstat (limited to 'server/src/api.cpp')
| -rw-r--r-- | server/src/api.cpp | 383 |
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 | ||
| 18 | namespace { | 18 | namespace { |
| 19 | 19 | ||
| 20 | namespace chrono = std::chrono; | 20 | namespace chrono = std::chrono; |
| 21 | namespace json = boost::json; | 21 | namespace json = boost::json; |
| 22 | namespace views = std::views; | 22 | namespace views = std::views; |
| 23 | 23 | ||
| 24 | } // namespace <anonymous> | 24 | } // namespace |
| 25 | 25 | ||
| 26 | namespace routemon::api { | 26 | namespace routemon::api { |
| 27 | 27 | ||
| 28 | auto json_value_from_point(geo::point const& p) -> json::value { | 28 | auto 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 { | 33 | auto 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 | |||
| 41 | auto 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 | |||
| 50 | auto 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{ | 59 | auto 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{ | 72 | auto 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{ | 80 | auto 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{ | 88 | auto 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) | 98 | auto 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 | |||
| 107 | handler::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> { | 136 | auto 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 { | 270 | auto 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 |