32 #include "nav2_map_server/map_io.hpp"
33 #include "nav2_ros_common/validate_messages.hpp"
34 #include "rclcpp/clock.hpp"
35 #include "rclcpp/logger.hpp"
36 #include "rclcpp/logging.hpp"
42 #include <Eigen/Dense>
54 #include "nav2_util/geometry_utils.hpp"
56 #include "yaml-cpp/yaml.h"
58 #include "tf2/LinearMath/Matrix3x3.hpp"
59 #include "tf2/LinearMath/Quaternion.hpp"
60 #include "nav2_util/occ_grid_values.hpp"
65 char * dirname(
char * path)
67 static const char dot[] =
".";
77 if (*c ==
'\\') {*c =
'/';}
82 last_slash = path != NULL ? strrchr(path,
'/') : NULL;
84 if (last_slash != NULL && last_slash == path) {
88 }
else if (last_slash != NULL && last_slash[1] ==
'\0') {
90 last_slash =
reinterpret_cast<char *
>(memchr(path, last_slash - path,
'/'));
93 if (last_slash != NULL) {
100 path =
const_cast<char *
>(dot);
107 namespace nav2_map_server
109 using nav2_util::geometry_utils::orientationAroundZAxis;
118 T yaml_get_value(
const YAML::Node & node,
const std::string & key)
121 return node[key].as<T>();
122 }
catch (YAML::Exception & e) {
123 std::stringstream ss;
124 ss <<
"Failed to parse YAML tag '" << key <<
"' for reason: " << e.msg;
125 throw YAML::Exception(e.mark, ss.str());
129 std::string get_home_dir()
131 if (
const char * home_dir = std::getenv(
"HOME")) {
132 return std::string{home_dir};
134 return std::string{};
137 std::string expand_user_home_dir_if_needed(
138 std::string yaml_filename,
139 std::string home_variable_value)
141 if (yaml_filename.size() < 2 || !(yaml_filename[0] ==
'~' && yaml_filename[1] ==
'/')) {
142 return yaml_filename;
144 if (home_variable_value.empty()) {
147 "map_io"),
"Map yaml file name starts with '~/' but no HOME variable set. \n"
148 <<
"[INFO] [map_io] User home dir will be not expanded \n");
149 return yaml_filename;
151 const std::string prefix{home_variable_value};
152 return yaml_filename.replace(0, 1, prefix);
155 LoadParameters loadMapYaml(
const std::string & yaml_filename)
157 const auto expanded_yaml_filename =
158 expand_user_home_dir_if_needed(yaml_filename, get_home_dir());
159 YAML::Node doc = YAML::LoadFile(expanded_yaml_filename);
160 LoadParameters load_parameters;
162 auto image_file_name = yaml_get_value<std::string>(doc,
"image");
163 if (image_file_name.empty()) {
164 throw YAML::Exception(doc[
"image"].Mark(),
"The image tag was empty.");
166 if (image_file_name[0] !=
'/') {
168 std::vector<char> fname_copy(expanded_yaml_filename.begin(), expanded_yaml_filename.end());
169 fname_copy.push_back(
'\0');
170 image_file_name = std::string(dirname(fname_copy.data())) +
'/' + image_file_name;
172 load_parameters.image_file_name = image_file_name;
174 load_parameters.resolution = yaml_get_value<double>(doc,
"resolution");
175 load_parameters.origin = yaml_get_value<std::vector<double>>(doc,
"origin");
176 if (load_parameters.origin.size() != 3) {
177 throw YAML::Exception(
178 doc[
"origin"].Mark(),
"value of the 'origin' tag should have 3 elements, not " +
179 std::to_string(load_parameters.origin.size()));
182 load_parameters.free_thresh = yaml_get_value<double>(doc,
"free_thresh");
183 load_parameters.occupied_thresh = yaml_get_value<double>(doc,
"occupied_thresh");
185 auto map_mode_node = doc[
"mode"];
186 if (!map_mode_node.IsDefined()) {
187 load_parameters.mode = MapMode::Trinary;
189 load_parameters.mode = map_mode_from_string(map_mode_node.as<std::string>());
193 load_parameters.negate = yaml_get_value<int>(doc,
"negate");
194 }
catch (YAML::Exception &) {
195 load_parameters.negate = yaml_get_value<bool>(doc,
"negate");
198 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"resolution: " << load_parameters.resolution);
199 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[0]: " << load_parameters.origin[0]);
200 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[1]: " << load_parameters.origin[1]);
201 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[2]: " << load_parameters.origin[2]);
202 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"free_thresh: " << load_parameters.free_thresh);
205 "map_io"),
"occupied_thresh: " << load_parameters.occupied_thresh);
207 rclcpp::get_logger(
"map_io"),
208 "mode: " << map_mode_to_string(load_parameters.mode));
209 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"negate: " << load_parameters.negate);
211 return load_parameters;
214 void loadMapFromFile(
215 const LoadParameters & load_parameters,
216 nav_msgs::msg::OccupancyGrid & map)
218 Magick::InitializeMagick(
nullptr);
219 nav_msgs::msg::OccupancyGrid msg;
222 rclcpp::get_logger(
"map_io"),
"Loading image_file: " <<
223 load_parameters.image_file_name);
224 Magick::Image img(load_parameters.image_file_name);
227 msg.info.width = img.size().width();
228 msg.info.height = img.size().height();
230 msg.info.resolution = load_parameters.resolution;
231 msg.info.origin.position.x = load_parameters.origin[0];
232 msg.info.origin.position.y = load_parameters.origin[1];
233 msg.info.origin.position.z = 0.0;
234 msg.info.origin.orientation = orientationAroundZAxis(load_parameters.origin[2]);
237 msg.data.resize(msg.info.width * msg.info.height);
240 Magick::Image gray = img;
241 gray.type(Magick::GrayscaleType);
244 size_t width = gray.columns();
245 size_t height = gray.rows();
247 std::vector<uint8_t> buffer(width * height);
248 gray.write(0, 0, width, height,
"I", Magick::CharPixel, buffer.data());
251 Eigen::Map<Eigen::Matrix<uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
252 gray_matrix(buffer.data(), height, width);
254 bool has_alpha = img.matte();
259 Eigen::Map<Eigen::Matrix<int8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
260 output_map(msg.data.data(), height, width);
266 std::array<int8_t, 256> lut;
268 if (load_parameters.mode == MapMode::Trinary || load_parameters.mode == MapMode::Scale) {
273 const float free_thresh =
static_cast<float>(load_parameters.free_thresh);
274 const float occupied_thresh =
static_cast<float>(load_parameters.occupied_thresh);
275 const float scale_span =
276 static_cast<float>(load_parameters.occupied_thresh - load_parameters.free_thresh);
278 for (
int g = 0; g < 256; ++g) {
279 float occ =
static_cast<float>(g) / 255.0f;
280 if (!load_parameters.negate) {
284 int8_t value = nav2_util::OCC_GRID_UNKNOWN;
286 if (occ >= occupied_thresh) {
287 value = nav2_util::OCC_GRID_OCCUPIED;
289 if (occ <= free_thresh) {
290 value = nav2_util::OCC_GRID_FREE;
292 if (load_parameters.mode == MapMode::Scale &&
293 occ > free_thresh && occ < occupied_thresh)
295 value =
static_cast<int8_t
>(std::round(((occ - free_thresh) / scale_span) * 100.0f));
300 output_map = gray_matrix.unaryExpr([&lut](uint8_t g) -> int8_t {
return lut[g];})
301 .colwise().reverse();
306 std::vector<uint8_t> alpha_buf(width * height);
307 img.write(0, 0, width, height,
"A", Magick::CharPixel, alpha_buf.data());
310 Eigen::Map<Eigen::Array<uint8_t, Eigen::Dynamic, Eigen::Dynamic,
311 Eigen::RowMajor>> alpha_array(
312 alpha_buf.data(), height, width);
315 output_map = (alpha_array.colwise().reverse() < 255)
316 .select(nav2_util::OCC_GRID_UNKNOWN, output_map);
319 }
else if (load_parameters.mode == MapMode::Raw) {
323 for (
int g = 0; g < 256; ++g) {
324 const int8_t value =
static_cast<int8_t
>(g);
325 const bool in_bounds = value >= nav2_util::OCC_GRID_FREE &&
326 value <= nav2_util::OCC_GRID_OCCUPIED;
327 lut[g] = in_bounds ? value : nav2_util::OCC_GRID_UNKNOWN;
330 output_map = gray_matrix.unaryExpr([&lut](uint8_t g) -> int8_t {
return lut[g];})
331 .colwise().reverse();
335 throw std::runtime_error(
"Invalid map mode");
339 rclcpp::Clock clock(RCL_SYSTEM_TIME);
340 msg.info.map_load_time = clock.now();
341 msg.header.frame_id =
"map";
342 msg.header.stamp = clock.now();
346 "map_io"),
"Read map " << load_parameters.image_file_name
347 <<
": " << msg.info.width <<
" X " << msg.info.height <<
" map @ "
348 << msg.info.resolution <<
" m/cell");
353 LOAD_MAP_STATUS loadMapFromYaml(
354 const std::string & yaml_file,
355 nav_msgs::msg::OccupancyGrid & map)
357 if (yaml_file.empty()) {
358 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"YAML file name is empty, can't load!");
359 return MAP_DOES_NOT_EXIST;
361 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Loading yaml file: " << yaml_file);
362 LoadParameters load_parameters;
364 load_parameters = loadMapYaml(yaml_file);
365 }
catch (YAML::Exception & e) {
368 "map_io"),
"Failed processing YAML file " << yaml_file <<
" at position (" <<
369 e.mark.line <<
":" << e.mark.column <<
") for reason: " << e.what());
370 return INVALID_MAP_METADATA;
371 }
catch (std::exception & e) {
373 rclcpp::get_logger(
"map_io"),
"Failed to parse map YAML loaded from file " << yaml_file <<
374 " for reason: " << e.what());
375 return INVALID_MAP_METADATA;
378 loadMapFromFile(load_parameters, map);
379 }
catch (std::exception & e) {
382 "map_io"),
"Failed to load image file " << load_parameters.image_file_name <<
383 " for reason: " << e.what());
384 return INVALID_MAP_DATA;
387 return LOAD_MAP_SUCCESS;
398 void checkSaveParameters(SaveParameters & save_parameters)
401 Magick::InitializeMagick(
nullptr);
404 if (save_parameters.map_file_name ==
"") {
405 rclcpp::Clock clock(RCL_SYSTEM_TIME);
406 save_parameters.map_file_name =
"map_" +
407 std::to_string(
static_cast<int>(clock.now().seconds()));
409 rclcpp::get_logger(
"map_io"),
"Map file unspecified. Map will be saved to " <<
410 save_parameters.map_file_name <<
" file");
414 if (save_parameters.occupied_thresh == 0.0) {
415 save_parameters.occupied_thresh = 0.65;
418 "map_io"),
"Occupied threshold unspecified. Setting it to default value: " <<
419 save_parameters.occupied_thresh);
421 if (save_parameters.free_thresh == 0.0) {
422 save_parameters.free_thresh = 0.25;
424 rclcpp::get_logger(
"map_io"),
"Free threshold unspecified. Setting it to default value: " <<
425 save_parameters.free_thresh);
427 if (1.0 < save_parameters.occupied_thresh) {
428 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Threshold_occupied must be 1.0 or less");
429 throw std::runtime_error(
"Incorrect thresholds");
431 if (save_parameters.free_thresh < 0.0) {
432 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Free threshold must be 0.0 or greater");
433 throw std::runtime_error(
"Incorrect thresholds");
435 if (save_parameters.occupied_thresh <= save_parameters.free_thresh) {
438 "map_io"),
"Threshold_free must be smaller than threshold_occupied");
439 throw std::runtime_error(
"Incorrect thresholds");
443 if (save_parameters.image_format ==
"") {
444 save_parameters.image_format = save_parameters.mode == MapMode::Scale ?
"png" :
"pgm";
446 rclcpp::get_logger(
"map_io"),
"Image format unspecified. Setting it to: " <<
447 save_parameters.image_format);
451 save_parameters.image_format.begin(),
452 save_parameters.image_format.end(),
453 save_parameters.image_format.begin(),
454 [](
unsigned char c) {return std::tolower(c);});
456 const std::vector<std::string> BLESSED_FORMATS{
"bmp",
"pgm",
"png"};
458 std::find(BLESSED_FORMATS.begin(), BLESSED_FORMATS.end(), save_parameters.image_format) ==
459 BLESSED_FORMATS.end())
461 std::stringstream ss;
463 for (
auto & format_name : BLESSED_FORMATS) {
467 ss <<
"'" << format_name <<
"'";
471 rclcpp::get_logger(
"map_io"),
"Requested image format '" << save_parameters.image_format <<
472 "' is not one of the recommended formats: " << ss.str());
474 const std::string FALLBACK_FORMAT =
"png";
477 Magick::CoderInfo info(save_parameters.image_format);
478 if (!info.isWritable()) {
480 rclcpp::get_logger(
"map_io"),
"Format '" << save_parameters.image_format <<
481 "' is not writable. Using '" << FALLBACK_FORMAT <<
"' instead");
482 save_parameters.image_format = FALLBACK_FORMAT;
484 }
catch (Magick::ErrorOption & e) {
487 "map_io"),
"Format '" << save_parameters.image_format <<
"' is not usable. Using '" <<
488 FALLBACK_FORMAT <<
"' instead:" << std::endl << e.what());
489 save_parameters.image_format = FALLBACK_FORMAT;
494 save_parameters.mode == MapMode::Scale &&
495 (save_parameters.image_format ==
"pgm" ||
496 save_parameters.image_format ==
"jpg" ||
497 save_parameters.image_format ==
"jpeg"))
500 rclcpp::get_logger(
"map_io"),
"Map mode 'scale' requires transparency, but format '" <<
501 save_parameters.image_format <<
502 "' does not support it. Consider switching image format to 'png'.");
512 void tryWriteMapToFile(
513 const nav_msgs::msg::OccupancyGrid & map,
514 const SaveParameters & save_parameters)
518 "map_io"),
"Received a " << map.info.width <<
" X " << map.info.height <<
" map @ " <<
519 map.info.resolution <<
" m/pix");
521 std::string mapdatafile = save_parameters.map_file_name +
"." + save_parameters.image_format;
524 Magick::Image image({map.info.width, map.info.height},
"red");
530 save_parameters.mode == MapMode::Scale ?
531 Magick::TrueColorMatteType : Magick::GrayscaleType);
536 int free_thresh_int = std::rint(save_parameters.free_thresh * 100.0);
537 int occupied_thresh_int = std::rint(save_parameters.occupied_thresh * 100.0);
539 for (
size_t y = 0; y < map.info.height; y++) {
540 for (
size_t x = 0; x < map.info.width; x++) {
541 int8_t map_cell = map.data[map.info.width * (map.info.height - y - 1) + x];
545 switch (save_parameters.mode) {
546 case MapMode::Trinary:
547 if (map_cell < 0 || 100 < map_cell) {
548 pixel = Magick::ColorGray(205 / 255.0);
549 }
else if (map_cell <= free_thresh_int) {
550 pixel = Magick::ColorGray(254 / 255.0);
551 }
else if (occupied_thresh_int <= map_cell) {
552 pixel = Magick::ColorGray(0 / 255.0);
554 pixel = Magick::ColorGray(205 / 255.0);
558 if (map_cell < 0 || 100 < map_cell) {
559 pixel = Magick::ColorGray{0.5};
560 pixel.alphaQuantum(TransparentOpacity);
562 pixel = Magick::ColorGray{(100.0 - map_cell) / 100.0};
567 if (map_cell < 0 || 100 < map_cell) {
570 q = map_cell / 255.0 * MaxRGB;
572 pixel = Magick::Color(q, q, q);
577 "map_io"),
"Map mode should be Trinary, Scale or Raw");
578 throw std::runtime_error(
"Invalid map mode");
580 image.pixelColor(x, y, pixel);
585 rclcpp::get_logger(
"map_io"),
586 "Writing map occupancy data to " << mapdatafile);
587 image.write(mapdatafile);
590 std::string mapmetadatafile = save_parameters.map_file_name +
".yaml";
592 std::ofstream yaml(mapmetadatafile);
594 geometry_msgs::msg::Quaternion orientation = map.info.origin.orientation;
595 tf2::Matrix3x3 mat(tf2::Quaternion(orientation.x, orientation.y, orientation.z, orientation.w));
596 double yaw, pitch, roll;
597 mat.getEulerYPR(yaw, pitch, roll);
599 const int file_name_index = mapdatafile.find_last_of(
"/\\");
600 std::string image_name = mapdatafile.substr(file_name_index + 1);
603 e << YAML::Precision(7);
605 e << YAML::Key <<
"image" << YAML::Value << image_name;
606 e << YAML::Key <<
"mode" << YAML::Value << map_mode_to_string(save_parameters.mode);
607 e << YAML::Key <<
"resolution" << YAML::Value << to_string_with_precision(
610 e << YAML::Key <<
"origin" << YAML::Flow << YAML::BeginSeq <<
611 to_string_with_precision(map.info.origin.position.x, 3) <<
612 to_string_with_precision(map.info.origin.position.y, 3) << yaw << YAML::EndSeq;
613 e << YAML::Key <<
"negate" << YAML::Value << 0;
615 if (save_parameters.mode == MapMode::Trinary) {
620 e << YAML::Key <<
"occupied_thresh" << YAML::Value << 0.65;
621 e << YAML::Key <<
"free_thresh" << YAML::Value << 0.196;
623 e << YAML::Key <<
"occupied_thresh" << YAML::Value <<
624 to_string_with_precision(save_parameters.occupied_thresh, 3);
625 e << YAML::Key <<
"free_thresh" << YAML::Value <<
626 to_string_with_precision(save_parameters.free_thresh, 3);
631 rclcpp::get_logger(
"map_io"),
"YAML writer failed with an error " << e.GetLastError() <<
632 ". The map metadata may be invalid.");
635 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Writing map metadata to " << mapmetadatafile);
636 std::ofstream(mapmetadatafile) << e.c_str();
638 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Map saved");
642 const nav_msgs::msg::OccupancyGrid & map,
643 const SaveParameters & save_parameters)
645 if (!nav2::validateMsg(map)) {
647 rclcpp::get_logger(
"map_io"),
648 "Failed to write map for reason: invalid OccupancyGrid message");
653 SaveParameters save_parameters_loc = save_parameters;
657 checkSaveParameters(save_parameters_loc);
659 tryWriteMapToFile(map, save_parameters_loc);
660 }
catch (std::exception & e) {
662 rclcpp::get_logger(
"map_io"),
663 "Failed to write map for reason: " << e.what());
669 std::string to_string_with_precision(
double value,
int precision)
671 std::ostringstream out;
672 out << std::fixed << std::setprecision(precision) << value;