module; #include #include module routemon:api$impl; import :api; namespace { namespace chrono = std::chrono; namespace json = boost::json; namespace views = std::views; } // namespace namespace routemon::api { auto json_value_from_point(geo::utm::zonable_wgs84_point const& p) -> json::value { return json::array{p.lat(), p.lon()}; } auto json_value_from_linestring(geo::utm::zonable_wgs84_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)}, {"blse_index_size", info.blse_index_size}, {"bpe_index_size", info.bpe_index_size}, }; } handler::handler(log::logger const& l, datex2::situation_publication pub) : l_{l.sub("handler")}, pub_{std::move(pub)} { auto const nsit = pub_.situations.size(); auto progress_percent = [nsit](decltype(pub_.situations)::difference_type i) -> int { return static_cast( static_cast(i) / static_cast(nsit) * 100.0f); }; l_.info("Building indexes for {} situations", nsit); l_.info("Progress: 0%"); auto const before_build = chrono::steady_clock::now(); for (auto const& [i, sit] : pub_.situations | std::views::enumerate) { for (auto const& rc : sit->road_closures) { for (auto const& ls : rc->relevant_line_strings) { blse_index_.insert(*ls, rc); } for (auto p : rc->relevant_points) { bpe_index_.insert(p, rc); } } auto prev_prog = progress_percent(i); auto cur_prog = progress_percent(i + 1); if (cur_prog > prev_prog && cur_prog % 5 == 0 && cur_prog != 100) { l_.info("Progress: {}%", cur_prog); } } l_.info("Progress: 100%"); auto const after_build = chrono::steady_clock::now(); auto const dur_build_s = chrono::duration{after_build - before_build}; l_.info("Indexes built in {}", dur_build_s); l_.info("BLSE index size: {}", blse_index_.size()); l_.info("Point index size: {}", bpe_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()}; // TODO: eliminate use of overlap segments auto segments = std::vector{}; for (auto const& track : gpx_file.tracks) for (auto const& seg : track.segments) segments.push_back(seg.waypoints); auto const before_query = chrono::steady_clock::now(); auto vincenty_strategy = geo::wgs84::vincenty_strategy{}; 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 (auto const& part : segments) { l_.debug("Checking part [{}/{}]", ++i, segments.size()); auto part_zone_lss = geo::utm::multizonal::split_linestring_across_zones(part); // TODO: consider buffering with min_distance_ auto part_box = geo::wgs84::box{}; bgeo::envelope(part, part_box); for (auto const& rc : blse_index_.intersection(part_zone_lss)) { if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) continue; // auto small_parts = bgeo::model::multi_linestring{}; // bgeo::intersection(part, lse, small_parts); relevant_road_closures.emplace(rc); // if (!bgeo::is_empty(small_parts)) // if (bgeo::distance(*ls, small_parts, vincenty_strategy) < // min_distance_) // relevant_road_closures.emplace(rc); ls_checked++; } for (auto const& rc : bpe_index_.intersection(part_zone_lss)) { if (rc->validity && rc->validity->intersect(check_periods).periods().empty()) continue; 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::utm::zonable_wgs84_linestring { return *lsp; }) | std::ranges::to>(), }; }) | std::ranges::to>(), }; }) | std::ranges::to>(), }; } auto handler::sysinfo() -> struct sysinfo { return { .using_publication_of = pub_.publication_time, .blse_index_size = blse_index_.size(), .bpe_index_size = bpe_index_.size(), }; } } // namespace routemon::api