32 #include "nav2_map_server/map_io.hpp"
45 #include "nav2_util/geometry_utils.hpp"
47 #include "yaml-cpp/yaml.h"
48 #include "tf2/LinearMath/Matrix3x3.h"
49 #include "tf2/LinearMath/Quaternion.h"
50 #include "nav2_util/occ_grid_values.hpp"
55 char * dirname(
char * path)
57 static const char dot[] =
".";
67 if (*c ==
'\\') {*c =
'/';}
72 last_slash = path != NULL ? strrchr(path,
'/') : NULL;
74 if (last_slash != NULL && last_slash == path) {
78 }
else if (last_slash != NULL && last_slash[1] ==
'\0') {
80 last_slash =
reinterpret_cast<char *
>(memchr(path, last_slash - path,
'/'));
83 if (last_slash != NULL) {
90 path =
reinterpret_cast<char *
>(dot);
97 namespace nav2_map_server
99 using nav2_util::geometry_utils::orientationAroundZAxis;
108 T yaml_get_value(
const YAML::Node & node,
const std::string & key)
111 return node[key].as<T>();
112 }
catch (YAML::Exception & e) {
113 std::stringstream ss;
114 ss <<
"Failed to parse YAML tag '" << key <<
"' for reason: " << e.msg;
115 throw YAML::Exception(e.mark, ss.str());
119 std::string get_home_dir()
121 if (
const char * home_dir = std::getenv(
"HOME")) {
122 return std::string{home_dir};
124 return std::string{};
127 std::string expand_user_home_dir_if_needed(
128 std::string yaml_filename,
129 std::string home_variable_value)
131 if (yaml_filename.size() < 2 || !(yaml_filename[0] ==
'~' && yaml_filename[1] ==
'/')) {
132 return yaml_filename;
134 if (home_variable_value.empty()) {
137 "map_io"),
"Map yaml file name starts with '~/' but no HOME variable set. \n"
138 <<
"[INFO] [map_io] User home dir will be not expanded \n");
139 return yaml_filename;
141 const std::string prefix{home_variable_value};
142 return yaml_filename.replace(0, 1, prefix);
145 LoadParameters loadMapYaml(
const std::string & yaml_filename)
147 YAML::Node doc = YAML::LoadFile(expand_user_home_dir_if_needed(yaml_filename, get_home_dir()));
148 LoadParameters load_parameters;
150 auto image_file_name = yaml_get_value<std::string>(doc,
"image");
151 if (image_file_name.empty()) {
152 throw YAML::Exception(doc[
"image"].Mark(),
"The image tag was empty.");
154 if (image_file_name[0] !=
'/') {
156 std::vector<char> fname_copy(yaml_filename.begin(), yaml_filename.end());
157 fname_copy.push_back(
'\0');
158 image_file_name = std::string(dirname(fname_copy.data())) +
'/' + image_file_name;
160 load_parameters.image_file_name = image_file_name;
162 load_parameters.resolution = yaml_get_value<double>(doc,
"resolution");
163 load_parameters.origin = yaml_get_value<std::vector<double>>(doc,
"origin");
164 if (load_parameters.origin.size() != 3) {
165 throw YAML::Exception(
166 doc[
"origin"].Mark(),
"value of the 'origin' tag should have 3 elements, not " +
167 std::to_string(load_parameters.origin.size()));
170 load_parameters.free_thresh = yaml_get_value<double>(doc,
"free_thresh");
171 load_parameters.occupied_thresh = yaml_get_value<double>(doc,
"occupied_thresh");
173 auto map_mode_node = doc[
"mode"];
174 if (!map_mode_node.IsDefined()) {
175 load_parameters.mode = MapMode::Trinary;
177 load_parameters.mode = map_mode_from_string(map_mode_node.as<std::string>());
181 load_parameters.negate = yaml_get_value<int>(doc,
"negate");
182 }
catch (YAML::Exception &) {
183 load_parameters.negate = yaml_get_value<bool>(doc,
"negate");
186 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"resolution: " << load_parameters.resolution);
187 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[0]: " << load_parameters.origin[0]);
188 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[1]: " << load_parameters.origin[1]);
189 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[2]: " << load_parameters.origin[2]);
190 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"free_thresh: " << load_parameters.free_thresh);
193 "map_io"),
"occupied_thresh: " << load_parameters.occupied_thresh);
195 rclcpp::get_logger(
"map_io"),
196 "mode: " << map_mode_to_string(load_parameters.mode));
197 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"negate: " << load_parameters.negate);
199 return load_parameters;
202 void loadMapFromFile(
203 const LoadParameters & load_parameters,
204 nav_msgs::msg::OccupancyGrid & map)
206 Magick::InitializeMagick(
nullptr);
207 nav_msgs::msg::OccupancyGrid msg;
210 rclcpp::get_logger(
"map_io"),
"Loading image_file: " <<
211 load_parameters.image_file_name);
212 Magick::Image img(load_parameters.image_file_name);
215 msg.info.width = img.size().width();
216 msg.info.height = img.size().height();
218 msg.info.resolution = load_parameters.resolution;
219 msg.info.origin.position.x = load_parameters.origin[0];
220 msg.info.origin.position.y = load_parameters.origin[1];
221 msg.info.origin.position.z = 0.0;
222 msg.info.origin.orientation = orientationAroundZAxis(load_parameters.origin[2]);
225 msg.data.resize(msg.info.width * msg.info.height);
228 for (
size_t y = 0; y < msg.info.height; y++) {
229 for (
size_t x = 0; x < msg.info.width; x++) {
230 auto pixel = img.pixelColor(x, y);
232 std::vector<Magick::Quantum> channels = {pixel.redQuantum(), pixel.greenQuantum(),
233 pixel.blueQuantum()};
234 if (load_parameters.mode == MapMode::Trinary && img.matte()) {
237 channels.push_back(MaxRGB - pixel.alphaQuantum());
240 for (
auto c : channels) {
244 double shade = Magick::ColorGray::scaleQuantumToDouble(sum / channels.size());
249 double occ = (load_parameters.negate ? shade : 1.0 - shade);
252 switch (load_parameters.mode) {
253 case MapMode::Trinary:
254 if (load_parameters.occupied_thresh < occ) {
255 map_cell = nav2_util::OCC_GRID_OCCUPIED;
256 }
else if (occ < load_parameters.free_thresh) {
257 map_cell = nav2_util::OCC_GRID_FREE;
259 map_cell = nav2_util::OCC_GRID_UNKNOWN;
263 if (pixel.alphaQuantum() != OpaqueOpacity) {
264 map_cell = nav2_util::OCC_GRID_UNKNOWN;
265 }
else if (load_parameters.occupied_thresh < occ) {
266 map_cell = nav2_util::OCC_GRID_OCCUPIED;
267 }
else if (occ < load_parameters.free_thresh) {
268 map_cell = nav2_util::OCC_GRID_FREE;
270 map_cell = std::rint(
271 (occ - load_parameters.free_thresh) /
272 (load_parameters.occupied_thresh - load_parameters.free_thresh) * 100.0);
276 double occ_percent = std::round(shade * 255);
277 if (nav2_util::OCC_GRID_FREE <= occ_percent &&
278 occ_percent <= nav2_util::OCC_GRID_OCCUPIED)
280 map_cell =
static_cast<int8_t
>(occ_percent);
282 map_cell = nav2_util::OCC_GRID_UNKNOWN;
287 throw std::runtime_error(
"Invalid map mode");
289 msg.data[msg.info.width * (msg.info.height - y - 1) + x] = map_cell;
294 rclcpp::Clock clock(RCL_SYSTEM_TIME);
295 msg.info.map_load_time = clock.now();
296 msg.header.frame_id =
"map";
297 msg.header.stamp = clock.now();
301 "map_io"),
"Read map " << load_parameters.image_file_name
302 <<
": " << msg.info.width <<
" X " << msg.info.height <<
" map @ "
303 << msg.info.resolution <<
" m/cell");
308 LOAD_MAP_STATUS loadMapFromYaml(
309 const std::string & yaml_file,
310 nav_msgs::msg::OccupancyGrid & map)
312 if (yaml_file.empty()) {
313 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"YAML file name is empty, can't load!");
314 return MAP_DOES_NOT_EXIST;
316 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Loading yaml file: " << yaml_file);
317 LoadParameters load_parameters;
319 load_parameters = loadMapYaml(yaml_file);
320 }
catch (YAML::Exception & e) {
323 "map_io"),
"Failed processing YAML file " << yaml_file <<
" at position (" <<
324 e.mark.line <<
":" << e.mark.column <<
") for reason: " << e.what());
325 return INVALID_MAP_METADATA;
326 }
catch (std::exception & e) {
328 rclcpp::get_logger(
"map_io"),
"Failed to parse map YAML loaded from file " << yaml_file <<
329 " for reason: " << e.what());
330 return INVALID_MAP_METADATA;
333 loadMapFromFile(load_parameters, map);
334 }
catch (std::exception & e) {
337 "map_io"),
"Failed to load image file " << load_parameters.image_file_name <<
338 " for reason: " << e.what());
339 return INVALID_MAP_DATA;
342 return LOAD_MAP_SUCCESS;
353 void checkSaveParameters(SaveParameters & save_parameters)
356 Magick::InitializeMagick(
nullptr);
359 if (save_parameters.map_file_name ==
"") {
360 rclcpp::Clock clock(RCL_SYSTEM_TIME);
361 save_parameters.map_file_name =
"map_" +
362 std::to_string(
static_cast<int>(clock.now().seconds()));
364 rclcpp::get_logger(
"map_io"),
"Map file unspecified. Map will be saved to " <<
365 save_parameters.map_file_name <<
" file");
369 if (save_parameters.occupied_thresh == 0.0) {
370 save_parameters.occupied_thresh = 0.65;
373 "map_io"),
"Occupied threshold unspecified. Setting it to default value: " <<
374 save_parameters.occupied_thresh);
376 if (save_parameters.free_thresh == 0.0) {
377 save_parameters.free_thresh = 0.25;
379 rclcpp::get_logger(
"map_io"),
"Free threshold unspecified. Setting it to default value: " <<
380 save_parameters.free_thresh);
382 if (1.0 < save_parameters.occupied_thresh) {
383 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Threshold_occupied must be 1.0 or less");
384 throw std::runtime_error(
"Incorrect thresholds");
386 if (save_parameters.free_thresh < 0.0) {
387 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Free threshold must be 0.0 or greater");
388 throw std::runtime_error(
"Incorrect thresholds");
390 if (save_parameters.occupied_thresh <= save_parameters.free_thresh) {
393 "map_io"),
"Threshold_free must be smaller than threshold_occupied");
394 throw std::runtime_error(
"Incorrect thresholds");
398 if (save_parameters.image_format ==
"") {
399 save_parameters.image_format = save_parameters.mode == MapMode::Scale ?
"png" :
"pgm";
401 rclcpp::get_logger(
"map_io"),
"Image format unspecified. Setting it to: " <<
402 save_parameters.image_format);
406 save_parameters.image_format.begin(),
407 save_parameters.image_format.end(),
408 save_parameters.image_format.begin(),
409 [](
unsigned char c) {return std::tolower(c);});
411 const std::vector<std::string> BLESSED_FORMATS{
"bmp",
"pgm",
"png"};
413 std::find(BLESSED_FORMATS.begin(), BLESSED_FORMATS.end(), save_parameters.image_format) ==
414 BLESSED_FORMATS.end())
416 std::stringstream ss;
418 for (
auto & format_name : BLESSED_FORMATS) {
422 ss <<
"'" << format_name <<
"'";
426 rclcpp::get_logger(
"map_io"),
"Requested image format '" << save_parameters.image_format <<
427 "' is not one of the recommended formats: " << ss.str());
429 const std::string FALLBACK_FORMAT =
"png";
432 Magick::CoderInfo info(save_parameters.image_format);
433 if (!info.isWritable()) {
435 rclcpp::get_logger(
"map_io"),
"Format '" << save_parameters.image_format <<
436 "' is not writable. Using '" << FALLBACK_FORMAT <<
"' instead");
437 save_parameters.image_format = FALLBACK_FORMAT;
439 }
catch (Magick::ErrorOption & e) {
442 "map_io"),
"Format '" << save_parameters.image_format <<
"' is not usable. Using '" <<
443 FALLBACK_FORMAT <<
"' instead:" << std::endl << e.what());
444 save_parameters.image_format = FALLBACK_FORMAT;
449 save_parameters.mode == MapMode::Scale &&
450 (save_parameters.image_format ==
"pgm" ||
451 save_parameters.image_format ==
"jpg" ||
452 save_parameters.image_format ==
"jpeg"))
455 rclcpp::get_logger(
"map_io"),
"Map mode 'scale' requires transparency, but format '" <<
456 save_parameters.image_format <<
457 "' does not support it. Consider switching image format to 'png'.");
467 void tryWriteMapToFile(
468 const nav_msgs::msg::OccupancyGrid & map,
469 const SaveParameters & save_parameters)
473 "map_io"),
"Received a " << map.info.width <<
" X " << map.info.height <<
" map @ " <<
474 map.info.resolution <<
" m/pix");
476 std::string mapdatafile = save_parameters.map_file_name +
"." + save_parameters.image_format;
479 Magick::Image image({map.info.width, map.info.height},
"red");
485 save_parameters.mode == MapMode::Scale ?
486 Magick::TrueColorMatteType : Magick::GrayscaleType);
491 int free_thresh_int = std::rint(save_parameters.free_thresh * 100.0);
492 int occupied_thresh_int = std::rint(save_parameters.occupied_thresh * 100.0);
494 for (
size_t y = 0; y < map.info.height; y++) {
495 for (
size_t x = 0; x < map.info.width; x++) {
496 int8_t map_cell = map.data[map.info.width * (map.info.height - y - 1) + x];
500 switch (save_parameters.mode) {
501 case MapMode::Trinary:
502 if (map_cell < 0 || 100 < map_cell) {
503 pixel = Magick::ColorGray(205 / 255.0);
504 }
else if (map_cell <= free_thresh_int) {
505 pixel = Magick::ColorGray(254 / 255.0);
506 }
else if (occupied_thresh_int <= map_cell) {
507 pixel = Magick::ColorGray(0 / 255.0);
509 pixel = Magick::ColorGray(205 / 255.0);
513 if (map_cell < 0 || 100 < map_cell) {
514 pixel = Magick::ColorGray{0.5};
515 pixel.alphaQuantum(TransparentOpacity);
517 pixel = Magick::ColorGray{(100.0 - map_cell) / 100.0};
522 if (map_cell < 0 || 100 < map_cell) {
525 q = map_cell / 255.0 * MaxRGB;
527 pixel = Magick::Color(q, q, q);
532 "map_io"),
"Map mode should be Trinary, Scale or Raw");
533 throw std::runtime_error(
"Invalid map mode");
535 image.pixelColor(x, y, pixel);
540 rclcpp::get_logger(
"map_io"),
541 "Writing map occupancy data to " << mapdatafile);
542 image.write(mapdatafile);
545 std::string mapmetadatafile = save_parameters.map_file_name +
".yaml";
547 std::ofstream yaml(mapmetadatafile);
549 geometry_msgs::msg::Quaternion orientation = map.info.origin.orientation;
550 tf2::Matrix3x3 mat(tf2::Quaternion(orientation.x, orientation.y, orientation.z, orientation.w));
551 double yaw, pitch, roll;
552 mat.getEulerYPR(yaw, pitch, roll);
554 const int file_name_index = mapdatafile.find_last_of(
"/\\");
555 std::string image_name = mapdatafile.substr(file_name_index + 1);
558 e << YAML::Precision(3);
560 e << YAML::Key <<
"image" << YAML::Value << image_name;
561 e << YAML::Key <<
"mode" << YAML::Value << map_mode_to_string(save_parameters.mode);
562 e << YAML::Key <<
"resolution" << YAML::Value << map.info.resolution;
563 e << YAML::Key <<
"origin" << YAML::Flow << YAML::BeginSeq << map.info.origin.position.x <<
564 map.info.origin.position.y << yaw << YAML::EndSeq;
565 e << YAML::Key <<
"negate" << YAML::Value << 0;
566 e << YAML::Key <<
"occupied_thresh" << YAML::Value << save_parameters.occupied_thresh;
567 e << YAML::Key <<
"free_thresh" << YAML::Value << save_parameters.free_thresh;
571 rclcpp::get_logger(
"map_io"),
"YAML writer failed with an error " << e.GetLastError() <<
572 ". The map metadata may be invalid.");
575 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Writing map metadata to " << mapmetadatafile);
576 std::ofstream(mapmetadatafile) << e.c_str();
578 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Map saved");
582 const nav_msgs::msg::OccupancyGrid & map,
583 const SaveParameters & save_parameters)
586 SaveParameters save_parameters_loc = save_parameters;
590 checkSaveParameters(save_parameters_loc);
592 tryWriteMapToFile(map, save_parameters_loc);
593 }
catch (std::exception & e) {
595 rclcpp::get_logger(
"map_io"),
596 "Failed to write map for reason: " << e.what());