lanelet2_map_server v1.2.3
Loading...
Searching...
No Matches
lanelet2_map_server.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
5#include <GeographicLib/Geodesic.hpp>
6#include <algorithm>
7#include <chrono>
8#include <cmath>
9#include <filesystem>
10#include <limits>
11#include <pugixml.hpp>
12#include <sstream>
13#include <type_traits>
14#include <utility>
15
16Lanelet2MapServer::Lanelet2MapServer() : Node("lanelet2_map_server") {
17 // General parameters
18 this->declareAndLoadParameter("map_frame_id", map_frame_id_, "Frame ID of Lanelet2 map", true, false, false);
19 // Automatic map selection parameters
20 this->declareAndLoadParameter("use_automatic_map_selection", use_automatic_map_selection_, "Automatic map selection", false,
21 false, true);
22 this->declareAndLoadParameter("map_directory", map_directory_, "Directory containing Lanelet2 maps", true, false, false);
23 // Map-Server parameters (are not reconfigurable and not required if automatic map selection is enabled)
24 this->declareAndLoadParameter("map_filepath", map_filepath_, "Path to Lanelet2 map", !use_automatic_map_selection_,
26 origin_lat_set_ = this->declareAndLoadParameter("origin_lat", origin_lat_, "Latitude of origin of Lanelet2 map",
27 !use_automatic_map_selection_, false, false);
28 origin_lon_set_ = this->declareAndLoadParameter("origin_lon", origin_lon_, "Longitude of origin of Lanelet2 map",
29 !use_automatic_map_selection_, false, false);
30 // Map-Contents is never required and never reconfigurable
31 this->declareAndLoadParameter("map_contents", map_contents_, "Contents of Lanelet2 map", false, false, false);
32
33 this->setup();
34}
35
36template <typename T>
37bool Lanelet2MapServer::declareAndLoadParameter(const std::string& name,
38 T& param,
39 const std::string& description,
40 const bool add_to_auto_reconfigurable_params,
41 const bool is_required,
42 const bool read_only,
43 const std::optional<double>& from_value,
44 const std::optional<double>& to_value,
45 const std::optional<double>& step_value,
46 const std::string& additional_constraints) {
47 rcl_interfaces::msg::ParameterDescriptor param_desc;
48 param_desc.description = description;
49 param_desc.additional_constraints = additional_constraints;
50 param_desc.read_only = read_only;
51
52 auto type = rclcpp::ParameterValue(param).get_type();
53
54 if (from_value.has_value() && to_value.has_value()) {
55 if constexpr (std::is_integral_v<T>) {
56 rcl_interfaces::msg::IntegerRange range;
57 range.set__from_value(static_cast<T>(from_value.value())).set__to_value(static_cast<T>(to_value.value()));
58 if (step_value.has_value()) range.set__step(static_cast<T>(step_value.value()));
59 param_desc.integer_range = {range};
60 } else if constexpr (std::is_floating_point_v<T>) {
61 rcl_interfaces::msg::FloatingPointRange range;
62 range.set__from_value(static_cast<T>(from_value.value())).set__to_value(static_cast<T>(to_value.value()));
63 if (step_value.has_value()) range.set__step(static_cast<T>(step_value.value()));
64 param_desc.floating_point_range = {range};
65 } else {
66 RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
67 }
68 }
69
70 this->declare_parameter(name, type, param_desc);
71 bool initial_set = false;
72
73 try {
74 param = this->get_parameter(name).get_value<T>();
75 std::stringstream ss;
76 ss << "Loaded parameter '" << name << "': ";
77 if constexpr (is_vector_v<T>) {
78 ss << "[";
79 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
80 ss << "]";
81 } else {
82 ss << param;
83 }
84 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
85 initial_set = true;
86 } catch (rclcpp::exceptions::ParameterUninitializedException&) {
87 if (is_required) {
88 RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
89 exit(EXIT_FAILURE);
90 } else {
91 std::stringstream ss;
92 ss << "Missing parameter '" << name << "', using default value: ";
93 if constexpr (is_vector_v<T>) {
94 ss << "[";
95 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
96 ss << "]";
97 } else {
98 ss << param;
99 }
100 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
101 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
102 }
103 }
104
105 if (add_to_auto_reconfigurable_params) {
106 std::function<void(const rclcpp::Parameter&)> setter = [&param](const rclcpp::Parameter& p) { param = p.get_value<T>(); };
107 auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
108 }
109
110 return initial_set;
111}
112
113rcl_interfaces::msg::SetParametersResult Lanelet2MapServer::parametersCallback(const std::vector<rclcpp::Parameter>& parameters) {
114 std::string previous_map_filepath = map_filepath_;
115 rcl_interfaces::msg::SetParametersResult result;
116 result.successful = false;
117 double previous_origin_lat = origin_lat_;
118 double previous_origin_lon = origin_lon_;
119 bool relevant_change = false;
120
121 bool map_filepath_changed = false;
122 bool origin_lat_changed = false;
123 bool origin_lon_changed = false;
124
125 RCLCPP_INFO(this->get_logger(), "Received request to change %zu parameter(s)", parameters.size());
126 for (const auto& p : parameters) {
127 if (p.get_name() == "map_contents") {
128 RCLCPP_INFO(this->get_logger(), "Incoming param: map_contents = <omitted, size=%zu>", p.get_value<std::string>().size());
129 } else {
130 RCLCPP_INFO(this->get_logger(), "Incoming param: %s = %s", p.get_name().c_str(), p.value_to_string().c_str());
131 }
132 }
133
134 for (const auto& param : parameters) {
135 for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
136 if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
137 std::get<1>(auto_reconfigurable_param)(param);
138 RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s' to: %s", param.get_name().c_str(),
139 param.value_to_string().c_str());
140 break;
141 }
142 }
143 // handle special cases
144 if (param.get_name() == "map_filepath") {
145 if (param.get_value<std::string>() != map_filepath_) {
146 result.reason = "Parameter 'map_filepath' is not reconfigurable when automatic map selection is enabled.";
147 return result;
148 }
149 map_filepath_changed = previous_map_filepath != map_filepath_;
150 relevant_change = relevant_change || map_filepath_changed;
151 }
152 if (param.get_name() == "origin_lat") {
153 if (param.get_value<double>() != origin_lat_) {
154 result.reason = "Parameter 'origin_lat' is not reconfigurable when automatic map selection is enabled.";
155 return result;
156 }
157 origin_lat_changed = previous_origin_lat != origin_lat_;
158 relevant_change = relevant_change || origin_lat_changed;
159
160 if (origin_lat_changed) origin_lat_set_ = true;
161 }
162 if (param.get_name() == "origin_lon") {
163 if (param.get_value<double>() != origin_lon_) {
164 result.reason = "Parameter 'origin_lon' is not reconfigurable when automatic map selection is enabled.";
165 return result;
166 }
167 origin_lon_changed = previous_origin_lon != origin_lon_;
168 relevant_change = relevant_change || origin_lon_changed;
169
170 if (origin_lon_changed) origin_lon_set_ = true;
171 }
172 if (param.get_name() == "map_contents" && param.get_value<std::string>() != map_contents_) {
173 result.reason = "Parameter 'map_contents' is not reconfigurable.";
174 return result;
175 }
176 if (param.get_name() == "map_directory" && use_automatic_map_selection_) {
178 if (available_maps_.empty()) {
179 RCLCPP_ERROR(this->get_logger(), "No Lanelet2 maps found in '%s'", map_directory_.c_str());
180 // reload parameters in timer callback since parameters cannot be updated in this callback
181 one_shot_timer_ = this->create_wall_timer(std::chrono::milliseconds(1), [this]() {
182 this->one_shot_timer_->cancel();
184 });
185 }
186 }
187 }
188
189 if (!use_automatic_map_selection_ && relevant_change) {
190 // set default origin (bottom left) if not set before
193 result.reason = "Could not derive lower-left origin from map in manual mode.";
194 result.successful = false;
195 return result;
196 }
197
198 RCLCPP_INFO(this->get_logger(), "Derived manual origin from lower-left map corner for '%s' (lat=%.9f, lon=%.9f)",
200 }
201
202 // load map if not unbalanced origin parameters are provided (both or none)
205 // reload parameters in timer callback since parameters cannot be updated in this callback
206 one_shot_timer_ = this->create_wall_timer(std::chrono::milliseconds(1), [this]() {
207 this->one_shot_timer_->cancel();
208
209 this->loadMapContents();
210 this->updateMapParameters();
211 this->pub_tf();
212 });
213 } else {
214 this->updateMapParameters();
215 RCLCPP_ERROR(this->get_logger(), "Map sanity check failed for map '%s' with origin (lat=%.9f, lon=%.9f)",
217 result.reason = "Map sanity check failed in manual mode.";
218 result.successful = false;
219 return result;
220 }
221 } else {
222 this->updateMapParameters();
223 RCLCPP_WARN(this->get_logger(),
224 "Waiting for origin readiness: if origin is explictly set, both parameters must be provided.");
225 result.reason = "Waiting for origin readiness: if origin is explictly set, both parameters must be provided.";
226 result.successful = true;
227 return result;
228 }
229 }
230 result.successful = true;
231 return result;
232}
233
235 // callback for dynamic parameter configuration
237 this->add_on_set_parameters_callback(std::bind(&Lanelet2MapServer::parametersCallback, this, std::placeholders::_1));
238
239 tf_static_broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);
240
243 if (available_maps_.empty()) {
244 RCLCPP_ERROR(this->get_logger(), "No Lanelet2 maps found in '%s'", map_directory_.c_str());
246 }
247 // Create NavSatFix subscription and automatic map update timer
248 navsat_subscription_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
249 "~/gps/fix", 10, std::bind(&Lanelet2MapServer::navSatFixCallback, this, std::placeholders::_1));
251 this->create_wall_timer(std::chrono::seconds(1), std::bind(&Lanelet2MapServer::automaticMapUpdateTimerCallback, this));
252 } else {
255 RCLCPP_ERROR(this->get_logger(), "Could not derive lower-left origin from map '%s' in manual mode.",
256 map_filepath_.c_str());
258 return;
259 }
260 RCLCPP_INFO(this->get_logger(), "Derived manual origin from lower-left map corner for '%s' (lat=%.9f, lon=%.9f)",
262 } else if (origin_lat_set_ != origin_lon_set_) {
263 RCLCPP_ERROR(this->get_logger(),
264 "Manual mode requires both 'origin_lat' and 'origin_lon' when either is provided. Received origin_lat set=%s, "
265 "origin_lon set=%s.",
266 origin_lat_set_ ? "true" : "false", origin_lon_set_ ? "true" : "false");
268 return;
269 }
270
272 this->loadMapContents();
273 this->updateMapParameters();
274 this->pub_tf();
275 } else {
276 RCLCPP_ERROR(this->get_logger(), "Map sanity check failed for map '%s' with origin (lat=%.9f, lon=%.9f)",
279 }
280 }
281}
282
283bool Lanelet2MapServer::deriveOriginFromMap(const std::string& map_filepath, double& origin_lat, double& origin_lon) const {
284 if (map_filepath.empty()) {
285 RCLCPP_ERROR(this->get_logger(), "No map filepath provided, cannot derive origin from map.");
286 return false;
287 }
288
289 Lanelet2MapMeta map_meta;
290 map_meta.map_path = map_filepath;
291 derive_map_meta(map_meta);
292 if (map_meta.diagonal_length <= 0.0) {
293 return false;
294 }
295
296 origin_lat = map_meta.min_lat;
297 origin_lon = map_meta.min_lon;
298 return true;
299}
300
301void Lanelet2MapServer::find_available_maps(const std::string& directory, std::vector<Lanelet2MapMeta>& maps) const {
302 maps.clear();
303 try {
304 for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
305 if (!entry.is_regular_file()) {
306 continue;
307 }
308 if (entry.path().extension() == ".osm") {
309 Lanelet2MapMeta map_meta;
310 map_meta.map_path = entry.path().string();
311 maps.emplace_back(map_meta);
312 }
313 }
314 } catch (const std::filesystem::filesystem_error& e) {
315 RCLCPP_ERROR_STREAM(get_logger(), "Failed to scan '" << directory << "': " << e.what());
316 }
317 for (auto& map_meta : maps) {
318 derive_map_meta(map_meta);
319 }
320 RCLCPP_INFO(this->get_logger(), "Discovered %zu Lanelet2 maps in '%s'", maps.size(), directory.c_str());
321 for (const auto& map_meta : available_maps_) {
322 RCLCPP_INFO(this->get_logger(), "Map '%s': min_lat=%.9f, min_lon=%.9f, max_lat=%.9f, max_lon=%.9f, diagonal_length=%.3f",
323 map_meta.map_path.c_str(), map_meta.min_lat, map_meta.min_lon, map_meta.max_lat, map_meta.max_lon,
324 map_meta.diagonal_length);
325 }
326}
327
329 map_meta.diagonal_length = -1.0;
330 pugi::xml_document document;
331 const pugi::xml_parse_result result = document.load_file(map_meta.map_path.c_str());
332 if (!result) {
333 RCLCPP_ERROR_STREAM(get_logger(), "Unable to parse map '" << map_meta.map_path << "': " << result.description());
334 return;
335 }
336
337 pugi::xml_node root = document.child("osm");
338 if (!root) {
339 root = document.document_element();
340 }
341 if (!root) {
342 RCLCPP_WARN_STREAM(get_logger(), "No root element found when deriving meta for '" << map_meta.map_path << "'");
343 return;
344 }
345
346 const double kDoubleMax = std::numeric_limits<double>::max();
347 const double kDoubleLowest = std::numeric_limits<double>::lowest();
348 double min_lat = kDoubleMax;
349 double min_lon = kDoubleMax;
350 double max_lat = kDoubleLowest;
351 double max_lon = kDoubleLowest;
352 bool found_node = false;
353
354 for (pugi::xml_node node : root.children("node")) {
355 const double lat = node.attribute("lat").as_double(std::numeric_limits<double>::quiet_NaN());
356 const double lon = node.attribute("lon").as_double(std::numeric_limits<double>::quiet_NaN());
357 if (std::isnan(lat) || std::isnan(lon)) {
358 continue;
359 }
360
361 min_lat = std::min(min_lat, lat);
362 max_lat = std::max(max_lat, lat);
363 min_lon = std::min(min_lon, lon);
364 max_lon = std::max(max_lon, lon);
365 found_node = true;
366 }
367
368 if (!found_node) {
369 RCLCPP_WARN_STREAM(get_logger(), "No node elements found while deriving bounds for '" << map_meta.map_path << "'");
370 return;
371 }
372
373 map_meta.min_lat = min_lat;
374 map_meta.max_lat = max_lat;
375 map_meta.min_lon = min_lon;
376 map_meta.max_lon = max_lon;
377
378 double distance = 0.0;
379 GeographicLib::Geodesic::WGS84().Inverse(min_lat, min_lon, max_lat, max_lon, distance);
380 map_meta.diagonal_length = distance;
381}
382
383void Lanelet2MapServer::navSatFixCallback(const sensor_msgs::msg::NavSatFix::SharedPtr msg) {
384 current_latitude_ = msg->latitude;
385 current_longitude_ = msg->longitude;
386 gps_fix_received_ = true;
387}
388
390 if (!gps_fix_received_) {
391 RCLCPP_WARN(this->get_logger(), "No initial GPS fix received, unable to select map");
393 return;
394 }
395
396 const Lanelet2MapMeta* selected_map = nullptr;
397 for (const auto& map_meta : available_maps_) {
398 // check for invalid map
399 if (map_meta.diagonal_length <= 0.0) {
400 continue;
401 }
402 // check for maps outside current GPS location
403 if (current_latitude_ < map_meta.min_lat || current_latitude_ > map_meta.max_lat || current_longitude_ < map_meta.min_lon ||
404 current_longitude_ > map_meta.max_lon) {
405 continue;
406 }
407 if (selected_map == nullptr || map_meta.diagonal_length < selected_map->diagonal_length) {
408 if (map_sanity_check(map_meta.map_path, map_meta.min_lat, map_meta.min_lon)) {
409 RCLCPP_DEBUG(this->get_logger(), "Map sanity check passed for '%s'", map_meta.map_path.c_str());
410 selected_map = &map_meta;
411 } else {
412 RCLCPP_WARN(this->get_logger(), "Map sanity check failed for '%s'", map_meta.map_path.c_str());
413 continue;
414 }
415 }
416 }
417
418 if (selected_map == nullptr) {
419 RCLCPP_WARN(this->get_logger(), "No suitable map found for current GPS location (lat=%.9f, lon=%.9f)", current_latitude_,
422 return;
423 }
424
425 if (map_filepath_ == selected_map->map_path) {
426 RCLCPP_DEBUG(this->get_logger(), "Currently loaded map '%s' is still valid", map_filepath_.c_str());
427 return;
428 }
429
430 map_filepath_ = selected_map->map_path;
431 origin_lat_ = selected_map->min_lat;
432 origin_lon_ = selected_map->min_lon;
433 origin_lat_set_ = false;
434 origin_lon_set_ = false;
435 this->loadMapContents();
436 this->updateMapParameters();
437 this->pub_tf();
438}
439
441 if (map_filepath_.empty()) {
442 map_contents_ = "";
443 return;
444 }
445 std::ifstream file(map_filepath_);
446 map_contents_ = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
447 RCLCPP_INFO(this->get_logger(), "Loaded map contents from '%s' to parameter 'map_contents'", map_filepath_.c_str());
448}
449
451 this->set_parameter(rclcpp::Parameter("map_filepath", map_filepath_));
452 this->set_parameter(rclcpp::Parameter("map_contents", map_contents_));
453 this->set_parameter(rclcpp::Parameter("origin_lat", origin_lat_));
454 this->set_parameter(rclcpp::Parameter("origin_lon", origin_lon_));
455}
456
458 map_filepath_ = "";
459 map_contents_ = "";
460 origin_lat_ = 0.0;
461 origin_lon_ = 0.0;
462 origin_lat_set_ = false;
463 origin_lon_set_ = false;
465}
466
467bool Lanelet2MapServer::map_sanity_check(std::string map_filepath, double origin_lat, double origin_lon) const {
468 lanelet::projection::UtmProjector proj(lanelet::Origin({origin_lat, origin_lon}));
469 try {
470 lanelet::LaneletMapPtr map = lanelet::load(map_filepath, proj);
471 } catch (const lanelet::IOError& e) {
472 RCLCPP_ERROR_STREAM(this->get_logger(), "Could not load map '" << map_filepath << "': " << e.what());
473 return false;
474 }
475
476 return true;
477}
478
479void Lanelet2MapServer::derive_utm_zone(const double latitude, const double longitude, int& zone, bool& northp) {
480 northp = latitude >= 0.0;
481 zone = static_cast<int>(std::floor((longitude + 180.0) / 6.0)) + 1;
482}
483
485 if (map_filepath_.empty()) {
486 return;
487 }
488 geometry_msgs::msg::TransformStamped t;
489
490 // Create Projector without offset
491 lanelet::projection::UtmProjector proj_utm(lanelet::Origin({origin_lat_, origin_lon_}), false);
492 lanelet::BasicPoint3d origin_utm = proj_utm.forward(lanelet::GPSPoint({origin_lat_, origin_lon_, 0.0}));
493
494 t.transform.translation.x = origin_utm.x();
495 t.transform.translation.y = origin_utm.y();
496 t.transform.translation.z = origin_utm.z();
497
498 t.transform.rotation.x = 0;
499 t.transform.rotation.y = 0;
500 t.transform.rotation.z = 0;
501 t.transform.rotation.w = 1;
502
503 bool northp = false;
504 int zone = 0;
506
507 std::string hemisphere;
508 if (northp) {
509 hemisphere = "N";
510 } else {
511 hemisphere = "S";
512 }
513
514 t.header.frame_id = "utm_" + std::to_string(zone) + hemisphere;
515 t.child_frame_id = map_frame_id_;
516 t.header.stamp = now();
517
518 // Send the transformation
519 tf_static_broadcaster_->sendTransform(t);
520 RCLCPP_INFO(get_logger(), "Broadcast static transform from '%s' to '%s'", t.header.frame_id.c_str(), t.child_frame_id.c_str());
521}
522
530int main(int argc, char** argv) {
531 rclcpp::init(argc, argv);
532 rclcpp::spin(std::make_shared<Lanelet2MapServer>());
533 rclcpp::shutdown();
534 return 0;
535}
void unsetMapParameters()
Clears map-specific parameters after an invalid or unloaded map state.
std::vector< Lanelet2MapMeta > available_maps_
void updateMapParameters()
Writes the current map state into this node's exported ROS parameters.
rclcpp::TimerBase::SharedPtr one_shot_timer_
rclcpp::TimerBase::SharedPtr automatic_map_timer_
bool declareAndLoadParameter(const std::string &name, T &param, const std::string &description, const bool add_to_auto_reconfigurable_params=true, const bool is_required=false, const bool read_only=false, const std::optional< double > &from_value=std::nullopt, const std::optional< double > &to_value=std::nullopt, const std::optional< double > &step_value=std::nullopt, const std::string &additional_constraints="")
Declares and loads a ROS parameter.
OnSetParametersCallbackHandle::SharedPtr parameters_callback_
Callback handle for dynamic parameter reconfiguration.
void setup()
Sets up timers, callbacks, publishers, and initial map loading.
void automaticMapUpdateTimerCallback()
Periodically reevaluates the best map when automatic selection is enabled.
std::shared_ptr< tf2_ros::StaticTransformBroadcaster > tf_static_broadcaster_
bool map_sanity_check(std::string map_filepath, double origin_lat, double origin_lon) const
Tries to load a map with the provided origin to verify that it is usable.
void derive_map_meta(Lanelet2MapMeta &map_meta) const
Fills the derived metadata fields for a discovered map.
Lanelet2MapServer()
Creates the Lanelet2 map server node and declares its parameters.
bool deriveOriginFromMap(const std::string &map_filepath, double &origin_lat, double &origin_lon) const
Derives a south-west origin latitude and longitude from a Lanelet2 map file.
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Handles reconfiguration when a parameter value is changed.
void loadMapContents()
Loads the configured map file and caches its serialized contents.
static void derive_utm_zone(const double latitude, const double longitude, int &zone, bool &northp)
Derives the UTM zone and hemisphere from a geographic position.
void navSatFixCallback(const sensor_msgs::msg::NavSatFix::SharedPtr msg)
Stores the latest GNSS fix for automatic map selection.
std::vector< std::tuple< std::string, std::function< void(const rclcpp::Parameter &)> > > auto_reconfigurable_params_
Auto-reconfigurable parameters for dynamic reconfiguration.
void find_available_maps(const std::string &directory, std::vector< Lanelet2MapMeta > &maps) const
Recursively scans a directory for available Lanelet2 map files.
void pub_tf() const
Publishes the static transform from the derived UTM frame to the map frame.
rclcpp::Subscription< sensor_msgs::msg::NavSatFix >::SharedPtr navsat_subscription_
int main(int argc, char **argv)
Starts the Lanelet2 map server node.
constexpr bool is_vector_v