SMACC2
Loading...
Searching...
No Matches
cp_kml_mission_loader.cpp
Go to the documentation of this file.
1// Copyright 2026 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17#include <tinyxml2.h>
18
19#include <fstream>
20#include <sstream>
21
22namespace cl_px4_mr
23{
24
25namespace
26{
27
28// "gx:LineString" -> "LineString"
29std::string localName(const char * qualified)
30{
31 if (qualified == nullptr)
32 {
33 return "";
34 }
35 std::string name(qualified);
36 const auto colon = name.rfind(':');
37 return colon == std::string::npos ? name : name.substr(colon + 1);
38}
39
40const tinyxml2::XMLElement * firstChildByLocalName(
41 const tinyxml2::XMLElement * parent, const std::string & wanted)
42{
43 for (const tinyxml2::XMLElement * child = parent->FirstChildElement(); child != nullptr;
44 child = child->NextSiblingElement())
45 {
46 if (localName(child->Name()) == wanted)
47 {
48 return child;
49 }
50 }
51 return nullptr;
52}
53
54// depth-first search for the first <LineString> with a non-empty <coordinates>
55const tinyxml2::XMLElement * findFirstLineStringCoordinates(const tinyxml2::XMLElement * element)
56{
57 if (element == nullptr)
58 {
59 return nullptr;
60 }
61
62 if (localName(element->Name()) == "LineString")
63 {
64 const tinyxml2::XMLElement * coords = firstChildByLocalName(element, "coordinates");
65 if (coords != nullptr && coords->GetText() != nullptr)
66 {
67 return coords;
68 }
69 }
70
71 for (const tinyxml2::XMLElement * child = element->FirstChildElement(); child != nullptr;
72 child = child->NextSiblingElement())
73 {
74 const tinyxml2::XMLElement * found = findFirstLineStringCoordinates(child);
75 if (found != nullptr)
76 {
77 return found;
78 }
79 }
80 return nullptr;
81}
82
83// KML coordinate tuples are "lon,lat[,alt]" separated by whitespace
84bool parseCoordinates(const std::string & text, std::vector<GeoPoint> & out, std::string & error)
85{
86 std::istringstream tokens(text);
87 std::string token;
88 while (tokens >> token)
89 {
90 std::vector<std::string> fields;
91 std::string field;
92 std::istringstream fieldStream(token);
93 while (std::getline(fieldStream, field, ','))
94 {
95 fields.push_back(field);
96 }
97
98 if (fields.size() < 2)
99 {
100 error = "coordinate tuple '" + token + "' has fewer than 2 fields";
101 return false;
102 }
103
104 try
105 {
106 GeoPoint p;
107 p.lon = std::stod(fields[0]);
108 p.lat = std::stod(fields[1]);
109 p.alt = fields.size() >= 3 && !fields[2].empty() ? std::stod(fields[2]) : 0.0;
110 out.push_back(p);
111 }
112 catch (const std::exception & e)
113 {
114 error = "coordinate tuple '" + token + "' is not numeric: " + e.what();
115 return false;
116 }
117 }
118
119 if (out.empty())
120 {
121 error = "LineString has no coordinate tuples";
122 return false;
123 }
124 return true;
125}
126
127std::vector<GeoPoint> parseDocument(tinyxml2::XMLDocument & doc, std::string & error)
128{
129 std::vector<GeoPoint> points;
130 const tinyxml2::XMLElement * coords = findFirstLineStringCoordinates(doc.RootElement());
131 if (coords == nullptr)
132 {
133 error = "no LineString/coordinates element found";
134 return points;
135 }
136
137 if (!parseCoordinates(coords->GetText(), points, error))
138 {
139 points.clear();
140 }
141 return points;
142}
143
144} // namespace
145
147
149
151{
152 RCLCPP_INFO(getLogger(), "CpKmlMissionLoader: ready (first LineString backbone only)");
153}
154
156 const std::string & xml, std::string & error)
157{
158 tinyxml2::XMLDocument doc;
159 if (doc.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS)
160 {
161 error = std::string("XML parse error: ") + (doc.ErrorStr() ? doc.ErrorStr() : "unknown");
162 return {};
163 }
164 return parseDocument(doc, error);
165}
166
167KmlLoadResult CpKmlMissionLoader::loadFile(const std::string & absolutePath)
168{
169 KmlLoadResult result;
170
171 tinyxml2::XMLDocument doc;
172 const tinyxml2::XMLError status = doc.LoadFile(absolutePath.c_str());
173 if (status != tinyxml2::XML_SUCCESS)
174 {
175 result.error = std::string("cannot load '") + absolutePath +
176 "': " + (doc.ErrorStr() ? doc.ErrorStr() : "unknown error");
177 RCLCPP_WARN(getLogger(), "CpKmlMissionLoader: %s", result.error.c_str());
178 return result;
179 }
180
181 std::string error;
182 std::vector<GeoPoint> points = parseDocument(doc, error);
183 if (points.empty())
184 {
185 result.error = "'" + absolutePath + "': " + error;
186 RCLCPP_WARN(getLogger(), "CpKmlMissionLoader: %s", result.error.c_str());
187 return result;
188 }
189
190 {
191 std::lock_guard<std::mutex> lock(mutex_);
192 mission_ = points;
193 source_ = absolutePath;
194 }
195
196 result.ok = true;
197 result.pointCount = points.size();
198 RCLCPP_INFO(
199 getLogger(),
200 "CpKmlMissionLoader: loaded %zu backbone points from '%s' (first %.6f,%.6f last %.6f,%.6f)",
201 points.size(), absolutePath.c_str(), points.front().lat, points.front().lon, points.back().lat,
202 points.back().lon);
203 return result;
204}
205
206void CpKmlMissionLoader::setMission(std::vector<GeoPoint> points, const std::string & source)
207{
208 std::lock_guard<std::mutex> lock(mutex_);
209 mission_ = std::move(points);
210 source_ = source;
211}
212
214{
215 std::lock_guard<std::mutex> lock(mutex_);
216 return !mission_.empty();
217}
218
219std::vector<GeoPoint> CpKmlMissionLoader::getMission() const
220{
221 std::lock_guard<std::mutex> lock(mutex_);
222 return mission_;
223}
224
226{
227 std::lock_guard<std::mutex> lock(mutex_);
228 return source_;
229}
230
232{
233 std::lock_guard<std::mutex> lock(mutex_);
234 mission_.clear();
235 source_.clear();
236}
237
238} // namespace cl_px4_mr
std::vector< GeoPoint > getMission() const
void setMission(std::vector< GeoPoint > points, const std::string &source)
KmlLoadResult loadFile(const std::string &absolutePath)
static std::vector< GeoPoint > parseKmlString(const std::string &xml, std::string &error)
rclcpp::Logger getLogger() const
const tinyxml2::XMLElement * findFirstLineStringCoordinates(const tinyxml2::XMLElement *element)
const tinyxml2::XMLElement * firstChildByLocalName(const tinyxml2::XMLElement *parent, const std::string &wanted)
std::vector< GeoPoint > parseDocument(tinyxml2::XMLDocument &doc, std::string &error)
bool parseCoordinates(const std::string &text, std::vector< GeoPoint > &out, std::string &error)