summaryrefslogtreecommitdiffstats
path: root/server/src/gpx.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/gpx.cpp')
-rw-r--r--server/src/gpx.cpp189
1 files changed, 189 insertions, 0 deletions
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 @@
1module;
2
3#include <boost/geometry/algorithms/append.hpp>
4
5module routemon:gpx$impl;
6
7import std;
8import :gpx;
9import :util;
10import :xml;
11
12using namespace std::literals::string_view_literals;
13
14namespace routemon::gpx {
15
16 namespace v10 {
17
18 constexpr auto xmlns = "http://www.topografix.com/GPX/1/0"sv;
19 auto qname(std::string_view local) -> xml::qname_view {
20 return {.ns_uri = xmlns, .local = local};
21 }
22
23 auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser<geo::point> {
24 auto parse_xml_double = [](std::string_view sv) -> std::optional<double> {
25 return util::parse_double(sv, std::chars_format::fixed);
26 };
27 auto mlat = std::optional<double>{};
28 auto mlon = std::optional<double>{};
29 for (auto const& [name, value] : attrs) {
30 if (name == qname("lat")) {
31 mlat = parse_xml_double(value);
32 } else if (name == qname("lon")) {
33 mlon = parse_xml_double(value);
34 }
35 }
36 if (!mlat || !mlon)
37 throw std::runtime_error{"expected valid latitude and longitude for waypoint"};
38 co_await xml::ignore_contents(e);
39 co_return geo::point{*mlon, *mlat};
40 }
41
42 auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser<track_segment> {
43 auto s = track_segment{};
44 while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo<parse_wpt>()))
45 bgeo::append(s.waypoints, *mwpt);
46 co_return std::move(s);
47 }
48
49 auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser<track> {
50 auto t = track{};
51 t.name = co_await allow_element(e, qname("name"), xml::hohalo<xml::read_string_contents>());
52 co_await allow_element(e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>());
53 t.desc = co_await allow_element(e, qname("desc"), xml::hohalo<xml::read_string_contents>());
54 co_await xml::ignore_contents(e, /* until */ qname("trkseg"));
55 while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo<parse_trkseg>()))
56 t.segments.push_back(std::move(*mseg));
57 co_return std::move(t);
58 }
59
60 auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser<file> {
61 auto f = file{};
62 if (attrs.lookup(qname("version")) != "1.0"sv)
63 throw std::runtime_error{"expected GPX version to be 1.0"};
64 if (auto mcreator = attrs.lookup(qname("creator")))
65 f.creator = *mcreator;
66 else
67 throw std::runtime_error{"expected GPX file to have creator"};
68
69 f.meta.name = co_await allow_element(e, qname("name"), xml::hohalo<xml::read_string_contents>());
70 f.meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo<xml::read_string_contents>());
71
72 co_await xml::ignore_contents(e, /* until */ qname("trk"));
73 while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>()))
74 f.tracks.push_back(std::move(*mtrk));
75 co_await xml::ignore_contents(e);
76
77 co_return std::move(f);
78 }
79
80 } // namespace v10
81
82 namespace v11 {
83
84 constexpr auto xmlns = "http://www.topografix.com/GPX/1/1"sv;
85 auto qname(std::string_view local) -> xml::qname_view {
86 return {.ns_uri = xmlns, .local = local};
87 }
88
89 auto parse_metadata(xml::executor_ref e, xml::attribute_view) -> xml::parser<metadata> {
90 auto meta = metadata{};
91 meta.name = co_await allow_element(e, qname("name"), xml::hohalo<xml::read_string_contents>());
92 meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo<xml::read_string_contents>());
93 co_await xml::ignore_contents(e);
94 co_return meta;
95 }
96
97 auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser<geo::point> {
98 auto parse_xml_double = [](std::string_view sv) -> std::optional<double> {
99 return util::parse_double(sv, std::chars_format::fixed);
100 };
101 auto mlat = std::optional<double>{};
102 auto mlon = std::optional<double>{};
103 for (auto const& [name, value] : attrs) {
104 if (name == qname("lat")) {
105 mlat = parse_xml_double(value);
106 } else if (name == qname("lon")) {
107 mlon = parse_xml_double(value);
108 }
109 }
110 if (!mlat || !mlon)
111 throw std::runtime_error{"expected valid latitude and longitude for waypoint"};
112 co_await xml::ignore_contents(e);
113 co_return geo::point{*mlon, *mlat};
114 }
115
116 auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser<track_segment> {
117 auto s = track_segment{};
118 while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo<parse_wpt>()))
119 bgeo::append(s.waypoints, *mwpt);
120 co_await allow_element(e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>());
121 co_return std::move(s);
122 }
123
124 auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser<track> {
125 auto t = track{};
126 t.name = co_await allow_element(e, qname("name"), xml::hohalo<xml::read_string_contents>());
127 co_await allow_element(e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>());
128 t.desc = co_await allow_element(e, qname("desc"), xml::hohalo<xml::read_string_contents>());
129 co_await xml::ignore_contents(e, /* until */ qname("trkseg"));
130 while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo<parse_trkseg>()))
131 t.segments.push_back(std::move(*mseg));
132 co_return std::move(t);
133 }
134
135 auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser<file> {
136 auto f = file{};
137 if (attrs.lookup(qname("version")) != "1.1"sv)
138 throw std::runtime_error{"expected GPX version to be 1.1"};
139 if (auto mcreator = attrs.lookup(qname("creator")))
140 f.creator = *mcreator;
141 else
142 throw std::runtime_error{"expected GPX file to have creator"};
143
144 if (auto mmeta = co_await allow_element(e, qname("metadata"), xml::hohalo<parse_metadata>()))
145 f.meta = *mmeta;
146 co_await xml::ignore_contents(e, /* until */ qname("trk"));
147 while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>()))
148 f.tracks.push_back(std::move(*mtrk));
149 co_await allow_element(e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>());
150
151 co_return std::move(f);
152 }
153
154 } // namespace v11
155
156 auto parse_file(xml::executor_ref e) -> xml::parser<file> {
157 auto decl = co_await expect_event<xml::xml_decl_event>(e);
158 if (decl.version != "1.0"sv)
159 throw std::runtime_error{std::format("unsupported XML version, got {}", std::string_view{decl.version})};
160 if (decl.encoding != "UTF-8"sv)
161 throw std::runtime_error{"unsupported encoding"};
162 if (auto mf = co_await allow_element(e, v10::qname("gpx"), xml::hohalo<v10::parse_gpx>()))
163 co_return std::move(*mf);
164 if (auto mf = co_await allow_element(e, v11::qname("gpx"), xml::hohalo<v11::parse_gpx>()))
165 co_return std::move(*mf);
166 throw std::runtime_error{"no supported GPX document found"};
167 }
168
169 reader::reader()
170 : p_{parse_file(util::not_null{&e_})}
171 { e_.set_continuation(p_.promise().base_handle()); }
172
173 auto reader::init() -> void {
174 e_.start();
175 }
176
177 auto reader::put(std::string_view buf) -> void {
178 e_.read(buf, false);
179 }
180
181 auto reader::finish() -> gpx::file {
182 e_.read(std::string_view{}, true);
183 e_.end();
184 // Promise is still alive since the last coroutine performs a
185 // symmetric transfer to std::noop_coroutine() in final_suspend().
186 return std::move(p_.promise().returned_value());
187 }
188
189} // namespace routemon::gpx