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/gpx.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 server/src/gpx.cpp (limited to 'server/src/gpx.cpp') diff --git a/server/src/gpx.cpp b/server/src/gpx.cpp new file mode 100644 index 0000000..62dccff --- /dev/null +++ b/server/src/gpx.cpp @@ -0,0 +1,189 @@ +module; + +#include + +module routemon:gpx$impl; + +import std; +import :gpx; +import :util; +import :xml; + +using namespace std::literals::string_view_literals; + +namespace routemon::gpx { + + namespace v10 { + + constexpr auto xmlns = "http://www.topografix.com/GPX/1/0"sv; + auto qname(std::string_view local) -> xml::qname_view { + return {.ns_uri = xmlns, .local = local}; + } + + auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { + auto parse_xml_double = [](std::string_view sv) -> std::optional { + return util::parse_double(sv, std::chars_format::fixed); + }; + auto mlat = std::optional{}; + auto mlon = std::optional{}; + for (auto const& [name, value] : attrs) { + if (name == qname("lat")) { + mlat = parse_xml_double(value); + } else if (name == qname("lon")) { + mlon = parse_xml_double(value); + } + } + if (!mlat || !mlon) + throw std::runtime_error{"expected valid latitude and longitude for waypoint"}; + co_await xml::ignore_contents(e); + co_return geo::point{*mlon, *mlat}; + } + + auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser { + auto s = track_segment{}; + while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo())) + bgeo::append(s.waypoints, *mwpt); + co_return std::move(s); + } + + auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser { + auto t = track{}; + t.name = co_await allow_element(e, qname("name"), xml::hohalo()); + co_await allow_element(e, qname("cmt"), xml::hohalo()); + t.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); + co_await xml::ignore_contents(e, /* until */ qname("trkseg")); + while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo())) + t.segments.push_back(std::move(*mseg)); + co_return std::move(t); + } + + auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { + auto f = file{}; + if (attrs.lookup(qname("version")) != "1.0"sv) + throw std::runtime_error{"expected GPX version to be 1.0"}; + if (auto mcreator = attrs.lookup(qname("creator"))) + f.creator = *mcreator; + else + throw std::runtime_error{"expected GPX file to have creator"}; + + f.meta.name = co_await allow_element(e, qname("name"), xml::hohalo()); + f.meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); + + co_await xml::ignore_contents(e, /* until */ qname("trk")); + while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo())) + f.tracks.push_back(std::move(*mtrk)); + co_await xml::ignore_contents(e); + + co_return std::move(f); + } + + } // namespace v10 + + namespace v11 { + + constexpr auto xmlns = "http://www.topografix.com/GPX/1/1"sv; + auto qname(std::string_view local) -> xml::qname_view { + return {.ns_uri = xmlns, .local = local}; + } + + auto parse_metadata(xml::executor_ref e, xml::attribute_view) -> xml::parser { + auto meta = metadata{}; + meta.name = co_await allow_element(e, qname("name"), xml::hohalo()); + meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); + co_await xml::ignore_contents(e); + co_return meta; + } + + auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { + auto parse_xml_double = [](std::string_view sv) -> std::optional { + return util::parse_double(sv, std::chars_format::fixed); + }; + auto mlat = std::optional{}; + auto mlon = std::optional{}; + for (auto const& [name, value] : attrs) { + if (name == qname("lat")) { + mlat = parse_xml_double(value); + } else if (name == qname("lon")) { + mlon = parse_xml_double(value); + } + } + if (!mlat || !mlon) + throw std::runtime_error{"expected valid latitude and longitude for waypoint"}; + co_await xml::ignore_contents(e); + co_return geo::point{*mlon, *mlat}; + } + + auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser { + auto s = track_segment{}; + while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo())) + bgeo::append(s.waypoints, *mwpt); + co_await allow_element(e, qname("extensions"), xml::hohalo()); + co_return std::move(s); + } + + auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser { + auto t = track{}; + t.name = co_await allow_element(e, qname("name"), xml::hohalo()); + co_await allow_element(e, qname("cmt"), xml::hohalo()); + t.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); + co_await xml::ignore_contents(e, /* until */ qname("trkseg")); + while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo())) + t.segments.push_back(std::move(*mseg)); + co_return std::move(t); + } + + auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { + auto f = file{}; + if (attrs.lookup(qname("version")) != "1.1"sv) + throw std::runtime_error{"expected GPX version to be 1.1"}; + if (auto mcreator = attrs.lookup(qname("creator"))) + f.creator = *mcreator; + else + throw std::runtime_error{"expected GPX file to have creator"}; + + if (auto mmeta = co_await allow_element(e, qname("metadata"), xml::hohalo())) + f.meta = *mmeta; + co_await xml::ignore_contents(e, /* until */ qname("trk")); + while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo())) + f.tracks.push_back(std::move(*mtrk)); + co_await allow_element(e, qname("extensions"), xml::hohalo()); + + co_return std::move(f); + } + + } // namespace v11 + + auto parse_file(xml::executor_ref e) -> xml::parser { + auto decl = co_await expect_event(e); + if (decl.version != "1.0"sv) + throw std::runtime_error{std::format("unsupported XML version, got {}", std::string_view{decl.version})}; + if (decl.encoding != "UTF-8"sv) + throw std::runtime_error{"unsupported encoding"}; + if (auto mf = co_await allow_element(e, v10::qname("gpx"), xml::hohalo())) + co_return std::move(*mf); + if (auto mf = co_await allow_element(e, v11::qname("gpx"), xml::hohalo())) + co_return std::move(*mf); + throw std::runtime_error{"no supported GPX document found"}; + } + + reader::reader() + : p_{parse_file(util::not_null{&e_})} + { e_.set_continuation(p_.promise().base_handle()); } + + auto reader::init() -> void { + e_.start(); + } + + auto reader::put(std::string_view buf) -> void { + e_.read(buf, false); + } + + auto reader::finish() -> gpx::file { + e_.read(std::string_view{}, true); + e_.end(); + // Promise is still alive since the last coroutine performs a + // symmetric transfer to std::noop_coroutine() in final_suspend(). + return std::move(p_.promise().returned_value()); + } + +} // namespace routemon::gpx -- cgit v1.3