From 973aec43ea54bbf95b64fbcb636403401d1ca60e Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Fri, 28 Aug 2026 18:03:05 +0200 Subject: Import from e4b104792206ee7ea64bf39c6b7d2c0c230f9d14 --- server/src/api.cpp | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 server/src/api.cpp (limited to 'server/src/api.cpp') 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 @@ +module; + +#include +#include + +module routemon:api$impl; + +import std; +import :api; +import :datex2; +import :geo; +import :gpx; +import :log; +import :req_ctx; +import :time; +import :trace; + +namespace { + + namespace chrono = std::chrono; + namespace json = boost::json; + namespace views = std::views; + +} // namespace + +namespace routemon::api { + + auto json_value_from_point(geo::point const& p) -> json::value { + return json::array{bgeo::get<1>(p), bgeo::get<0>(p)}; + } + auto json_value_from_linestring(geo::linestring const& ls) -> json::value { + json::array a; + for (auto const& p : ls) + a.push_back(json_value_from_point(p)); + return a; + } + auto json_value_from_linestrings(std::vector const& lss) -> json::value { + json::array a; + for (auto const& ls : lss) + a.push_back(json_value_from_linestring(ls)); + return a; + } + + auto tag_invoke(json::value_from_tag, json::value& jv, relevant_road_closure const& clo) -> void { + jv = json::object{ + {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)}, + }; + } + auto tag_invoke(json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void { + jv = json::object{ + {"id", json::value_from(sit.id)}, + {"location", sit.location ? json_value_from_point(*sit.location) : nullptr}, + {"comments", json::value_from(sit.comments)}, + {"relevant_road_closures", json::value_from(sit.relevant_road_closures)}, + }; + } + auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) -> void { + jv = json::object{ + {"points", json_value_from_linestring(seg.points)}, + }; + } + auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) -> void { + jv = json::object{ + {"segments", json::value_from(track.segments)}, + }; + } + auto tag_invoke(json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void { + jv = json::object{ + {"tracks", json::value_from(res.tracks)}, + {"relevant_situations", json::value_from(res.relevant_situations)}, + }; + } + auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) -> void { + jv = json::object{ + {"using_publication_of", std::format("{:%FT%TZ}", info.using_publication_of)}, + }; + } + + handler::handler(log::logger const& l, datex2::situation_publication pub) + : l_{l.sub("handler")}, pub_{std::move(pub)} + { + l_.info("Building indices"); + auto const before_build = chrono::steady_clock::now(); + for (auto const& sit : pub_.situations) { + for (auto const& rc : sit->road_closures) { + for (auto const& ls : rc->relevant_line_strings) { + auto box = geo::box{}; + bgeo::envelope(*ls, box); + lse_index_.insert(std::make_tuple(box, ls, rc)); + } + for (auto p : rc->relevant_points) { + p_index_.insert(std::make_pair(p, rc)); + } + } + } + auto const after_build = chrono::steady_clock::now(); + auto const dur_build = chrono::duration_cast(after_build - before_build); + l_.info("Indices built in {}", dur_build); + l_.info("LSE index size: {}", lse_index_.size()); + l_.info("Point index size: {}", p_index_.size()); + } + + auto handler::process_gpx(gpx::file&& gpx_file) -> std::optional { + auto const now = chrono::utc_clock::now(); + auto const relevant = std::initializer_list{time::period{now - chrono::days(7), now + chrono::days(7)}}; + auto const check_periods = time::period_seq{relevant.begin(), relevant.end()}; + + auto splits_with_overlap_segments = std::vector{}; + for (auto const& track : gpx_file.tracks) + for (auto const& seg : track.segments) + geo::split_linestring_with_overlap_segments(seg.waypoints, 5000 /* meters max total dist until a new split is forced */, + splits_with_overlap_segments); + auto const before_query = chrono::steady_clock::now(); + + l_.debug("Querying for relevant situations"); + auto relevant_road_closures = std::unordered_set>{}; + auto ls_checked = 0uz; + auto p_checked = 0uz; + auto i = 0; + for (geo::linestring const& part : splits_with_overlap_segments) { + l_.debug("Checking part [{}/{}]", ++i, splits_with_overlap_segments.size()); + + auto part_box = geo::box{}; + bgeo::envelope(part, part_box); + + for (auto it = lse_index_.qbegin(bgeo::index::intersects(part_box)); it != lse_index_.qend(); it++) { + // Cannot use structured bindings here, as boost::geometry::get interferes with ADL. + // It is a candidate as the namespace boost::geometry is part of the associated namespace set, + // which happens because geo::linestring ≡ boost::geometry::model::linestring is part + // of the whole tuple type (lse_index_value) that is the value_type of the iterator. + std::shared_ptr const& ls = std::get<1>(*it); + std::shared_ptr const& rc = std::get<2>(*it); + if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) + continue; + if (bgeo::distance(*ls, part, geo::vincenty_strategy{}) < 5.0) + relevant_road_closures.emplace(rc); + ls_checked++; + } + for (auto it = p_index_.qbegin(bgeo::index::intersects(part_box)); it != p_index_.qend(); it++) { + // Cannot use structured bindings here for the same reason as above. + geo::point const& p = std::get<0>(*it); + std::shared_ptr const& rc = std::get<1>(*it); + if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) + continue; + if (bgeo::distance(p, part, geo::vincenty_strategy{}) < 5.0) + relevant_road_closures.emplace(rc); + p_checked++; + } + } + + auto const after_query = chrono::steady_clock::now(); + l_.debug("Done (checked {} line string(s) and {} point(s)) in {}", + ls_checked, p_checked, chrono::duration_cast(after_query - before_query)); + + auto relevant_situations = std::unordered_set>{}; + for (auto const& rc : relevant_road_closures) + relevant_situations.emplace(rc->parent); + + l_.debug("Identified {} relevant road closure(s), part of {} unique situation(s)", + relevant_road_closures.size(), relevant_situations.size()); + for (auto const& sit : relevant_situations) + l_.debug("Relevant situation: {}", sit->id); + + return process_gpx_result{ + .tracks = gpx_file.tracks + | views::transform([](auto const& trk) -> track { + return { + .segments = trk.segments + | views::transform([](auto const& seg) -> track_segment { + return {.points = seg.waypoints}; + }) + | std::ranges::to>(), + }; + }) + | std::ranges::to>(), + .relevant_situations = relevant_situations + | views::transform([&](std::shared_ptr sit) -> relevant_situation { + return { + .id = sit->id, + .location = sit->location, + .comments = sit->comments, + .relevant_road_closures = relevant_road_closures + | views::filter([&](std::shared_ptr const& rc) -> bool { + return std::shared_ptr{rc->parent} == sit; + }) + | views::transform([](std::shared_ptr const& rc) -> relevant_road_closure { + return { + .relevant_lss = rc->relevant_line_strings + | views::transform([](auto const& lsp) -> geo::linestring { + return *lsp; + }) + | std::ranges::to>(), + }; + }) + | std::ranges::to>(), + }; + }) + | std::ranges::to>(), + }; + } + + auto handler::sysinfo() -> struct sysinfo { + return {.using_publication_of = pub_.publication_time}; + } + +} // namespace routemon::api -- cgit v1.3