32 #include "nav2_map_server/map_io.hpp"
33 #include "nav2_ros_common/validate_messages.hpp"
34 #include "rclcpp/rclcpp.hpp"
40 #include <Eigen/Dense>
50 #include "nav2_util/geometry_utils.hpp"
52 #include "yaml-cpp/yaml.h"
54 #include "tf2/LinearMath/Matrix3x3.hpp"
55 #include "tf2/LinearMath/Quaternion.hpp"
56 #include "nav2_util/occ_grid_values.hpp"
61 char * dirname(
char * path)
63 static const char dot[] =
".";
73 if (*c ==
'\\') {*c =
'/';}
78 last_slash = path != NULL ? strrchr(path,
'/') : NULL;
80 if (last_slash != NULL && last_slash == path) {
84 }
else if (last_slash != NULL && last_slash[1] ==
'\0') {
86 last_slash =
reinterpret_cast<char *
>(memchr(path, last_slash - path,
'/'));
89 if (last_slash != NULL) {
96 path =
const_cast<char *
>(dot);
103 namespace nav2_map_server
105 using nav2_util::geometry_utils::orientationAroundZAxis;
114 T yaml_get_value(
const YAML::Node & node,
const std::string & key)
117 return node[key].as<T>();
118 }
catch (YAML::Exception & e) {
119 std::stringstream ss;
120 ss <<
"Failed to parse YAML tag '" << key <<
"' for reason: " << e.msg;
121 throw YAML::Exception(e.mark, ss.str());
125 std::string get_home_dir()
127 if (
const char * home_dir = std::getenv(
"HOME")) {
128 return std::string{home_dir};
130 return std::string{};
133 std::string expand_user_home_dir_if_needed(
134 std::string yaml_filename,
135 std::string home_variable_value)
137 if (yaml_filename.size() < 2 || !(yaml_filename[0] ==
'~' && yaml_filename[1] ==
'/')) {
138 return yaml_filename;
140 if (home_variable_value.empty()) {
143 "map_io"),
"Map yaml file name starts with '~/' but no HOME variable set. \n"
144 <<
"[INFO] [map_io] User home dir will be not expanded \n");
145 return yaml_filename;
147 const std::string prefix{home_variable_value};
148 return yaml_filename.replace(0, 1, prefix);
151 LoadParameters loadMapYaml(
const std::string & yaml_filename)
153 YAML::Node doc = YAML::LoadFile(expand_user_home_dir_if_needed(yaml_filename, get_home_dir()));
154 LoadParameters load_parameters;
156 auto image_file_name = yaml_get_value<std::string>(doc,
"image");
157 if (image_file_name.empty()) {
158 throw YAML::Exception(doc[
"image"].Mark(),
"The image tag was empty.");
160 if (image_file_name[0] !=
'/') {
162 std::vector<char> fname_copy(yaml_filename.begin(), yaml_filename.end());
163 fname_copy.push_back(
'\0');
164 image_file_name = std::string(dirname(fname_copy.data())) +
'/' + image_file_name;
166 load_parameters.image_file_name = image_file_name;
168 load_parameters.resolution = yaml_get_value<double>(doc,
"resolution");
169 load_parameters.origin = yaml_get_value<std::vector<double>>(doc,
"origin");
170 if (load_parameters.origin.size() != 3) {
171 throw YAML::Exception(
172 doc[
"origin"].Mark(),
"value of the 'origin' tag should have 3 elements, not " +
173 std::to_string(load_parameters.origin.size()));
176 load_parameters.free_thresh = yaml_get_value<double>(doc,
"free_thresh");
177 load_parameters.occupied_thresh = yaml_get_value<double>(doc,
"occupied_thresh");
179 auto map_mode_node = doc[
"mode"];
180 if (!map_mode_node.IsDefined()) {
181 load_parameters.mode = MapMode::Trinary;
183 load_parameters.mode = map_mode_from_string(map_mode_node.as<std::string>());
187 load_parameters.negate = yaml_get_value<int>(doc,
"negate");
188 }
catch (YAML::Exception &) {
189 load_parameters.negate = yaml_get_value<bool>(doc,
"negate");
192 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"resolution: " << load_parameters.resolution);
193 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[0]: " << load_parameters.origin[0]);
194 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[1]: " << load_parameters.origin[1]);
195 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"origin[2]: " << load_parameters.origin[2]);
196 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"free_thresh: " << load_parameters.free_thresh);
199 "map_io"),
"occupied_thresh: " << load_parameters.occupied_thresh);
201 rclcpp::get_logger(
"map_io"),
202 "mode: " << map_mode_to_string(load_parameters.mode));
203 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"negate: " << load_parameters.negate);
205 return load_parameters;
208 void loadMapFromFile(
209 const LoadParameters & load_parameters,
210 nav_msgs::msg::OccupancyGrid & map)
212 Magick::InitializeMagick(
nullptr);
213 nav_msgs::msg::OccupancyGrid msg;
216 rclcpp::get_logger(
"map_io"),
"Loading image_file: " <<
217 load_parameters.image_file_name);
218 Magick::Image img(load_parameters.image_file_name);
221 msg.info.width = img.size().width();
222 msg.info.height = img.size().height();
224 msg.info.resolution = load_parameters.resolution;
225 msg.info.origin.position.x = load_parameters.origin[0];
226 msg.info.origin.position.y = load_parameters.origin[1];
227 msg.info.origin.position.z = 0.0;
228 msg.info.origin.orientation = orientationAroundZAxis(load_parameters.origin[2]);
231 msg.data.resize(msg.info.width * msg.info.height);
234 Magick::Image gray = img;
235 gray.type(Magick::GrayscaleType);
238 size_t width = gray.columns();
239 size_t height = gray.rows();
241 std::vector<uint8_t> buffer(width * height);
242 gray.write(0, 0, width, height,
"I", Magick::CharPixel, buffer.data());
245 Eigen::Map<Eigen::Matrix<uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
246 gray_matrix(buffer.data(), height, width);
248 bool has_alpha = img.matte();
254 Eigen::Matrix<int8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> result(height, width);
256 if (load_parameters.mode == MapMode::Trinary || load_parameters.mode == MapMode::Scale) {
258 Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic,
259 Eigen::RowMajor> normalized = gray_matrix.cast<
float>() / 255.0f;
262 if (!load_parameters.negate) {
263 normalized = (1.0f - normalized.array()).matrix();
267 Eigen::Matrix<uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> occupied =
268 (normalized.array() >= load_parameters.occupied_thresh).cast<uint8_t>();
270 Eigen::Matrix<uint8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> free =
271 (normalized.array() <= load_parameters.free_thresh).cast<uint8_t>();
274 result.setConstant(nav2_util::OCC_GRID_UNKNOWN);
277 result = (occupied.array() > 0).select(nav2_util::OCC_GRID_OCCUPIED, result);
278 result = (free.array() > 0).select(nav2_util::OCC_GRID_FREE, result);
281 if (load_parameters.mode == MapMode::Scale) {
283 auto in_between_mask = (normalized.array() > load_parameters.free_thresh) &&
284 (normalized.array() < load_parameters.occupied_thresh);
286 if (in_between_mask.any()) {
288 Eigen::ArrayXXf scaled_float = ((normalized.array() - load_parameters.free_thresh) /
289 (load_parameters.occupied_thresh - load_parameters.free_thresh)) * 100.0f;
292 Eigen::Matrix<int8_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> scaled_int =
293 scaled_float.array().round().cast<int8_t>();
295 result = in_between_mask.select(scaled_int, result);
302 std::vector<uint8_t> alpha_buf(width * height);
303 img.write(0, 0, width, height,
"A", Magick::CharPixel, alpha_buf.data());
306 Eigen::Map<Eigen::Array<uint8_t, Eigen::Dynamic, Eigen::Dynamic,
307 Eigen::RowMajor>> alpha_array(
308 alpha_buf.data(), height, width);
311 result = (alpha_array < 255).select(nav2_util::OCC_GRID_UNKNOWN, result);
314 }
else if (load_parameters.mode == MapMode::Raw) {
316 result = gray_matrix.cast<int8_t>();
319 auto out_of_bounds = (result.array() < nav2_util::OCC_GRID_FREE) ||
320 (result.array() > nav2_util::OCC_GRID_OCCUPIED);
322 result = out_of_bounds.select(nav2_util::OCC_GRID_UNKNOWN, result);
326 throw std::runtime_error(
"Invalid map mode");
330 Eigen::Matrix<int8_t, Eigen::Dynamic, Eigen::Dynamic,
331 Eigen::RowMajor> flipped = result.colwise().reverse();
332 std::memcpy(msg.data.data(), flipped.data(), width * height);
335 rclcpp::Clock clock(RCL_SYSTEM_TIME);
336 msg.info.map_load_time = clock.now();
337 msg.header.frame_id =
"map";
338 msg.header.stamp = clock.now();
342 "map_io"),
"Read map " << load_parameters.image_file_name
343 <<
": " << msg.info.width <<
" X " << msg.info.height <<
" map @ "
344 << msg.info.resolution <<
" m/cell");
349 LOAD_MAP_STATUS loadMapFromYaml(
350 const std::string & yaml_file,
351 nav_msgs::msg::OccupancyGrid & map)
353 if (yaml_file.empty()) {
354 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"YAML file name is empty, can't load!");
355 return MAP_DOES_NOT_EXIST;
357 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Loading yaml file: " << yaml_file);
358 LoadParameters load_parameters;
360 load_parameters = loadMapYaml(yaml_file);
361 }
catch (YAML::Exception & e) {
364 "map_io"),
"Failed processing YAML file " << yaml_file <<
" at position (" <<
365 e.mark.line <<
":" << e.mark.column <<
") for reason: " << e.what());
366 return INVALID_MAP_METADATA;
367 }
catch (std::exception & e) {
369 rclcpp::get_logger(
"map_io"),
"Failed to parse map YAML loaded from file " << yaml_file <<
370 " for reason: " << e.what());
371 return INVALID_MAP_METADATA;
374 loadMapFromFile(load_parameters, map);
375 }
catch (std::exception & e) {
378 "map_io"),
"Failed to load image file " << load_parameters.image_file_name <<
379 " for reason: " << e.what());
380 return INVALID_MAP_DATA;
383 return LOAD_MAP_SUCCESS;
394 void checkSaveParameters(SaveParameters & save_parameters)
397 Magick::InitializeMagick(
nullptr);
400 if (save_parameters.map_file_name ==
"") {
401 rclcpp::Clock clock(RCL_SYSTEM_TIME);
402 save_parameters.map_file_name =
"map_" +
403 std::to_string(
static_cast<int>(clock.now().seconds()));
405 rclcpp::get_logger(
"map_io"),
"Map file unspecified. Map will be saved to " <<
406 save_parameters.map_file_name <<
" file");
410 if (save_parameters.occupied_thresh == 0.0) {
411 save_parameters.occupied_thresh = 0.65;
414 "map_io"),
"Occupied threshold unspecified. Setting it to default value: " <<
415 save_parameters.occupied_thresh);
417 if (save_parameters.free_thresh == 0.0) {
418 save_parameters.free_thresh = 0.25;
420 rclcpp::get_logger(
"map_io"),
"Free threshold unspecified. Setting it to default value: " <<
421 save_parameters.free_thresh);
423 if (1.0 < save_parameters.occupied_thresh) {
424 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Threshold_occupied must be 1.0 or less");
425 throw std::runtime_error(
"Incorrect thresholds");
427 if (save_parameters.free_thresh < 0.0) {
428 RCLCPP_ERROR_STREAM(rclcpp::get_logger(
"map_io"),
"Free threshold must be 0.0 or greater");
429 throw std::runtime_error(
"Incorrect thresholds");
431 if (save_parameters.occupied_thresh <= save_parameters.free_thresh) {
434 "map_io"),
"Threshold_free must be smaller than threshold_occupied");
435 throw std::runtime_error(
"Incorrect thresholds");
439 if (save_parameters.image_format ==
"") {
440 save_parameters.image_format = save_parameters.mode == MapMode::Scale ?
"png" :
"pgm";
442 rclcpp::get_logger(
"map_io"),
"Image format unspecified. Setting it to: " <<
443 save_parameters.image_format);
447 save_parameters.image_format.begin(),
448 save_parameters.image_format.end(),
449 save_parameters.image_format.begin(),
450 [](
unsigned char c) {return std::tolower(c);});
452 const std::vector<std::string> BLESSED_FORMATS{
"bmp",
"pgm",
"png"};
454 std::find(BLESSED_FORMATS.begin(), BLESSED_FORMATS.end(), save_parameters.image_format) ==
455 BLESSED_FORMATS.end())
457 std::stringstream ss;
459 for (
auto & format_name : BLESSED_FORMATS) {
463 ss <<
"'" << format_name <<
"'";
467 rclcpp::get_logger(
"map_io"),
"Requested image format '" << save_parameters.image_format <<
468 "' is not one of the recommended formats: " << ss.str());
470 const std::string FALLBACK_FORMAT =
"png";
473 Magick::CoderInfo info(save_parameters.image_format);
474 if (!info.isWritable()) {
476 rclcpp::get_logger(
"map_io"),
"Format '" << save_parameters.image_format <<
477 "' is not writable. Using '" << FALLBACK_FORMAT <<
"' instead");
478 save_parameters.image_format = FALLBACK_FORMAT;
480 }
catch (Magick::ErrorOption & e) {
483 "map_io"),
"Format '" << save_parameters.image_format <<
"' is not usable. Using '" <<
484 FALLBACK_FORMAT <<
"' instead:" << std::endl << e.what());
485 save_parameters.image_format = FALLBACK_FORMAT;
490 save_parameters.mode == MapMode::Scale &&
491 (save_parameters.image_format ==
"pgm" ||
492 save_parameters.image_format ==
"jpg" ||
493 save_parameters.image_format ==
"jpeg"))
496 rclcpp::get_logger(
"map_io"),
"Map mode 'scale' requires transparency, but format '" <<
497 save_parameters.image_format <<
498 "' does not support it. Consider switching image format to 'png'.");
508 void tryWriteMapToFile(
509 const nav_msgs::msg::OccupancyGrid & map,
510 const SaveParameters & save_parameters)
514 "map_io"),
"Received a " << map.info.width <<
" X " << map.info.height <<
" map @ " <<
515 map.info.resolution <<
" m/pix");
517 std::string mapdatafile = save_parameters.map_file_name +
"." + save_parameters.image_format;
520 Magick::Image image({map.info.width, map.info.height},
"red");
526 save_parameters.mode == MapMode::Scale ?
527 Magick::TrueColorMatteType : Magick::GrayscaleType);
532 int free_thresh_int = std::rint(save_parameters.free_thresh * 100.0);
533 int occupied_thresh_int = std::rint(save_parameters.occupied_thresh * 100.0);
535 for (
size_t y = 0; y < map.info.height; y++) {
536 for (
size_t x = 0; x < map.info.width; x++) {
537 int8_t map_cell = map.data[map.info.width * (map.info.height - y - 1) + x];
541 switch (save_parameters.mode) {
542 case MapMode::Trinary:
543 if (map_cell < 0 || 100 < map_cell) {
544 pixel = Magick::ColorGray(205 / 255.0);
545 }
else if (map_cell <= free_thresh_int) {
546 pixel = Magick::ColorGray(254 / 255.0);
547 }
else if (occupied_thresh_int <= map_cell) {
548 pixel = Magick::ColorGray(0 / 255.0);
550 pixel = Magick::ColorGray(205 / 255.0);
554 if (map_cell < 0 || 100 < map_cell) {
555 pixel = Magick::ColorGray{0.5};
556 pixel.alphaQuantum(TransparentOpacity);
558 pixel = Magick::ColorGray{(100.0 - map_cell) / 100.0};
563 if (map_cell < 0 || 100 < map_cell) {
566 q = map_cell / 255.0 * MaxRGB;
568 pixel = Magick::Color(q, q, q);
573 "map_io"),
"Map mode should be Trinary, Scale or Raw");
574 throw std::runtime_error(
"Invalid map mode");
576 image.pixelColor(x, y, pixel);
581 rclcpp::get_logger(
"map_io"),
582 "Writing map occupancy data to " << mapdatafile);
583 image.write(mapdatafile);
586 std::string mapmetadatafile = save_parameters.map_file_name +
".yaml";
588 std::ofstream yaml(mapmetadatafile);
590 geometry_msgs::msg::Quaternion orientation = map.info.origin.orientation;
591 tf2::Matrix3x3 mat(tf2::Quaternion(orientation.x, orientation.y, orientation.z, orientation.w));
592 double yaw, pitch, roll;
593 mat.getEulerYPR(yaw, pitch, roll);
595 const int file_name_index = mapdatafile.find_last_of(
"/\\");
596 std::string image_name = mapdatafile.substr(file_name_index + 1);
599 e << YAML::Precision(7);
601 e << YAML::Key <<
"image" << YAML::Value << image_name;
602 e << YAML::Key <<
"mode" << YAML::Value << map_mode_to_string(save_parameters.mode);
603 e << YAML::Key <<
"resolution" << YAML::Value << to_string_with_precision(
606 e << YAML::Key <<
"origin" << YAML::Flow << YAML::BeginSeq <<
607 to_string_with_precision(map.info.origin.position.x, 3) <<
608 to_string_with_precision(map.info.origin.position.y, 3) << yaw << YAML::EndSeq;
609 e << YAML::Key <<
"negate" << YAML::Value << 0;
611 if (save_parameters.mode == MapMode::Trinary) {
616 e << YAML::Key <<
"occupied_thresh" << YAML::Value << 0.65;
617 e << YAML::Key <<
"free_thresh" << YAML::Value << 0.196;
619 e << YAML::Key <<
"occupied_thresh" << YAML::Value <<
620 to_string_with_precision(save_parameters.occupied_thresh, 3);
621 e << YAML::Key <<
"free_thresh" << YAML::Value <<
622 to_string_with_precision(save_parameters.free_thresh, 3);
627 rclcpp::get_logger(
"map_io"),
"YAML writer failed with an error " << e.GetLastError() <<
628 ". The map metadata may be invalid.");
631 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Writing map metadata to " << mapmetadatafile);
632 std::ofstream(mapmetadatafile) << e.c_str();
634 RCLCPP_INFO_STREAM(rclcpp::get_logger(
"map_io"),
"Map saved");
638 const nav_msgs::msg::OccupancyGrid & map,
639 const SaveParameters & save_parameters)
641 if (!nav2::validateMsg(map)) {
643 rclcpp::get_logger(
"map_io"),
644 "Failed to write map for reason: invalid OccupancyGrid message");
649 SaveParameters save_parameters_loc = save_parameters;
653 checkSaveParameters(save_parameters_loc);
655 tryWriteMapToFile(map, save_parameters_loc);
656 }
catch (std::exception & e) {
658 rclcpp::get_logger(
"map_io"),
659 "Failed to write map for reason: " << e.what());
665 std::string to_string_with_precision(
double value,
int precision)
667 std::ostringstream out;
668 out << std::fixed << std::setprecision(precision) << value;