add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
cmake_minimum_required(VERSION 3.5)
project(nav2_map_server)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake_modules)
find_package(ament_cmake REQUIRED)
find_package(nav2_common REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(yaml_cpp_vendor REQUIRED)
find_package(std_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(nav2_util REQUIRED)
find_package(GRAPHICSMAGICKCPP REQUIRED)
nav2_package()
include_directories(include)
set(map_server_executable map_server)
set(map_saver_cli_executable map_saver_cli)
set(map_saver_server_executable map_saver_server)
set(costmap_filter_info_server_executable costmap_filter_info_server)
add_executable(${map_server_executable}
src/map_server/main.cpp)
add_executable(${map_saver_cli_executable}
src/map_saver/main_cli.cpp)
add_executable(${map_saver_server_executable}
src/map_saver/main_server.cpp)
add_executable(${costmap_filter_info_server_executable}
src/costmap_filter_info/main.cpp)
set(map_io_library_name map_io)
set(library_name ${map_server_executable}_core)
add_library(${map_io_library_name} SHARED
src/map_mode.cpp
src/map_io.cpp)
add_library(${library_name} SHARED
src/map_server/map_server.cpp
src/map_saver/map_saver.cpp
src/costmap_filter_info/costmap_filter_info_server.cpp)
set(map_io_dependencies
yaml_cpp_vendor
nav_msgs
nav2_util
tf2)
set(map_server_dependencies
rclcpp
rclcpp_lifecycle
rclcpp_components
nav_msgs
nav2_msgs
yaml_cpp_vendor
std_msgs
nav2_util)
set(map_saver_dependencies
rclcpp
rclcpp_lifecycle
nav_msgs
nav2_msgs
nav2_util)
ament_target_dependencies(${map_server_executable}
${map_server_dependencies})
ament_target_dependencies(${map_saver_cli_executable}
${map_saver_dependencies})
ament_target_dependencies(${map_saver_server_executable}
${map_saver_dependencies})
ament_target_dependencies(${costmap_filter_info_server_executable}
${map_saver_dependencies})
ament_target_dependencies(${library_name}
${map_server_dependencies})
ament_target_dependencies(${map_io_library_name}
${map_io_dependencies})
target_link_libraries(${library_name}
${map_io_library_name})
target_link_libraries(${map_server_executable}
${library_name})
if(WIN32)
target_compile_definitions(${map_server_executable} PRIVATE
YAML_CPP_DLL)
endif()
target_link_libraries(${map_saver_cli_executable}
${library_name})
target_link_libraries(${map_saver_server_executable}
${library_name})
target_link_libraries(${costmap_filter_info_server_executable}
${library_name})
target_include_directories(${map_io_library_name} SYSTEM PRIVATE
${GRAPHICSMAGICKCPP_INCLUDE_DIRS})
target_link_libraries(${map_io_library_name}
${GRAPHICSMAGICKCPP_LIBRARIES})
if(WIN32)
target_compile_definitions(${map_io_library_name} PRIVATE
YAML_CPP_DLL)
endif()
rclcpp_components_register_nodes(${library_name} "nav2_map_server::CostmapFilterInfoServer")
rclcpp_components_register_nodes(${library_name} "nav2_map_server::MapSaver")
rclcpp_components_register_nodes(${library_name} "nav2_map_server::MapServer")
install(TARGETS
${library_name} ${map_io_library_name}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(TARGETS
${map_server_executable} ${map_saver_cli_executable} ${map_saver_server_executable}
${costmap_filter_info_server_executable}
RUNTIME DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY include/
DESTINATION include/)
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
set(ament_cmake_copyright_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
find_package(ament_cmake_gtest REQUIRED)
add_subdirectory(test)
endif()
ament_export_include_directories(include)
ament_export_libraries(
${library_name}
${map_io_library_name}
)
ament_export_dependencies(${map_io_dependencies} ${map_server_dependencies})
ament_package()
+143
View File
@@ -0,0 +1,143 @@
# Map Server
The `Map Server` provides maps to the rest of the Nav2 system using both topic and
service interfaces. Map server will expose maps on the node bringup, but can also change maps using a `load_map` service during run-time, as well as save maps using a `save_map` server.
See its [Configuration Guide Page](https://navigation.ros.org/configuration/packages/configuring-map-server.html) for additional parameter descriptions.
### Architecture
In contrast to the ROS1 navigation map server, the nav2 map server will support a variety
of map types, and thus some aspects of the original code have been refactored to support
this new extensible framework.
Currently map server divides into tree parts:
- `map_server`
- `map_saver`
- `map_io` library
`map_server` is responsible for loading the map from a file through command-line interface
or by using service requests.
`map_saver` saves the map into a file. Like `map_server`, it has an ability to save the map from
command-line or by calling a service.
`map_io` - is a map input-output library. The library is designed to be an object-independent
in order to allow easily save/load map from external code just by calling necessary function.
This library is also used by `map_loader` and `map_saver` to work. Currently it contains
OccupancyGrid saving/loading functions moved from the rest part of map server code.
It is designed to be replaceable for a new IO library (e.g. for library with new map encoding
method or any other library supporting costmaps, multifloor maps, etc...).
### CLI-usage
#### Map Server
The `Map Server` is a composable ROS2 node. By default, there is a `map_server` executable that
instances one of these nodes, but it is possible to compose multiple map server nodes into
a single process, if desired.
The command line for the map server executable is slightly different that it was with ROS1.
With ROS1, one invoked the map server and passing the map YAML filename, like this:
```
$ map_server map.yaml
```
Where the YAML file specified contained the various map metadata, such as:
```
image: testmap.png
resolution: 0.1
origin: [2.0, 3.0, 1.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196
```
The Nav2 software retains the map YAML file format from Nav1, but uses the ROS2 parameter
mechanism to get the name of the YAML file to use. This effectively introduces a
level of indirection to get the map yaml filename. For example, for a node named 'map_server',
the parameter file would look like this:
```
# map_server_params.yaml
map_server:
ros__parameters:
yaml_filename: "map.yaml"
```
One can invoke the map service executable directly, passing the params file on the command line,
like this:
```
$ map_server __params:=map_server_params.yaml
```
There is also possibility of having multiple map server nodes in a single process, where the parameters file would separate the parameters by node name, like this:
```
# combined_params.yaml
map_server1:
ros__parameters:
yaml_filename: "some_map.yaml"
map_server2:
ros__parameters:
yaml_filename: "another_map.yaml"
```
Then, one would invoke this process with the params file that contains the parameters for both nodes:
```
$ process_with_multiple_map_servers __params:=combined_params.yaml
```
The parameter for the initial map (yaml_filename) has to be set, but an empty string can be used if no initial map should be loaded. In this case, no map is loaded during
on_configure or published during on_activate. The _load_map_-service should the be used to load and publish a map.
#### Map Saver
Like in ROS1 `map_saver` could be used as CLI-executable. It was renamed to `map_saver_cli`
and could be invoked by following command:
```
$ ros2 run nav2_map_server map_saver_cli [arguments] [--ros-args ROS remapping args]
```
## Currently Supported Map Types
- Occupancy grid (nav_msgs/msg/OccupancyGrid)
## MapIO library
`MapIO` library contains following API functions declared in `map_io.hpp` to work with
OccupancyGrid maps:
- loadMapYaml(): Load and parse the given YAML file
- loadMapFromFile(): Load the image from map file and generate an OccupancyGrid
- loadMapFromYaml(): Load the map YAML, image from map file and generate an OccupancyGrid
- saveMapToFile(): Write OccupancyGrid map to file
## Services
As in ROS navigation, the `map_server` node provides a "map" service to get the map. See the nav_msgs/srv/GetMap.srv file for details.
NEW in ROS2 Eloquent, `map_server` also now provides a "load_map" service and `map_saver` -
a "save_map" service. See nav2_msgs/srv/LoadMap.srv and nav2_msgs/srv/SaveMap.srv for details.
For using these services `map_server`/`map_saver` should be launched as a continuously running
`nav2::LifecycleNode` node. In addition to the CLI, `Map Saver` has a functionality of server
handling incoming services. To run `Map Saver` in a server mode
`nav2_map_server/launch/map_saver_server.launch.py` launch-file could be used.
Service usage examples:
```
$ ros2 service call /map_server/load_map nav2_msgs/srv/LoadMap "{map_url: /ros/maps/map.yaml}"
$ ros2 service call /map_saver/save_map nav2_msgs/srv/SaveMap "{map_topic: map, map_url: my_map, image_format: pgm, map_mode: trinary, free_thresh: 0.25, occupied_thresh: 0.65}"
```
@@ -0,0 +1,34 @@
# Copyright 2019 Rover Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# CMake script for finding Magick++, the C++ interface for the
# GraphicsMagick library
#
# Output variables:
# GRAPHICSMAGICKCPP_FOUND - system has GraphicsMagick Magick++
# GRAPHICSMAGICKCPP_INCLUDE_DIRS - include directories for Magick++
# GRAPHICSMAGICKCPP_LIBRARIES - libraries you need to link to
include(FindPackageHandleStandardArgs)
find_path(GRAPHICSMAGICKCPP_INCLUDE_DIRS
NAMES "Magick++.h"
PATH_SUFFIXES GraphicsMagick)
find_library(GRAPHICSMAGICKCPP_LIBRARIES
NAMES "GraphicsMagick++" "graphicsmagick")
find_package_handle_standard_args(
GRAPHICSMAGICKCPP
GRAPHICSMAGICKCPP_LIBRARIES
GRAPHICSMAGICKCPP_INCLUDE_DIRS)
@@ -0,0 +1,81 @@
// Copyright (c) 2020 Samsung Research Russia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_MAP_SERVER__COSTMAP_FILTER_INFO_SERVER_HPP_
#define NAV2_MAP_SERVER__COSTMAP_FILTER_INFO_SERVER_HPP_
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_msgs/msg/costmap_filter_info.hpp"
namespace nav2_map_server
{
class CostmapFilterInfoServer : public nav2_util::LifecycleNode
{
public:
/**
* @brief Constructor for the nav2_map_server::CostmapFilterInfoServer
* @param options Additional options to control creation of the node.
*/
explicit CostmapFilterInfoServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
/**
* @brief Destructor for the nav2_map_server::CostmapFilterInfoServer
*/
~CostmapFilterInfoServer();
protected:
/**
* @brief Creates CostmapFilterInfo publisher and forms published message from ROS parameters
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State & state) override;
/**
* @brief Publishes a CostmapFilterInfo message
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Deactivates publisher
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Resets publisher
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when in Shutdown state
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State & state) override;
private:
rclcpp_lifecycle::LifecyclePublisher<nav2_msgs::msg::CostmapFilterInfo>::SharedPtr publisher_;
nav2_msgs::msg::CostmapFilterInfo msg_;
}; // CostmapFilterInfoServer
} // namespace nav2_map_server
#endif // NAV2_MAP_SERVER__COSTMAP_FILTER_INFO_SERVER_HPP_
@@ -0,0 +1,114 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* OccupancyGrid map input-output library */
#ifndef NAV2_MAP_SERVER__MAP_IO_HPP_
#define NAV2_MAP_SERVER__MAP_IO_HPP_
#include <string>
#include <vector>
#include "nav2_map_server/map_mode.hpp"
#include "nav_msgs/msg/occupancy_grid.hpp"
/* Map input part */
namespace nav2_map_server
{
struct LoadParameters
{
std::string image_file_name;
double resolution{0};
std::vector<double> origin{0, 0, 0};
double free_thresh;
double occupied_thresh;
MapMode mode;
bool negate;
};
typedef enum
{
LOAD_MAP_SUCCESS,
MAP_DOES_NOT_EXIST,
INVALID_MAP_METADATA,
INVALID_MAP_DATA
} LOAD_MAP_STATUS;
/**
* @brief Load and parse the given YAML file
* @param yaml_filename Name of the map file passed though parameter
* @return Map loading parameters obtained from YAML file
* @throw YAML::Exception
*/
LoadParameters loadMapYaml(const std::string & yaml_filename);
/**
* @brief Load the image from map file and generate an OccupancyGrid
* @param load_parameters Parameters of loading map
* @param map Output loaded map
* @throw std::exception
*/
void loadMapFromFile(
const LoadParameters & load_parameters,
nav_msgs::msg::OccupancyGrid & map);
/**
* @brief Load the map YAML, image from map file and
* generate an OccupancyGrid
* @param yaml_file Name of input YAML file
* @param map Output loaded map
* @return status of map loaded
*/
LOAD_MAP_STATUS loadMapFromYaml(
const std::string & yaml_file,
nav_msgs::msg::OccupancyGrid & map);
/* Map output part */
struct SaveParameters
{
std::string map_file_name{""};
std::string image_format{""};
double free_thresh{0.0};
double occupied_thresh{0.0};
MapMode mode{MapMode::Trinary};
};
/**
* @brief Write OccupancyGrid map to file
* @param map OccupancyGrid map data
* @param save_parameters Map saving parameters.
* @return true or false
*/
bool saveMapToFile(
const nav_msgs::msg::OccupancyGrid & map,
const SaveParameters & save_parameters);
/**
* @brief Expand ~/ to home user dir.
* @param yaml_filename Name of input YAML file.
* @param home_dir Expanded `~/`home dir or empty string if HOME not set
*
* @return Expanded string or input string if `~/` not expanded
*/
std::string expand_user_home_dir_if_needed(
std::string yaml_filename,
std::string home_dir);
} // namespace nav2_map_server
#endif // NAV2_MAP_SERVER__MAP_IO_HPP_
@@ -0,0 +1,71 @@
// Copyright (c) 2019 Rover Robotics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_MAP_SERVER__MAP_MODE_HPP_
#define NAV2_MAP_SERVER__MAP_MODE_HPP_
#include <string>
#include <vector>
namespace nav2_map_server
{
/**
* @enum nav2_map_server::MapMode
* @brief Describes the relation between image pixel values and map occupancy
* status (0-100; -1). Lightness refers to the mean of a given pixel's RGB
* channels on a scale from 0 to 255.
*/
enum class MapMode
{
/**
* Together with associated threshold values (occupied and free):
* lightness >= occupied threshold - Occupied (100)
* ... (anything in between) - Unknown (-1)
* lightness <= free threshold - Free (0)
*/
Trinary,
/**
* Together with associated threshold values (occupied and free):
* alpha < 1.0 - Unknown (-1)
* lightness >= occ_th - Occupied (100)
* ... (linearly interpolate to)
* lightness <= free_th - Free (0)
*/
Scale,
/**
* Lightness = 0 - Free (0)
* ... (linearly interpolate to)
* Lightness = 100 - Occupied (100)
* Lightness >= 101 - Unknown
*/
Raw,
};
/**
* @brief Convert a MapMode enum to the name of the map mode
* @param map_mode Mode for the map
* @return String identifier of the given map mode
* @throw std::invalid_argument if the given value is not a defined map mode
*/
const char * map_mode_to_string(MapMode map_mode);
/**
* @brief Convert the name of a map mode to a MapMode enum
* @param map_mode_name Name of the map mode
* @throw std::invalid_argument if the name does not name a map mode
* @return map mode corresponding to the string
*/
MapMode map_mode_from_string(std::string map_mode_name);
} // namespace nav2_map_server
#endif // NAV2_MAP_SERVER__MAP_MODE_HPP_
@@ -0,0 +1,118 @@
// Copyright (c) 2020 Samsung Research Russia
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_MAP_SERVER__MAP_SAVER_HPP_
#define NAV2_MAP_SERVER__MAP_SAVER_HPP_
#include <string>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_msgs/srv/save_map.hpp"
#include "map_io.hpp"
namespace nav2_map_server
{
/**
* @class nav2_map_server::MapSaver
* @brief A class that provides map saving methods and services
*/
class MapSaver : public nav2_util::LifecycleNode
{
public:
/**
* @brief Constructor for the nav2_map_server::MapSaver
* @param options Additional options to control creation of the node.
*/
explicit MapSaver(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
/**
* @brief Destructor for the nav2_map_server::MapServer
*/
~MapSaver();
/**
* @brief Read a message from incoming map topic and save map to a file
* @param map_topic Incoming map topic name
* @param save_parameters Map saving parameters.
* @return true of false
*/
bool saveMapTopicToFile(
const std::string & map_topic,
const SaveParameters & save_parameters);
/**
* @brief Sets up map saving service
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when node switched to active state
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when node switched to inactive state
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when it is required node clean-up
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when in Shutdown state
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State & state) override;
protected:
/**
* @brief Map saving service callback
* @param request_header Service request header
* @param request Service request
* @param response Service response
*/
void saveMapCallback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<nav2_msgs::srv::SaveMap::Request> request,
std::shared_ptr<nav2_msgs::srv::SaveMap::Response> response);
// The timeout for saving the map in service
std::shared_ptr<rclcpp::Duration> save_map_timeout_;
// Default values for map thresholds
double free_thresh_default_;
double occupied_thresh_default_;
// param for handling QoS configuration
bool map_subscribe_transient_local_;
// The name of the service for saving a map from topic
const std::string save_map_service_name_{"save_map"};
// A service to save the map to a file at run time (SaveMap)
rclcpp::Service<nav2_msgs::srv::SaveMap>::SharedPtr save_map_service_;
};
} // namespace nav2_map_server
#endif // NAV2_MAP_SERVER__MAP_SAVER_HPP_
@@ -0,0 +1,148 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_MAP_SERVER__MAP_SERVER_HPP_
#define NAV2_MAP_SERVER__MAP_SERVER_HPP_
#include <string>
#include <memory>
#include <functional>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav_msgs/msg/occupancy_grid.hpp"
#include "nav_msgs/srv/get_map.hpp"
#include "nav2_msgs/srv/load_map.hpp"
namespace nav2_map_server
{
/**
* @class nav2_map_server::MapServer
* @brief Parses the map yaml file and creates a service and a publisher that
* provides occupancy grid
*/
class MapServer : public nav2_util::LifecycleNode
{
public:
/**
* @brief A constructor for nav2_map_server::MapServer
* @param options Additional options to control creation of the node.
*/
explicit MapServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
/**
* @brief A Destructor for nav2_map_server::MapServer
*/
~MapServer();
protected:
/**
* @brief Sets up required params and services. Loads map and its parameters from the file
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State & state) override;
/**
* @brief Start publishing the map using the latched topic
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Stops publishing the latched topic
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Resets the member variables
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State & state) override;
/**
* @brief Called when in Shutdown state
* @param state Lifecycle Node's state
* @return Success or Failure
*/
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State & state) override;
/**
* @brief Load the map YAML, image from map file name and
* generate output response containing an OccupancyGrid.
* Update msg_ class variable.
* @param yaml_file name of input YAML file
* @param response Output response with loaded OccupancyGrid map
* @return true or false
*/
bool loadMapResponseFromYaml(
const std::string & yaml_file,
std::shared_ptr<nav2_msgs::srv::LoadMap::Response> response);
/**
* @brief Method correcting msg_ header when it belongs to instantiated object
*/
void updateMsgHeader();
/**
* @brief Map getting service callback
* @param request_header Service request header
* @param request Service request
* @param response Service response
*/
void getMapCallback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
std::shared_ptr<nav_msgs::srv::GetMap::Response> response);
/**
* @brief Map loading service callback
* @param request_header Service request header
* @param request Service request
* @param response Service response
*/
void loadMapCallback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<nav2_msgs::srv::LoadMap::Request> request,
std::shared_ptr<nav2_msgs::srv::LoadMap::Response> response);
// The name of the service for getting a map
const std::string service_name_{"map"};
// The name of the service for loading a map
const std::string load_map_service_name_{"load_map"};
// A service to provide the occupancy grid (GetMap) and the message to return
rclcpp::Service<nav_msgs::srv::GetMap>::SharedPtr occ_service_;
// A service to load the occupancy grid from file at run time (LoadMap)
rclcpp::Service<nav2_msgs::srv::LoadMap>::SharedPtr load_map_service_;
// A topic on which the occupancy grid will be published
rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::OccupancyGrid>::SharedPtr occ_pub_;
// The frame ID used in the returned OccupancyGrid message
std::string frame_id_;
// The message to publish on the occupancy grid topic
nav_msgs::msg::OccupancyGrid msg_;
// true if msg_ was initialized
bool map_available_;
};
} // namespace nav2_map_server
#endif // NAV2_MAP_SERVER__MAP_SERVER_HPP_
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# Copyright (c) 2020 Samsung Research Russia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from launch import LaunchDescription
import launch_ros.actions
def generate_launch_description():
# Parameters
lifecycle_nodes = ['map_saver']
use_sim_time = True
autostart = True
save_map_timeout = 2.0
free_thresh_default = 0.25
occupied_thresh_default = 0.65
# Nodes launching commands
start_map_saver_server_cmd = launch_ros.actions.Node(
package='nav2_map_server',
executable='map_saver_server',
output='screen',
emulate_tty=True, # https://github.com/ros2/launch/issues/188
parameters=[{'save_map_timeout': save_map_timeout},
{'free_thresh_default': free_thresh_default},
{'occupied_thresh_default': occupied_thresh_default}])
start_lifecycle_manager_cmd = launch_ros.actions.Node(
package='nav2_lifecycle_manager',
executable='lifecycle_manager',
name='lifecycle_manager',
output='screen',
emulate_tty=True, # https://github.com/ros2/launch/issues/188
parameters=[{'use_sim_time': use_sim_time},
{'autostart': autostart},
{'node_names': lifecycle_nodes}])
ld = LaunchDescription()
ld.add_action(start_map_saver_server_cmd)
ld.add_action(start_lifecycle_manager_cmd)
return ld
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>nav2_map_server</name>
<version>1.1.18</version>
<description>
Refactored map server for ROS2 Navigation
</description>
<maintainer email="brian.wilcox@intel.com">Brian Wilcox</maintainer>
<license>Apache-2.0</license>
<license>BSD-3-Clause</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>nav2_common</build_depend>
<depend>rclcpp_lifecycle</depend>
<depend>nav_msgs</depend>
<depend>std_msgs</depend>
<depend>rclcpp</depend>
<depend>yaml_cpp_vendor</depend>
<depend>launch_ros</depend>
<depend>launch_testing</depend>
<depend>tf2</depend>
<depend>nav2_msgs</depend>
<depend>nav2_util</depend>
<depend>graphicsmagick</depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>launch</test_depend>
<test_depend>launch_testing</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
@@ -0,0 +1,115 @@
// Copyright (c) 2020 Samsung Research Russia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(AlexeyMerzlyakov): This dummy info publisher should be removed
// after Semantic Map Server having the same functionality will be developed.
#include "nav2_map_server/costmap_filter_info_server.hpp"
#include <string>
#include <memory>
#include <utility>
namespace nav2_map_server
{
CostmapFilterInfoServer::CostmapFilterInfoServer(const rclcpp::NodeOptions & options)
: nav2_util::LifecycleNode("costmap_filter_info_server", "", options)
{
declare_parameter("filter_info_topic", "costmap_filter_info");
declare_parameter("type", 0);
declare_parameter("mask_topic", "filter_mask");
declare_parameter("base", 0.0);
declare_parameter("multiplier", 1.0);
}
CostmapFilterInfoServer::~CostmapFilterInfoServer()
{
}
nav2_util::CallbackReturn
CostmapFilterInfoServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Configuring");
std::string filter_info_topic = get_parameter("filter_info_topic").as_string();
publisher_ = this->create_publisher<nav2_msgs::msg::CostmapFilterInfo>(
filter_info_topic, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable());
msg_ = nav2_msgs::msg::CostmapFilterInfo();
msg_.header.frame_id = "";
msg_.header.stamp = now();
msg_.type = get_parameter("type").as_int();
msg_.filter_mask_topic = get_parameter("mask_topic").as_string();
msg_.base = static_cast<float>(get_parameter("base").as_double());
msg_.multiplier = static_cast<float>(get_parameter("multiplier").as_double());
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
CostmapFilterInfoServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Activating");
publisher_->on_activate();
auto costmap_filter_info = std::make_unique<nav2_msgs::msg::CostmapFilterInfo>(msg_);
publisher_->publish(std::move(costmap_filter_info));
// create bond connection
createBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
CostmapFilterInfoServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Deactivating");
publisher_->on_deactivate();
// destroy bond connection
destroyBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
CostmapFilterInfoServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Cleaning up");
publisher_.reset();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
CostmapFilterInfoServer::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Shutting down");
return nav2_util::CallbackReturn::SUCCESS;
}
} // namespace nav2_map_server
#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(nav2_map_server::CostmapFilterInfoServer)
@@ -0,0 +1,32 @@
// Copyright (c) 2020 Samsung Research Russia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "nav2_map_server/costmap_filter_info_server.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char * argv[])
{
auto logger = rclcpp::get_logger("costmap_filter_info_server");
RCLCPP_INFO(logger, "This is costmap filter info publisher");
rclcpp::init(argc, argv);
auto node = std::make_shared<nav2_map_server::CostmapFilterInfoServer>();
rclcpp::spin(node->get_node_base_interface());
rclcpp::shutdown();
return 0;
}
+602
View File
@@ -0,0 +1,602 @@
/* Copyright 2019 Rover Robotics
* Copyright 2010 Brian Gerkey
* Copyright (c) 2008, Willow Garage, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <ORGANIZATION> nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "nav2_map_server/map_io.hpp"
#ifndef _WIN32
#include <libgen.h>
#endif
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <stdexcept>
#include <cstdlib>
#include "Magick++.h"
#include "nav2_util/geometry_utils.hpp"
#include "yaml-cpp/yaml.h"
#include "tf2/LinearMath/Matrix3x3.h"
#include "tf2/LinearMath/Quaternion.h"
#include "nav2_util/occ_grid_values.hpp"
#ifdef _WIN32
// https://github.com/rtv/Stage/blob/master/replace/dirname.c
static
char * dirname(char * path)
{
static const char dot[] = ".";
char * last_slash;
if (path == NULL) {
return path;
}
/* Replace all "\" with "/" */
char * c = path;
while (*c != '\0') {
if (*c == '\\') {*c = '/';}
++c;
}
/* Find last '/'. */
last_slash = path != NULL ? strrchr(path, '/') : NULL;
if (last_slash != NULL && last_slash == path) {
/* The last slash is the first character in the string. We have to
return "/". */
++last_slash;
} else if (last_slash != NULL && last_slash[1] == '\0') {
/* The '/' is the last character, we have to look further. */
last_slash = reinterpret_cast<char *>(memchr(path, last_slash - path, '/'));
}
if (last_slash != NULL) {
/* Terminate the path. */
last_slash[0] = '\0';
} else {
/* This assignment is ill-designed but the XPG specs require to
return a string containing "." in any case no directory part is
found and so a static and constant string is required. */
path = reinterpret_cast<char *>(dot);
}
return path;
}
#endif
namespace nav2_map_server
{
using nav2_util::geometry_utils::orientationAroundZAxis;
// === Map input part ===
/// Get the given subnode value.
/// The only reason this function exists is to wrap the exceptions in slightly nicer error messages,
/// including the name of the failed key
/// @throw YAML::Exception
template<typename T>
T yaml_get_value(const YAML::Node & node, const std::string & key)
{
try {
return node[key].as<T>();
} catch (YAML::Exception & e) {
std::stringstream ss;
ss << "Failed to parse YAML tag '" << key << "' for reason: " << e.msg;
throw YAML::Exception(e.mark, ss.str());
}
}
std::string get_home_dir()
{
if (const char * home_dir = std::getenv("HOME")) {
return std::string{home_dir};
}
return std::string{};
}
std::string expand_user_home_dir_if_needed(
std::string yaml_filename,
std::string home_variable_value)
{
if (yaml_filename.size() < 2 || !(yaml_filename[0] == '~' && yaml_filename[1] == '/')) {
return yaml_filename;
}
if (home_variable_value.empty()) {
RCLCPP_INFO_STREAM(
rclcpp::get_logger(
"map_io"), "Map yaml file name starts with '~/' but no HOME variable set. \n"
<< "[INFO] [map_io] User home dir will be not expanded \n");
return yaml_filename;
}
const std::string prefix{home_variable_value};
return yaml_filename.replace(0, 1, prefix);
}
LoadParameters loadMapYaml(const std::string & yaml_filename)
{
YAML::Node doc = YAML::LoadFile(expand_user_home_dir_if_needed(yaml_filename, get_home_dir()));
LoadParameters load_parameters;
auto image_file_name = yaml_get_value<std::string>(doc, "image");
if (image_file_name.empty()) {
throw YAML::Exception(doc["image"].Mark(), "The image tag was empty.");
}
if (image_file_name[0] != '/') {
// dirname takes a mutable char *, so we copy into a vector
std::vector<char> fname_copy(yaml_filename.begin(), yaml_filename.end());
fname_copy.push_back('\0');
image_file_name = std::string(dirname(fname_copy.data())) + '/' + image_file_name;
}
load_parameters.image_file_name = image_file_name;
load_parameters.resolution = yaml_get_value<double>(doc, "resolution");
load_parameters.origin = yaml_get_value<std::vector<double>>(doc, "origin");
if (load_parameters.origin.size() != 3) {
throw YAML::Exception(
doc["origin"].Mark(), "value of the 'origin' tag should have 3 elements, not " +
std::to_string(load_parameters.origin.size()));
}
load_parameters.free_thresh = yaml_get_value<double>(doc, "free_thresh");
load_parameters.occupied_thresh = yaml_get_value<double>(doc, "occupied_thresh");
auto map_mode_node = doc["mode"];
if (!map_mode_node.IsDefined()) {
load_parameters.mode = MapMode::Trinary;
} else {
load_parameters.mode = map_mode_from_string(map_mode_node.as<std::string>());
}
try {
load_parameters.negate = yaml_get_value<int>(doc, "negate");
} catch (YAML::Exception &) {
load_parameters.negate = yaml_get_value<bool>(doc, "negate");
}
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "resolution: " << load_parameters.resolution);
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "origin[0]: " << load_parameters.origin[0]);
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "origin[1]: " << load_parameters.origin[1]);
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "origin[2]: " << load_parameters.origin[2]);
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "free_thresh: " << load_parameters.free_thresh);
RCLCPP_INFO_STREAM(
rclcpp::get_logger(
"map_io"), "occupied_thresh: " << load_parameters.occupied_thresh);
RCLCPP_INFO_STREAM(
rclcpp::get_logger("map_io"),
"mode: " << map_mode_to_string(load_parameters.mode));
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "negate: " << load_parameters.negate);
return load_parameters;
}
void loadMapFromFile(
const LoadParameters & load_parameters,
nav_msgs::msg::OccupancyGrid & map)
{
Magick::InitializeMagick(nullptr);
nav_msgs::msg::OccupancyGrid msg;
RCLCPP_INFO_STREAM(
rclcpp::get_logger("map_io"), "Loading image_file: " <<
load_parameters.image_file_name);
Magick::Image img(load_parameters.image_file_name);
// Copy the image data into the map structure
msg.info.width = img.size().width();
msg.info.height = img.size().height();
msg.info.resolution = load_parameters.resolution;
msg.info.origin.position.x = load_parameters.origin[0];
msg.info.origin.position.y = load_parameters.origin[1];
msg.info.origin.position.z = 0.0;
msg.info.origin.orientation = orientationAroundZAxis(load_parameters.origin[2]);
// Allocate space to hold the data
msg.data.resize(msg.info.width * msg.info.height);
// Copy pixel data into the map structure
for (size_t y = 0; y < msg.info.height; y++) {
for (size_t x = 0; x < msg.info.width; x++) {
auto pixel = img.pixelColor(x, y);
std::vector<Magick::Quantum> channels = {pixel.redQuantum(), pixel.greenQuantum(),
pixel.blueQuantum()};
if (load_parameters.mode == MapMode::Trinary && img.matte()) {
// To preserve existing behavior, average in alpha with color channels in Trinary mode.
// CAREFUL. alpha is inverted from what you might expect. High = transparent, low = opaque
channels.push_back(MaxRGB - pixel.alphaQuantum());
}
double sum = 0;
for (auto c : channels) {
sum += c;
}
/// on a scale from 0.0 to 1.0 how bright is the pixel?
double shade = Magick::ColorGray::scaleQuantumToDouble(sum / channels.size());
// If negate is true, we consider blacker pixels free, and whiter
// pixels occupied. Otherwise, it's vice versa.
/// on a scale from 0.0 to 1.0, how occupied is the map cell (before thresholding)?
double occ = (load_parameters.negate ? shade : 1.0 - shade);
int8_t map_cell;
switch (load_parameters.mode) {
case MapMode::Trinary:
if (load_parameters.occupied_thresh < occ) {
map_cell = nav2_util::OCC_GRID_OCCUPIED;
} else if (occ < load_parameters.free_thresh) {
map_cell = nav2_util::OCC_GRID_FREE;
} else {
map_cell = nav2_util::OCC_GRID_UNKNOWN;
}
break;
case MapMode::Scale:
if (pixel.alphaQuantum() != OpaqueOpacity) {
map_cell = nav2_util::OCC_GRID_UNKNOWN;
} else if (load_parameters.occupied_thresh < occ) {
map_cell = nav2_util::OCC_GRID_OCCUPIED;
} else if (occ < load_parameters.free_thresh) {
map_cell = nav2_util::OCC_GRID_FREE;
} else {
map_cell = std::rint(
(occ - load_parameters.free_thresh) /
(load_parameters.occupied_thresh - load_parameters.free_thresh) * 100.0);
}
break;
case MapMode::Raw: {
double occ_percent = std::round(shade * 255);
if (nav2_util::OCC_GRID_FREE <= occ_percent &&
occ_percent <= nav2_util::OCC_GRID_OCCUPIED)
{
map_cell = static_cast<int8_t>(occ_percent);
} else {
map_cell = nav2_util::OCC_GRID_UNKNOWN;
}
break;
}
default:
throw std::runtime_error("Invalid map mode");
}
msg.data[msg.info.width * (msg.info.height - y - 1) + x] = map_cell;
}
}
// Since loadMapFromFile() does not belong to any node, publishing in a system time.
rclcpp::Clock clock(RCL_SYSTEM_TIME);
msg.info.map_load_time = clock.now();
msg.header.frame_id = "map";
msg.header.stamp = clock.now();
RCLCPP_INFO_STREAM(
rclcpp::get_logger(
"map_io"), "Read map " << load_parameters.image_file_name
<< ": " << msg.info.width << " X " << msg.info.height << " map @ "
<< msg.info.resolution << " m/cell");
map = msg;
}
LOAD_MAP_STATUS loadMapFromYaml(
const std::string & yaml_file,
nav_msgs::msg::OccupancyGrid & map)
{
if (yaml_file.empty()) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("map_io"), "YAML file name is empty, can't load!");
return MAP_DOES_NOT_EXIST;
}
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "Loading yaml file: " << yaml_file);
LoadParameters load_parameters;
try {
load_parameters = loadMapYaml(yaml_file);
} catch (YAML::Exception & e) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger(
"map_io"), "Failed processing YAML file " << yaml_file << " at position (" <<
e.mark.line << ":" << e.mark.column << ") for reason: " << e.what());
return INVALID_MAP_METADATA;
} catch (std::exception & e) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger("map_io"), "Failed to parse map YAML loaded from file " << yaml_file <<
" for reason: " << e.what());
return INVALID_MAP_METADATA;
}
try {
loadMapFromFile(load_parameters, map);
} catch (std::exception & e) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger(
"map_io"), "Failed to load image file " << load_parameters.image_file_name <<
" for reason: " << e.what());
return INVALID_MAP_DATA;
}
return LOAD_MAP_SUCCESS;
}
// === Map output part ===
/**
* @brief Checks map saving parameters for consistency
* @param save_parameters Map saving parameters.
* NOTE: save_parameters could be updated during function execution.
* @throw std::exception in case of inconsistent parameters
*/
void checkSaveParameters(SaveParameters & save_parameters)
{
// Magick must me initialized before any activity with images
Magick::InitializeMagick(nullptr);
// Checking map file name
if (save_parameters.map_file_name == "") {
rclcpp::Clock clock(RCL_SYSTEM_TIME);
save_parameters.map_file_name = "map_" +
std::to_string(static_cast<int>(clock.now().seconds()));
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Map file unspecified. Map will be saved to " <<
save_parameters.map_file_name << " file");
}
// Checking thresholds
if (save_parameters.occupied_thresh == 0.0) {
save_parameters.occupied_thresh = 0.65;
RCLCPP_WARN_STREAM(
rclcpp::get_logger(
"map_io"), "Occupied threshold unspecified. Setting it to default value: " <<
save_parameters.occupied_thresh);
}
if (save_parameters.free_thresh == 0.0) {
save_parameters.free_thresh = 0.25;
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Free threshold unspecified. Setting it to default value: " <<
save_parameters.free_thresh);
}
if (1.0 < save_parameters.occupied_thresh) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("map_io"), "Threshold_occupied must be 1.0 or less");
throw std::runtime_error("Incorrect thresholds");
}
if (save_parameters.free_thresh < 0.0) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("map_io"), "Free threshold must be 0.0 or greater");
throw std::runtime_error("Incorrect thresholds");
}
if (save_parameters.occupied_thresh <= save_parameters.free_thresh) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger(
"map_io"), "Threshold_free must be smaller than threshold_occupied");
throw std::runtime_error("Incorrect thresholds");
}
// Checking image format
if (save_parameters.image_format == "") {
save_parameters.image_format = save_parameters.mode == MapMode::Scale ? "png" : "pgm";
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Image format unspecified. Setting it to: " <<
save_parameters.image_format);
}
std::transform(
save_parameters.image_format.begin(),
save_parameters.image_format.end(),
save_parameters.image_format.begin(),
[](unsigned char c) {return std::tolower(c);});
const std::vector<std::string> BLESSED_FORMATS{"bmp", "pgm", "png"};
if (
std::find(BLESSED_FORMATS.begin(), BLESSED_FORMATS.end(), save_parameters.image_format) ==
BLESSED_FORMATS.end())
{
std::stringstream ss;
bool first = true;
for (auto & format_name : BLESSED_FORMATS) {
if (!first) {
ss << ", ";
}
ss << "'" << format_name << "'";
first = false;
}
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Requested image format '" << save_parameters.image_format <<
"' is not one of the recommended formats: " << ss.str());
}
const std::string FALLBACK_FORMAT = "png";
try {
Magick::CoderInfo info(save_parameters.image_format);
if (!info.isWritable()) {
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Format '" << save_parameters.image_format <<
"' is not writable. Using '" << FALLBACK_FORMAT << "' instead");
save_parameters.image_format = FALLBACK_FORMAT;
}
} catch (Magick::ErrorOption & e) {
RCLCPP_WARN_STREAM(
rclcpp::get_logger(
"map_io"), "Format '" << save_parameters.image_format << "' is not usable. Using '" <<
FALLBACK_FORMAT << "' instead:" << std::endl << e.what());
save_parameters.image_format = FALLBACK_FORMAT;
}
// Checking map mode
if (
save_parameters.mode == MapMode::Scale &&
(save_parameters.image_format == "pgm" ||
save_parameters.image_format == "jpg" ||
save_parameters.image_format == "jpeg"))
{
RCLCPP_WARN_STREAM(
rclcpp::get_logger("map_io"), "Map mode 'scale' requires transparency, but format '" <<
save_parameters.image_format <<
"' does not support it. Consider switching image format to 'png'.");
}
}
/**
* @brief Tries to write map data into a file
* @param map Occupancy grid data
* @param save_parameters Map saving parameters
* @throw std::expection in case of problem
*/
void tryWriteMapToFile(
const nav_msgs::msg::OccupancyGrid & map,
const SaveParameters & save_parameters)
{
RCLCPP_INFO_STREAM(
rclcpp::get_logger(
"map_io"), "Received a " << map.info.width << " X " << map.info.height << " map @ " <<
map.info.resolution << " m/pix");
std::string mapdatafile = save_parameters.map_file_name + "." + save_parameters.image_format;
{
// should never see this color, so the initialization value is just for debugging
Magick::Image image({map.info.width, map.info.height}, "red");
// In scale mode, we need the alpha (matte) channel. Else, we don't.
// NOTE: GraphicsMagick seems to have trouble loading the alpha channel when saved with
// Magick::GreyscaleMatte, so we use TrueColorMatte instead.
image.type(
save_parameters.mode == MapMode::Scale ?
Magick::TrueColorMatteType : Magick::GrayscaleType);
// Since we only need to support 100 different pixel levels, 8 bits is fine
image.depth(8);
int free_thresh_int = std::rint(save_parameters.free_thresh * 100.0);
int occupied_thresh_int = std::rint(save_parameters.occupied_thresh * 100.0);
for (size_t y = 0; y < map.info.height; y++) {
for (size_t x = 0; x < map.info.width; x++) {
int8_t map_cell = map.data[map.info.width * (map.info.height - y - 1) + x];
Magick::Color pixel;
switch (save_parameters.mode) {
case MapMode::Trinary:
if (map_cell < 0 || 100 < map_cell) {
pixel = Magick::ColorGray(205 / 255.0);
} else if (map_cell <= free_thresh_int) {
pixel = Magick::ColorGray(254 / 255.0);
} else if (occupied_thresh_int <= map_cell) {
pixel = Magick::ColorGray(0 / 255.0);
} else {
pixel = Magick::ColorGray(205 / 255.0);
}
break;
case MapMode::Scale:
if (map_cell < 0 || 100 < map_cell) {
pixel = Magick::ColorGray{0.5};
pixel.alphaQuantum(TransparentOpacity);
} else {
pixel = Magick::ColorGray{(100.0 - map_cell) / 100.0};
}
break;
case MapMode::Raw:
Magick::Quantum q;
if (map_cell < 0 || 100 < map_cell) {
q = MaxRGB;
} else {
q = map_cell / 255.0 * MaxRGB;
}
pixel = Magick::Color(q, q, q);
break;
default:
RCLCPP_ERROR_STREAM(
rclcpp::get_logger(
"map_io"), "Map mode should be Trinary, Scale or Raw");
throw std::runtime_error("Invalid map mode");
}
image.pixelColor(x, y, pixel);
}
}
RCLCPP_INFO_STREAM(
rclcpp::get_logger("map_io"),
"Writing map occupancy data to " << mapdatafile);
image.write(mapdatafile);
}
std::string mapmetadatafile = save_parameters.map_file_name + ".yaml";
{
std::ofstream yaml(mapmetadatafile);
geometry_msgs::msg::Quaternion orientation = map.info.origin.orientation;
tf2::Matrix3x3 mat(tf2::Quaternion(orientation.x, orientation.y, orientation.z, orientation.w));
double yaw, pitch, roll;
mat.getEulerYPR(yaw, pitch, roll);
const int file_name_index = mapdatafile.find_last_of("/\\");
std::string image_name = mapdatafile.substr(file_name_index + 1);
YAML::Emitter e;
e << YAML::Precision(3);
e << YAML::BeginMap;
e << YAML::Key << "image" << YAML::Value << image_name;
e << YAML::Key << "mode" << YAML::Value << map_mode_to_string(save_parameters.mode);
e << YAML::Key << "resolution" << YAML::Value << map.info.resolution;
e << YAML::Key << "origin" << YAML::Flow << YAML::BeginSeq << map.info.origin.position.x <<
map.info.origin.position.y << yaw << YAML::EndSeq;
e << YAML::Key << "negate" << YAML::Value << 0;
e << YAML::Key << "occupied_thresh" << YAML::Value << save_parameters.occupied_thresh;
e << YAML::Key << "free_thresh" << YAML::Value << save_parameters.free_thresh;
if (!e.good()) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger("map_io"), "YAML writer failed with an error " << e.GetLastError() <<
". The map metadata may be invalid.");
}
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "Writing map metadata to " << mapmetadatafile);
std::ofstream(mapmetadatafile) << e.c_str();
}
RCLCPP_INFO_STREAM(rclcpp::get_logger("map_io"), "Map saved");
}
bool saveMapToFile(
const nav_msgs::msg::OccupancyGrid & map,
const SaveParameters & save_parameters)
{
// Local copy of SaveParameters that might be modified by checkSaveParameters()
SaveParameters save_parameters_loc = save_parameters;
try {
// Checking map parameters for consistency
checkSaveParameters(save_parameters_loc);
tryWriteMapToFile(map, save_parameters_loc);
} catch (std::exception & e) {
RCLCPP_ERROR_STREAM(
rclcpp::get_logger("map_io"),
"Failed to write map for reason: " << e.what());
return false;
}
return true;
}
} // namespace nav2_map_server
@@ -0,0 +1,52 @@
// Copyright 2019 Rover Robotics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_map_server/map_mode.hpp"
#include <stdexcept>
#include <string>
namespace nav2_map_server
{
const char * map_mode_to_string(MapMode map_mode)
{
switch (map_mode) {
case MapMode::Trinary:
return "trinary";
case MapMode::Scale:
return "scale";
case MapMode::Raw:
return "raw";
default:
throw std::invalid_argument("map_mode");
}
}
MapMode map_mode_from_string(std::string map_mode_name)
{
for (auto & c : map_mode_name) {
c = tolower(c);
}
if (map_mode_name == "scale") {
return MapMode::Scale;
} else if (map_mode_name == "raw") {
return MapMode::Raw;
} else if (map_mode_name == "trinary") {
return MapMode::Trinary;
} else {
throw std::invalid_argument("map_mode_name");
}
}
} // namespace nav2_map_server
@@ -0,0 +1,181 @@
// Copyright 2019 Rover Robotics
// Copyright (c) 2008, Willow Garage, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include "nav2_map_server/map_mode.hpp"
#include "nav2_map_server/map_saver.hpp"
#include "rclcpp/rclcpp.hpp"
using namespace nav2_map_server; // NOLINT
const char * USAGE_STRING{
"Usage:\n"
" map_saver_cli [arguments] [--ros-args ROS remapping args]\n"
"\n"
"Arguments:\n"
" -h/--help\n"
" -t <map_topic>\n"
" -f <mapname>\n"
" --occ <threshold_occupied>\n"
" --free <threshold_free>\n"
" --fmt <image_format>\n"
" --mode trinary(default)/scale/raw\n"
"\n"
"NOTE: --ros-args should be passed at the end of command line"};
typedef enum
{
COMMAND_MAP_TOPIC,
COMMAND_MAP_FILE_NAME,
COMMAND_IMAGE_FORMAT,
COMMAND_OCCUPIED_THRESH,
COMMAND_FREE_THRESH,
COMMAND_MODE
} COMMAND_TYPE;
struct cmd_struct
{
const char * cmd;
COMMAND_TYPE command_type;
};
typedef enum
{
ARGUMENTS_INVALID,
ARGUMENTS_VALID,
HELP_MESSAGE
} ARGUMENTS_STATUS;
// Arguments parser
// Input parameters: logger, argc, argv
// Output parameters: map_topic, save_parameters
ARGUMENTS_STATUS parse_arguments(
const rclcpp::Logger & logger, int argc, char ** argv,
std::string & map_topic, SaveParameters & save_parameters)
{
const struct cmd_struct commands[] = {
{"-t", COMMAND_MAP_TOPIC},
{"-f", COMMAND_MAP_FILE_NAME},
{"--occ", COMMAND_OCCUPIED_THRESH},
{"--free", COMMAND_FREE_THRESH},
{"--mode", COMMAND_MODE},
{"--fmt", COMMAND_IMAGE_FORMAT},
};
std::vector<std::string> arguments(argv + 1, argv + argc);
std::vector<rclcpp::Parameter> params_from_args;
size_t cmd_size = sizeof(commands) / sizeof(commands[0]);
size_t i;
for (auto it = arguments.begin(); it != arguments.end(); it++) {
if (*it == "-h" || *it == "--help") {
std::cout << USAGE_STRING << std::endl;
return HELP_MESSAGE;
}
if (*it == "--ros-args") {
break;
}
for (i = 0; i < cmd_size; i++) {
if (commands[i].cmd == *it) {
if ((it + 1) == arguments.end()) {
RCLCPP_ERROR(logger, "Wrong argument: %s should be followed by a value.", it->c_str());
return ARGUMENTS_INVALID;
}
it++;
switch (commands[i].command_type) {
case COMMAND_MAP_TOPIC:
map_topic = *it;
break;
case COMMAND_MAP_FILE_NAME:
save_parameters.map_file_name = *it;
break;
case COMMAND_FREE_THRESH:
save_parameters.free_thresh = atof(it->c_str());
break;
case COMMAND_OCCUPIED_THRESH:
save_parameters.occupied_thresh = atof(it->c_str());
break;
case COMMAND_IMAGE_FORMAT:
save_parameters.image_format = *it;
break;
case COMMAND_MODE:
try {
save_parameters.mode = map_mode_from_string(*it);
} catch (std::invalid_argument &) {
save_parameters.mode = MapMode::Trinary;
RCLCPP_WARN(
logger,
"Map mode parameter not recognized: %s, using default value (trinary)",
it->c_str());
}
break;
}
break;
}
}
if (i == cmd_size) {
RCLCPP_ERROR(logger, "Wrong argument: %s", it->c_str());
return ARGUMENTS_INVALID;
}
}
return ARGUMENTS_VALID;
}
int main(int argc, char ** argv)
{
// ROS2 init
rclcpp::init(argc, argv);
auto logger = rclcpp::get_logger("map_saver_cli");
// Parse CLI-arguments
SaveParameters save_parameters;
std::string map_topic = "map";
switch (parse_arguments(logger, argc, argv, map_topic, save_parameters)) {
case ARGUMENTS_INVALID:
rclcpp::shutdown();
return -1;
case HELP_MESSAGE:
rclcpp::shutdown();
return 0;
case ARGUMENTS_VALID:
break;
}
// Call saveMapTopicToFile()
int retcode;
try {
auto map_saver = std::make_shared<nav2_map_server::MapSaver>();
map_saver->on_configure(rclcpp_lifecycle::State());
if (map_saver->saveMapTopicToFile(map_topic, save_parameters)) {
retcode = 0;
} else {
retcode = 1;
}
} catch (std::exception & e) {
RCLCPP_ERROR(logger, "Unexpected problem appear: %s", e.what());
retcode = -1;
}
// Exit
rclcpp::shutdown();
return retcode;
}
@@ -0,0 +1,32 @@
// Copyright (c) 2020 Samsung Research Russia
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <stdexcept>
#include <string>
#include "nav2_map_server/map_saver.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto logger = rclcpp::get_logger("map_saver_server");
auto service_node = std::make_shared<nav2_map_server::MapSaver>();
rclcpp::spin(service_node->get_node_base_interface());
rclcpp::shutdown();
return 0;
}
@@ -0,0 +1,242 @@
/*
* Copyright (c) 2020 Samsung Research Russia
* Copyright 2019 Rover Robotics
* Copyright (c) 2008, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <ORGANIZATION> nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "nav2_map_server/map_saver.hpp"
#include <string>
#include <memory>
#include <stdexcept>
#include <functional>
#include <mutex>
using namespace std::placeholders;
namespace nav2_map_server
{
MapSaver::MapSaver(const rclcpp::NodeOptions & options)
: nav2_util::LifecycleNode("map_saver", "", options)
{
RCLCPP_INFO(get_logger(), "Creating");
// Declare the node parameters
declare_parameter("save_map_timeout", 2.0);
declare_parameter("free_thresh_default", 0.25);
declare_parameter("occupied_thresh_default", 0.65);
declare_parameter("map_subscribe_transient_local", true);
}
MapSaver::~MapSaver()
{
}
nav2_util::CallbackReturn
MapSaver::on_configure(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Configuring");
// Make name prefix for services
const std::string service_prefix = get_name() + std::string("/");
save_map_timeout_ = std::make_shared<rclcpp::Duration>(
rclcpp::Duration::from_seconds(get_parameter("save_map_timeout").as_double()));
free_thresh_default_ = get_parameter("free_thresh_default").as_double();
occupied_thresh_default_ = get_parameter("occupied_thresh_default").as_double();
map_subscribe_transient_local_ = get_parameter("map_subscribe_transient_local").as_bool();
// Create a service that saves the occupancy grid from map topic to a file
save_map_service_ = create_service<nav2_msgs::srv::SaveMap>(
service_prefix + save_map_service_name_,
std::bind(&MapSaver::saveMapCallback, this, _1, _2, _3));
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapSaver::on_activate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Activating");
// create bond connection
createBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapSaver::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Deactivating");
// destroy bond connection
destroyBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapSaver::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Cleaning up");
save_map_service_.reset();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapSaver::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Shutting down");
return nav2_util::CallbackReturn::SUCCESS;
}
void MapSaver::saveMapCallback(
const std::shared_ptr<rmw_request_id_t>/*request_header*/,
const std::shared_ptr<nav2_msgs::srv::SaveMap::Request> request,
std::shared_ptr<nav2_msgs::srv::SaveMap::Response> response)
{
// Set input arguments and call saveMapTopicToFile()
SaveParameters save_parameters;
save_parameters.map_file_name = request->map_url;
save_parameters.image_format = request->image_format;
save_parameters.free_thresh = request->free_thresh;
save_parameters.occupied_thresh = request->occupied_thresh;
try {
save_parameters.mode = map_mode_from_string(request->map_mode);
} catch (std::invalid_argument &) {
save_parameters.mode = MapMode::Trinary;
RCLCPP_WARN(
get_logger(), "Map mode parameter not recognized: '%s', using default value (trinary)",
request->map_mode.c_str());
}
response->result = saveMapTopicToFile(request->map_topic, save_parameters);
}
bool MapSaver::saveMapTopicToFile(
const std::string & map_topic,
const SaveParameters & save_parameters)
{
// Local copies of map_topic and save_parameters that could be changed
std::string map_topic_loc = map_topic;
SaveParameters save_parameters_loc = save_parameters;
RCLCPP_INFO(
get_logger(), "Saving map from \'%s\' topic to \'%s\' file",
map_topic_loc.c_str(), save_parameters_loc.map_file_name.c_str());
try {
// Correct map_topic_loc if necessary
if (map_topic_loc == "") {
map_topic_loc = "map";
RCLCPP_WARN(
get_logger(), "Map topic unspecified. Map messages will be read from \'%s\' topic",
map_topic_loc.c_str());
}
// Set default for MapSaver node thresholds parameters
if (save_parameters_loc.free_thresh == 0.0) {
RCLCPP_WARN(
get_logger(),
"Free threshold unspecified. Setting it to default value: %f",
free_thresh_default_);
save_parameters_loc.free_thresh = free_thresh_default_;
}
if (save_parameters_loc.occupied_thresh == 0.0) {
RCLCPP_WARN(
get_logger(),
"Occupied threshold unspecified. Setting it to default value: %f",
occupied_thresh_default_);
save_parameters_loc.occupied_thresh = occupied_thresh_default_;
}
std::promise<nav_msgs::msg::OccupancyGrid::SharedPtr> prom;
std::future<nav_msgs::msg::OccupancyGrid::SharedPtr> future_result = prom.get_future();
// A callback function that receives map message from subscribed topic
auto mapCallback = [&prom](
const nav_msgs::msg::OccupancyGrid::SharedPtr msg) -> void {
prom.set_value(msg);
};
rclcpp::QoS map_qos(10); // initialize to default
if (map_subscribe_transient_local_) {
map_qos.transient_local();
map_qos.reliable();
map_qos.keep_last(1);
}
// Create new CallbackGroup for map_sub
auto callback_group = create_callback_group(
rclcpp::CallbackGroupType::MutuallyExclusive,
false);
auto option = rclcpp::SubscriptionOptions();
option.callback_group = callback_group;
auto map_sub = create_subscription<nav_msgs::msg::OccupancyGrid>(
map_topic_loc, map_qos, mapCallback, option);
// Create SingleThreadedExecutor to spin map_sub in callback_group
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_callback_group(callback_group, get_node_base_interface());
// Spin until map message received
auto timeout = save_map_timeout_->to_chrono<std::chrono::nanoseconds>();
auto status = executor.spin_until_future_complete(future_result, timeout);
if (status != rclcpp::FutureReturnCode::SUCCESS) {
RCLCPP_ERROR(get_logger(), "Failed to spin map subscription");
return false;
}
// map_sub is no more needed
map_sub.reset();
// Map message received. Saving it to file
nav_msgs::msg::OccupancyGrid::SharedPtr map_msg = future_result.get();
if (saveMapToFile(*map_msg, save_parameters_loc)) {
RCLCPP_INFO(get_logger(), "Map saved successfully");
return true;
} else {
RCLCPP_ERROR(get_logger(), "Failed to save the map");
return false;
}
} catch (std::exception & e) {
RCLCPP_ERROR(get_logger(), "Failed to save the map: %s", e.what());
return false;
}
return false;
}
} // namespace nav2_map_server
#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(nav2_map_server::MapSaver)
@@ -0,0 +1,30 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <stdexcept>
#include <string>
#include "nav2_map_server/map_server.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
std::string node_name("map_server");
rclcpp::init(argc, argv);
auto node = std::make_shared<nav2_map_server::MapServer>();
rclcpp::spin(node->get_node_base_interface());
rclcpp::shutdown();
}
@@ -0,0 +1,257 @@
/* Copyright (c) 2018 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Copyright 2019 Rover Robotics
* Copyright 2010 Brian Gerkey
* Copyright (c) 2008, Willow Garage, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "nav2_map_server/map_server.hpp"
#include <string>
#include <memory>
#include <fstream>
#include <stdexcept>
#include <utility>
#include "yaml-cpp/yaml.h"
#include "lifecycle_msgs/msg/state.hpp"
#include "nav2_map_server/map_io.hpp"
using namespace std::chrono_literals;
using namespace std::placeholders;
namespace nav2_map_server
{
MapServer::MapServer(const rclcpp::NodeOptions & options)
: nav2_util::LifecycleNode("map_server", "", options), map_available_(false)
{
RCLCPP_INFO(get_logger(), "Creating");
// Declare the node parameters
declare_parameter("yaml_filename", rclcpp::PARAMETER_STRING);
declare_parameter("topic_name", "map");
declare_parameter("frame_id", "map");
}
MapServer::~MapServer()
{
}
nav2_util::CallbackReturn
MapServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Configuring");
// Get the name of the YAML file to use (can be empty if no initial map should be used)
std::string yaml_filename = get_parameter("yaml_filename").as_string();
std::string topic_name = get_parameter("topic_name").as_string();
frame_id_ = get_parameter("frame_id").as_string();
// only try to load map if parameter was set
if (!yaml_filename.empty()) {
// Shared pointer to LoadMap::Response is also should be initialized
// in order to avoid null-pointer dereference
std::shared_ptr<nav2_msgs::srv::LoadMap::Response> rsp =
std::make_shared<nav2_msgs::srv::LoadMap::Response>();
if (!loadMapResponseFromYaml(yaml_filename, rsp)) {
throw std::runtime_error("Failed to load map yaml file: " + yaml_filename);
}
} else {
RCLCPP_INFO(
get_logger(),
"yaml-filename parameter is empty, set map through '%s'-service",
load_map_service_name_.c_str());
}
// Make name prefix for services
const std::string service_prefix = get_name() + std::string("/");
// Create a service that provides the occupancy grid
occ_service_ = create_service<nav_msgs::srv::GetMap>(
service_prefix + std::string(service_name_),
std::bind(&MapServer::getMapCallback, this, _1, _2, _3));
// Create a publisher using the QoS settings to emulate a ROS1 latched topic
occ_pub_ = create_publisher<nav_msgs::msg::OccupancyGrid>(
topic_name,
rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable());
// Create a service that loads the occupancy grid from a file
load_map_service_ = create_service<nav2_msgs::srv::LoadMap>(
service_prefix + std::string(load_map_service_name_),
std::bind(&MapServer::loadMapCallback, this, _1, _2, _3));
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Activating");
// Publish the map using the latched topic
occ_pub_->on_activate();
if (map_available_) {
auto occ_grid = std::make_unique<nav_msgs::msg::OccupancyGrid>(msg_);
occ_pub_->publish(std::move(occ_grid));
}
// create bond connection
createBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Deactivating");
occ_pub_->on_deactivate();
// destroy bond connection
destroyBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Cleaning up");
occ_pub_.reset();
occ_service_.reset();
load_map_service_.reset();
map_available_ = false;
msg_ = nav_msgs::msg::OccupancyGrid();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
MapServer::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Shutting down");
return nav2_util::CallbackReturn::SUCCESS;
}
void MapServer::getMapCallback(
const std::shared_ptr<rmw_request_id_t>/*request_header*/,
const std::shared_ptr<nav_msgs::srv::GetMap::Request>/*request*/,
std::shared_ptr<nav_msgs::srv::GetMap::Response> response)
{
// if not in ACTIVE state, ignore request
if (get_current_state().id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE) {
RCLCPP_WARN(
get_logger(),
"Received GetMap request but not in ACTIVE state, ignoring!");
return;
}
RCLCPP_INFO(get_logger(), "Handling GetMap request");
response->map = msg_;
}
void MapServer::loadMapCallback(
const std::shared_ptr<rmw_request_id_t>/*request_header*/,
const std::shared_ptr<nav2_msgs::srv::LoadMap::Request> request,
std::shared_ptr<nav2_msgs::srv::LoadMap::Response> response)
{
// if not in ACTIVE state, ignore request
if (get_current_state().id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE) {
RCLCPP_WARN(
get_logger(),
"Received LoadMap request but not in ACTIVE state, ignoring!");
response->result = response->RESULT_UNDEFINED_FAILURE;
return;
}
RCLCPP_INFO(get_logger(), "Handling LoadMap request");
// Load from file
if (loadMapResponseFromYaml(request->map_url, response)) {
auto occ_grid = std::make_unique<nav_msgs::msg::OccupancyGrid>(msg_);
occ_pub_->publish(std::move(occ_grid)); // publish new map
}
}
bool MapServer::loadMapResponseFromYaml(
const std::string & yaml_file,
std::shared_ptr<nav2_msgs::srv::LoadMap::Response> response)
{
switch (loadMapFromYaml(yaml_file, msg_)) {
case MAP_DOES_NOT_EXIST:
response->result = nav2_msgs::srv::LoadMap::Response::RESULT_MAP_DOES_NOT_EXIST;
return false;
case INVALID_MAP_METADATA:
response->result = nav2_msgs::srv::LoadMap::Response::RESULT_INVALID_MAP_METADATA;
return false;
case INVALID_MAP_DATA:
response->result = nav2_msgs::srv::LoadMap::Response::RESULT_INVALID_MAP_DATA;
return false;
case LOAD_MAP_SUCCESS:
// Correcting msg_ header when it belongs to specific node
updateMsgHeader();
map_available_ = true;
response->map = msg_;
response->result = nav2_msgs::srv::LoadMap::Response::RESULT_SUCCESS;
}
return true;
}
void MapServer::updateMsgHeader()
{
msg_.info.map_load_time = now();
msg_.header.frame_id = frame_id_;
msg_.header.stamp = now();
}
} // namespace nav2_map_server
#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(nav2_map_server::MapServer)
@@ -0,0 +1,8 @@
set(TEST_LAUNCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test_launch_files)
set(TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR})
add_definitions( -DTEST_DIRECTORY=\"${CMAKE_CURRENT_SOURCE_DIR}\")
add_subdirectory(unit)
add_subdirectory(component)
add_subdirectory(map_saver_cli)
@@ -0,0 +1,51 @@
include_directories(${PROJECT_SOURCE_DIR}/test)
# map_server component test
ament_add_gtest_executable(test_map_server_node
test_map_server_node.cpp
${PROJECT_SOURCE_DIR}/test/test_constants.cpp
)
ament_target_dependencies(test_map_server_node rclcpp nav_msgs)
target_link_libraries(test_map_server_node
${library_name}
)
ament_add_test(test_map_server_node
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test_map_server_launch.py"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
ENV
TEST_DIR=${TEST_DIR}
TEST_LAUNCH_DIR=${TEST_LAUNCH_DIR}
TEST_EXECUTABLE=$<TARGET_FILE:test_map_server_node>
)
# map_saver component test
ament_add_gtest_executable(test_map_saver_node
test_map_saver_node.cpp
${PROJECT_SOURCE_DIR}/test/test_constants.cpp
)
ament_target_dependencies(test_map_saver_node rclcpp nav_msgs)
target_link_libraries(test_map_saver_node
${library_name}
)
add_executable(test_map_saver_publisher
test_map_saver_publisher.cpp
${PROJECT_SOURCE_DIR}/test/test_constants.cpp
)
target_link_libraries(test_map_saver_publisher
${map_io_library_name}
)
ament_add_test(test_map_saver_node
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test_map_saver_launch.py"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
ENV
TEST_DIR=${TEST_DIR}
TEST_LAUNCH_DIR=${TEST_LAUNCH_DIR}
TEST_EXECUTABLE=$<TARGET_FILE:test_map_saver_node>
)
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
# Copyright (c) 2018 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from launch import LaunchDescription
from launch import LaunchService
from launch.actions import ExecuteProcess
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_testing.legacy import LaunchTestService
def main(argv=sys.argv[1:]):
launchFile = os.path.join(os.getenv('TEST_LAUNCH_DIR'), 'map_saver_node.launch.py')
testExecutable = os.getenv('TEST_EXECUTABLE')
ld = LaunchDescription([
IncludeLaunchDescription(PythonLaunchDescriptionSource([launchFile])),
])
test1_action = ExecuteProcess(
cmd=[testExecutable],
name='test_map_saver_node',
)
lts = LaunchTestService()
lts.add_test_action(ld, test1_action)
ls = LaunchService(argv=argv)
ls.include_launch_description(ld)
os.chdir(os.getenv('TEST_LAUNCH_DIR'))
return lts.run(ls)
if __name__ == '__main__':
sys.exit(main())
@@ -0,0 +1,222 @@
// Copyright (c) 2020 Samsung Research Russia
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <string>
#include <memory>
#include <filesystem>
#include "rclcpp/rclcpp.hpp"
#include "test_constants/test_constants.h"
#include "nav2_map_server/map_saver.hpp"
#include "nav2_util/lifecycle_service_client.hpp"
#include "nav2_msgs/srv/save_map.hpp"
#define TEST_DIR TEST_DIRECTORY
using std::filesystem::path;
using lifecycle_msgs::msg::Transition;
using namespace nav2_map_server; // NOLINT
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class MapSaverTestFixture : public ::testing::Test
{
public:
static void SetUpTestCase()
{
node_ = rclcpp::Node::make_shared("map_client_test");
lifecycle_client_ =
std::make_shared<nav2_util::LifecycleServiceClient>("map_saver", node_);
RCLCPP_INFO(node_->get_logger(), "Creating Test Node");
std::this_thread::sleep_for(std::chrono::seconds(5)); // allow node to start up
const std::chrono::seconds timeout(5);
lifecycle_client_->change_state(Transition::TRANSITION_CONFIGURE, timeout);
lifecycle_client_->change_state(Transition::TRANSITION_ACTIVATE, timeout);
}
static void TearDownTestCase()
{
lifecycle_client_->change_state(Transition::TRANSITION_DEACTIVATE);
lifecycle_client_->change_state(Transition::TRANSITION_CLEANUP);
lifecycle_client_.reset();
node_.reset();
}
template<class T>
typename T::Response::SharedPtr send_request(
rclcpp::Node::SharedPtr node,
typename rclcpp::Client<T>::SharedPtr client,
typename T::Request::SharedPtr request)
{
auto result = client->async_send_request(request);
// Wait for the result
if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) {
return result.get();
} else {
return nullptr;
}
}
protected:
// Check that map_msg corresponds to reference pattern
// Input: map_msg
void verifyMapMsg(const nav_msgs::msg::OccupancyGrid & map_msg)
{
ASSERT_FLOAT_EQ(map_msg.info.resolution, g_valid_image_res);
ASSERT_EQ(map_msg.info.width, g_valid_image_width);
ASSERT_EQ(map_msg.info.height, g_valid_image_height);
for (unsigned int i = 0; i < map_msg.info.width * map_msg.info.height; i++) {
ASSERT_EQ(g_valid_image_content[i], map_msg.data[i]);
}
}
static rclcpp::Node::SharedPtr node_;
static std::shared_ptr<nav2_util::LifecycleServiceClient> lifecycle_client_;
};
rclcpp::Node::SharedPtr MapSaverTestFixture::node_ = nullptr;
std::shared_ptr<nav2_util::LifecycleServiceClient> MapSaverTestFixture::lifecycle_client_ =
nullptr;
// Send map saving service request.
// Load saved map and verify obtained OccupancyGrid.
TEST_F(MapSaverTestFixture, SaveMap)
{
RCLCPP_INFO(node_->get_logger(), "Testing SaveMap service");
auto req = std::make_shared<nav2_msgs::srv::SaveMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::SaveMap>(
"/map_saver/save_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for save_map service");
ASSERT_TRUE(client->wait_for_service());
// 1. Send valid save_map serivce request
req->map_topic = "map";
req->map_url = path(g_tmp_dir) / path(g_valid_map_name);
req->image_format = "png";
req->map_mode = "trinary";
req->free_thresh = g_default_free_thresh;
req->occupied_thresh = g_default_occupied_thresh;
auto resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, true);
// 2. Load saved map and verify it
nav_msgs::msg::OccupancyGrid map_msg;
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Send map saving service request with default parameters.
// Load saved map and verify obtained OccupancyGrid.
TEST_F(MapSaverTestFixture, SaveMapDefaultParameters)
{
RCLCPP_INFO(node_->get_logger(), "Testing SaveMap service");
auto req = std::make_shared<nav2_msgs::srv::SaveMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::SaveMap>(
"/map_saver/save_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for save_map service");
ASSERT_TRUE(client->wait_for_service());
// 1. Send save_map serivce request with default parameters
req->map_topic = "";
req->map_url = path(g_tmp_dir) / path(g_valid_map_name);
req->image_format = "";
req->map_mode = "";
req->free_thresh = 0.0;
req->occupied_thresh = 0.0;
auto resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, true);
// 2. Load saved map and verify it
nav_msgs::msg::OccupancyGrid map_msg;
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Send map saving service requests with different sets of parameters.
// In case of map is expected to be saved correctly, load map from a saved
// file and verify obtained OccupancyGrid.
TEST_F(MapSaverTestFixture, SaveMapInvalidParameters)
{
RCLCPP_INFO(node_->get_logger(), "Testing SaveMap service");
auto req = std::make_shared<nav2_msgs::srv::SaveMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::SaveMap>(
"/map_saver/save_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for save_map service");
ASSERT_TRUE(client->wait_for_service());
// 1. Trying to send save_map serivce request with different sets of parameters
// In case of map is expected to be saved correctly, verify it
req->map_topic = "invalid_map";
req->map_url = path(g_tmp_dir) / path(g_valid_map_name);
req->image_format = "png";
req->map_mode = "trinary";
req->free_thresh = g_default_free_thresh;
req->occupied_thresh = g_default_occupied_thresh;
auto resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, false);
req->map_topic = "map";
req->image_format = "invalid_format";
resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, true);
nav_msgs::msg::OccupancyGrid map_msg;
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
req->image_format = "png";
req->map_mode = "invalid_mode";
resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, true);
status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
req->map_mode = "trinary";
req->free_thresh = 2.0;
req->occupied_thresh = 2.0;
resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, false);
req->free_thresh = -2.0;
req->occupied_thresh = -2.0;
resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, false);
req->free_thresh = 0.7;
req->occupied_thresh = 0.2;
resp = send_request<nav2_msgs::srv::SaveMap>(node_, client, req);
ASSERT_EQ(resp->result, false);
}
@@ -0,0 +1,59 @@
// Copyright (c) 2020 Samsung Research Russia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <filesystem>
#include <string>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "nav2_map_server/map_io.hpp"
#include "test_constants/test_constants.h"
#define TEST_DIR TEST_DIRECTORY
using namespace nav2_map_server; // NOLINT
using std::filesystem::path;
class TestPublisher : public rclcpp::Node
{
public:
TestPublisher()
: Node("map_publisher")
{
std::string pub_map_file = path(TEST_DIR) / path(g_valid_yaml_file);
nav_msgs::msg::OccupancyGrid msg;
LOAD_MAP_STATUS status = loadMapFromYaml(pub_map_file, msg);
if (status != LOAD_MAP_SUCCESS) {
RCLCPP_ERROR(get_logger(), "Can not load %s map file", pub_map_file.c_str());
return;
}
map_pub_ = create_publisher<nav_msgs::msg::OccupancyGrid>(
"map",
rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable());
map_pub_->publish(msg);
}
protected:
rclcpp::Publisher<nav_msgs::msg::OccupancyGrid>::SharedPtr map_pub_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto pub_node = std::make_shared<TestPublisher>();
rclcpp::spin(pub_node);
rclcpp::shutdown();
return 0;
}
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
# Copyright (c) 2018 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from launch import LaunchDescription
from launch import LaunchService
from launch.actions import ExecuteProcess
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_testing.legacy import LaunchTestService
def main(argv=sys.argv[1:]):
launchFile = os.path.join(os.getenv('TEST_LAUNCH_DIR'), 'map_server_node.launch.py')
testExecutable = os.getenv('TEST_EXECUTABLE')
ld = LaunchDescription([
IncludeLaunchDescription(PythonLaunchDescriptionSource([launchFile])),
])
test1_action = ExecuteProcess(
cmd=[testExecutable],
name='test_map_server_node',
)
lts = LaunchTestService()
lts.add_test_action(ld, test1_action)
ls = LaunchService(argv=argv)
ls.include_launch_description(ld)
os.chdir(os.getenv('TEST_LAUNCH_DIR'))
return lts.run(ls)
if __name__ == '__main__':
sys.exit(main())
@@ -0,0 +1,237 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <string>
#include <memory>
#include <filesystem>
#include <rclcpp/rclcpp.hpp>
#include "test_constants/test_constants.h"
#include "nav2_map_server/map_server.hpp"
#include "nav2_util/lifecycle_service_client.hpp"
#include "nav2_msgs/srv/load_map.hpp"
using namespace std::chrono_literals;
using namespace rclcpp; // NOLINT
#define TEST_DIR TEST_DIRECTORY
using std::filesystem::path;
using lifecycle_msgs::msg::Transition;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class MapServerTestFixture : public ::testing::Test
{
public:
static void SetUpTestCase()
{
node_ = rclcpp::Node::make_shared("map_client_test");
lifecycle_client_ =
std::make_shared<nav2_util::LifecycleServiceClient>("map_server", node_);
RCLCPP_INFO(node_->get_logger(), "Creating Test Node");
std::this_thread::sleep_for(std::chrono::seconds(5)); // allow node to start up
const std::chrono::seconds timeout(5);
lifecycle_client_->change_state(Transition::TRANSITION_CONFIGURE, timeout);
lifecycle_client_->change_state(Transition::TRANSITION_ACTIVATE, timeout);
}
static void TearDownTestCase()
{
lifecycle_client_->change_state(Transition::TRANSITION_DEACTIVATE);
lifecycle_client_->change_state(Transition::TRANSITION_CLEANUP);
lifecycle_client_->change_state(Transition::TRANSITION_UNCONFIGURED_SHUTDOWN);
lifecycle_client_.reset();
node_.reset();
}
template<class T>
typename T::Response::SharedPtr send_request(
rclcpp::Node::SharedPtr node,
typename rclcpp::Client<T>::SharedPtr client,
typename T::Request::SharedPtr request)
{
auto result = client->async_send_request(request);
// Wait for the result
if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) {
return result.get();
} else {
return nullptr;
}
}
protected:
// Check that map_msg corresponds to reference pattern
// Input: map_msg
void verifyMapMsg(const nav_msgs::msg::OccupancyGrid & map_msg)
{
ASSERT_FLOAT_EQ(map_msg.info.resolution, g_valid_image_res);
ASSERT_EQ(map_msg.info.width, g_valid_image_width);
ASSERT_EQ(map_msg.info.height, g_valid_image_height);
for (unsigned int i = 0; i < map_msg.info.width * map_msg.info.height; i++) {
ASSERT_EQ(g_valid_image_content[i], map_msg.data[i]);
}
}
static rclcpp::Node::SharedPtr node_;
static std::shared_ptr<nav2_util::LifecycleServiceClient> lifecycle_client_;
};
rclcpp::Node::SharedPtr MapServerTestFixture::node_ = nullptr;
std::shared_ptr<nav2_util::LifecycleServiceClient> MapServerTestFixture::lifecycle_client_ =
nullptr;
// Send map getting service request and verify obtained OccupancyGrid
TEST_F(MapServerTestFixture, GetMap)
{
RCLCPP_INFO(node_->get_logger(), "Testing GetMap service");
auto req = std::make_shared<nav_msgs::srv::GetMap::Request>();
auto client = node_->create_client<nav_msgs::srv::GetMap>(
"/map_server/map");
RCLCPP_INFO(node_->get_logger(), "Waiting for map service");
ASSERT_TRUE(client->wait_for_service());
auto resp = send_request<nav_msgs::srv::GetMap>(node_, client, req);
verifyMapMsg(resp->map);
}
// Send map loading service request and verify obtained OccupancyGrid
TEST_F(MapServerTestFixture, LoadMap)
{
RCLCPP_INFO(node_->get_logger(), "Testing LoadMap service");
auto req = std::make_shared<nav2_msgs::srv::LoadMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::LoadMap>(
"/map_server/load_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for load_map service");
ASSERT_TRUE(client->wait_for_service());
req->map_url = path(TEST_DIR) / path(g_valid_yaml_file);
auto resp = send_request<nav2_msgs::srv::LoadMap>(node_, client, req);
ASSERT_EQ(resp->result, nav2_msgs::srv::LoadMap::Response::RESULT_SUCCESS);
verifyMapMsg(resp->map);
}
// Send map loading service request without specifying which map to load
TEST_F(MapServerTestFixture, LoadMapNull)
{
RCLCPP_INFO(node_->get_logger(), "Testing LoadMap service");
auto req = std::make_shared<nav2_msgs::srv::LoadMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::LoadMap>(
"/map_server/load_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for load_map service");
ASSERT_TRUE(client->wait_for_service());
req->map_url = "";
RCLCPP_INFO(node_->get_logger(), "Sending load_map request with null file name");
auto resp = send_request<nav2_msgs::srv::LoadMap>(node_, client, req);
ASSERT_EQ(resp->result, nav2_msgs::srv::LoadMap::Response::RESULT_MAP_DOES_NOT_EXIST);
}
// Send map loading service request with non-existing yaml file
TEST_F(MapServerTestFixture, LoadMapInvalidYaml)
{
RCLCPP_INFO(node_->get_logger(), "Testing LoadMap service");
auto req = std::make_shared<nav2_msgs::srv::LoadMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::LoadMap>(
"/map_server/load_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for load_map service");
ASSERT_TRUE(client->wait_for_service());
req->map_url = "invalid_file.yaml";
RCLCPP_INFO(node_->get_logger(), "Sending load_map request with invalid yaml file name");
auto resp = send_request<nav2_msgs::srv::LoadMap>(node_, client, req);
ASSERT_EQ(resp->result, nav2_msgs::srv::LoadMap::Response::RESULT_INVALID_MAP_METADATA);
}
// Send map loading service request with yaml file containing non-existing map
TEST_F(MapServerTestFixture, LoadMapInvalidImage)
{
RCLCPP_INFO(node_->get_logger(), "Testing LoadMap service");
auto req = std::make_shared<nav2_msgs::srv::LoadMap::Request>();
auto client = node_->create_client<nav2_msgs::srv::LoadMap>(
"/map_server/load_map");
RCLCPP_INFO(node_->get_logger(), "Waiting for load_map service");
ASSERT_TRUE(client->wait_for_service());
req->map_url = path(TEST_DIR) / "invalid_image.yaml";
RCLCPP_INFO(node_->get_logger(), "Sending load_map request with invalid image file name");
auto resp = send_request<nav2_msgs::srv::LoadMap>(node_, client, req);
ASSERT_EQ(resp->result, nav2_msgs::srv::LoadMap::Response::RESULT_INVALID_MAP_DATA);
}
/**
* Test behaviour of server if yaml_filename is set to an empty string.
*/
TEST_F(MapServerTestFixture, NoInitialMap)
{
// turn off node into unconfigured state
lifecycle_client_->change_state(Transition::TRANSITION_DEACTIVATE);
lifecycle_client_->change_state(Transition::TRANSITION_CLEANUP);
auto client = node_->create_client<nav_msgs::srv::GetMap>("/map_server/map");
auto req = std::make_shared<nav_msgs::srv::GetMap::Request>();
auto parameters_client = std::make_shared<rclcpp::SyncParametersClient>(node_, "map_server");
ASSERT_TRUE(parameters_client->wait_for_service(3s));
// set yaml_filename-parameter to empty string (essentially restart the node)
RCLCPP_INFO(node_->get_logger(), "Removing yaml_filename-parameter before restarting");
parameters_client->set_parameters({Parameter("yaml_filename", ParameterValue(""))});
// only configure node, to test behaviour of service while node is not active
lifecycle_client_->change_state(Transition::TRANSITION_CONFIGURE, 3s);
RCLCPP_INFO(node_->get_logger(), "Testing LoadMap service while not being active");
auto load_map_req = std::make_shared<nav2_msgs::srv::LoadMap::Request>();
auto load_map_cl = node_->create_client<nav2_msgs::srv::LoadMap>("/map_server/load_map");
ASSERT_TRUE(load_map_cl->wait_for_service(3s));
auto resp = send_request<nav2_msgs::srv::LoadMap>(node_, load_map_cl, load_map_req);
ASSERT_EQ(resp->result, nav2_msgs::srv::LoadMap::Response::RESULT_UNDEFINED_FAILURE);
// activate server and load map:
lifecycle_client_->change_state(Transition::TRANSITION_ACTIVATE, 3s);
RCLCPP_INFO(node_->get_logger(), "active again");
load_map_req->map_url = path(TEST_DIR) / path(g_valid_yaml_file);
auto load_res = send_request<nav2_msgs::srv::LoadMap>(node_, load_map_cl, load_map_req);
ASSERT_EQ(load_res->result, nav2_msgs::srv::LoadMap::Response::RESULT_SUCCESS);
verifyMapMsg(load_res->map);
}
@@ -0,0 +1,7 @@
image: "invalid.png"
resolution: 0.1
origin: [2.0, 3.0, 1.0]
occupied_thresh: 0.65
free_thresh: 0.196
negate: 0
mode: "trinary"
@@ -0,0 +1,12 @@
include_directories(${PROJECT_SOURCE_DIR}/test)
# map_saver CLI
ament_add_gtest(test_map_saver_cli
test_map_saver_cli.cpp
${PROJECT_SOURCE_DIR}/test/test_constants.cpp
)
ament_target_dependencies(test_map_saver_cli rclcpp nav_msgs)
target_link_libraries(test_map_saver_cli
${dependencies}
)
@@ -0,0 +1,146 @@
// Copyright (c) 2020 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include <memory>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "nav_msgs/msg/occupancy_grid.hpp"
TEST(MapSaverCLI, CLITest)
{
std::string path = "/tmp/";
std::string file = "test_map";
std::string file_path = path + file;
rclcpp::init(0, nullptr);
auto node = std::make_shared<rclcpp::Node>("CLI_Test_Node");
RCLCPP_INFO(node->get_logger(), "Testing Map Saver CLI");
auto publisher = node->create_publisher<nav_msgs::msg::OccupancyGrid>(
"/map",
rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable());
auto msg = std::make_unique<nav_msgs::msg::OccupancyGrid>();
msg->header.frame_id = "map";
msg->header.stamp = node->now();
msg->info.map_load_time = node->now();
msg->info.resolution = 0.05;
msg->info.width = 3;
msg->info.height = 3;
msg->info.origin.position.x = 0.0;
msg->info.origin.position.y = 0.0;
msg->info.origin.orientation.w = 1.0;
msg->data.resize(9);
msg->data[0] = 0;
msg->data[2] = 100;
msg->data[1] = 101;
msg->data[3] = 50;
RCLCPP_INFO(node->get_logger(), "Publishing occupancy grid...");
publisher->publish(std::move(msg));
rclcpp::Rate(1).sleep();
// succeed on real map
RCLCPP_INFO(node->get_logger(), "Calling saver...");
EXPECT_FALSE(std::filesystem::exists(file_path + ".yaml"));
std::string command =
std::string(
"ros2 run nav2_map_server map_saver_cli -f ") + file_path;
auto return_code = system(command.c_str());
EXPECT_EQ(return_code, 0);
rclcpp::Rate(0.25).sleep();
RCLCPP_INFO(node->get_logger(), "Checking on file...");
EXPECT_TRUE(std::filesystem::exists(file_path + ".pgm"));
EXPECT_EQ(std::filesystem::file_size(file_path + ".pgm"), 20ul);
if (std::filesystem::exists(file_path + ".yaml")) {
std::filesystem::remove(file_path + ".yaml");
}
if (std::filesystem::exists(file_path + ".pgm")) {
std::filesystem::remove(file_path + ".pgm");
}
// fail on bogus map
RCLCPP_INFO(node->get_logger(), "Calling saver...");
EXPECT_FALSE(std::filesystem::exists(file_path + ".yaml"));
command =
std::string(
"ros2 run nav2_map_server map_saver_cli "
"-t map_failure --occ 100 --free 2 --mode trinary --fmt png -f ") + file_path +
std::string("--ros-args __node:=map_saver_test_node");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 65280);
rclcpp::Rate(0.25).sleep();
RCLCPP_INFO(node->get_logger(), "Checking on file...");
EXPECT_FALSE(std::filesystem::exists(file_path + ".yaml"));
RCLCPP_INFO(node->get_logger(), "Testing help...");
command =
std::string(
"ros2 run nav2_map_server map_saver_cli -h");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 0);
rclcpp::Rate(0.5).sleep();
RCLCPP_INFO(node->get_logger(), "Testing invalid mode...");
command =
std::string(
"ros2 run nav2_map_server map_saver_cli --mode fake_mode");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 0);
rclcpp::Rate(0.5).sleep();
RCLCPP_INFO(node->get_logger(), "Testing missing argument...");
command =
std::string(
"ros2 run nav2_map_server map_saver_cli --mode");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 65280);
rclcpp::Rate(0.5).sleep();
RCLCPP_INFO(node->get_logger(), "Testing wrong argument...");
command =
std::string(
"ros2 run nav2_map_server map_saver_cli --free 0 0");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 65280);
rclcpp::Rate(0.5).sleep();
command =
std::string(
"ros2 run nav2_map_server map_saver_cli --ros-args -r __node:=map_saver_test_node");
return_code = system(command.c_str());
EXPECT_EQ(return_code, 0);
}
@@ -0,0 +1,5 @@
map_saver:
ros__parameters:
save_map_timeout: 5.0
free_thresh_default: 0.196
occupied_thresh_default: 0.65
@@ -0,0 +1,3 @@
map_server:
ros__parameters:
yaml_filename: "../testmap.yaml"
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2008, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* Author: Brian Gerkey */
/* This file contains global constants shared among tests */
/* Note that these must be changed if the test image changes */
#include "test_constants/test_constants.h"
#include <vector>
const unsigned int g_valid_image_width = 10;
const unsigned int g_valid_image_height = 10;
// Note that the image content is given in row-major order, with the
// lower-left pixel first. This is different from a graphics coordinate
// system, which starts with the upper-left pixel. The loadMapFromFile
// call converts from the latter to the former when it loads the image, and
// we want to compare against the result of that conversion.
const char g_valid_image_content[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100, 100, 100, 100, 0, 0, 100, 100, 100, 0,
100, 100, 100, 100, 0, 0, 100, 100, 100, 0,
100, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100, 0, 0, 0, 0, 0, 100, 100, 0, 0,
100, 0, 0, 0, 0, 0, 100, 100, 0, 0,
100, 0, 0, 0, 0, 0, 100, 100, 0, 0,
100, 0, 0, 0, 0, 0, 100, 100, 0, 0,
100, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
const char * g_valid_map_name = "testmap";
const char * g_valid_png_file = "testmap.png";
const char * g_valid_bmp_file = "testmap.bmp";
const char * g_valid_pgm_file = "testmap.pgm";
const char * g_valid_yaml_file = "testmap.yaml";
const char * g_tmp_dir = "/tmp";
const double g_valid_image_res = 0.1;
const std::vector<double> g_valid_origin{2.0, 3.0, 1.0};
const double g_default_free_thresh = 0.196;
const double g_default_occupied_thresh = 0.65;
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2008, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TEST_CONSTANTS__TEST_CONSTANTS_H_
#define TEST_CONSTANTS__TEST_CONSTANTS_H_
/* Author: Brian Gerkey */
/* This file externs global constants shared among tests */
#include <vector>
extern const unsigned int g_valid_image_width;
extern const unsigned int g_valid_image_height;
extern const char g_valid_image_content[];
extern const char * g_valid_map_name;
extern const char * g_valid_png_file;
extern const char * g_valid_bmp_file;
extern const char * g_valid_pgm_file;
extern const char * g_valid_yaml_file;
extern const char * g_tmp_dir;
extern const double g_valid_image_res;
// *INDENT-OFF*
// Uncrustify may incorrectly guide to add extra spaces in < double > during CI tests
extern const std::vector<double> g_valid_origin;
// *INDENT-ON*
extern const double g_default_free_thresh;
extern const double g_default_occupied_thresh;
#endif // TEST_CONSTANTS__TEST_CONSTANTS_H_
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
# Copyright (c) 2018 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from launch import LaunchDescription
from launch.actions import ExecuteProcess
import launch_ros.actions
def generate_launch_description():
map_publisher = f"{os.path.dirname(os.getenv('TEST_EXECUTABLE'))}/test_map_saver_publisher"
ld = LaunchDescription()
map_saver_server_cmd = launch_ros.actions.Node(
package='nav2_map_server',
executable='map_saver_server',
output='screen',
parameters=[os.path.join(os.getenv('TEST_DIR'),
'map_saver_params.yaml')])
map_publisher_cmd = ExecuteProcess(
cmd=[map_publisher])
ld.add_action(map_saver_server_cmd)
ld.add_action(map_publisher_cmd)
return ld
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
# Copyright (c) 2018 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from launch import LaunchDescription
import launch_ros.actions
def generate_launch_description():
return LaunchDescription([
launch_ros.actions.Node(
package='nav2_map_server',
executable='map_server',
output='screen',
parameters=[os.path.join(os.getenv('TEST_DIR'),
'map_server_params.yaml')])
])
Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

@@ -0,0 +1,7 @@
image: "testmap.png"
resolution: 0.1
origin: [2.0, 3.0, 1.0]
occupied_thresh: 0.65
free_thresh: 0.196
negate: 0
mode: "trinary"
@@ -0,0 +1,21 @@
include_directories(${PROJECT_SOURCE_DIR}/test)
# map_io unit test
ament_add_gtest(test_map_io test_map_io.cpp ${PROJECT_SOURCE_DIR}/test/test_constants.cpp)
ament_target_dependencies(test_map_io rclcpp nav_msgs)
target_link_libraries(test_map_io
${map_io_library_name}
)
# costmap_filter_info_server unit test
ament_add_gtest(test_costmap_filter_info_server
test_costmap_filter_info_server.cpp
)
ament_target_dependencies(test_costmap_filter_info_server rclcpp)
target_link_libraries(test_costmap_filter_info_server
${library_name}
)
@@ -0,0 +1,177 @@
// Copyright (c) 2020 Samsung Research Russia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <string>
#include <memory>
#include <chrono>
#include <limits>
#include <mutex>
#include "rclcpp/rclcpp.hpp"
#include "nav2_map_server/costmap_filter_info_server.hpp"
using namespace std::chrono_literals;
typedef std::recursive_mutex mutex_t;
static const char FILTER_INFO_TOPIC[] = "filter_info";
static const int TYPE = 1;
static const char MASK_TOPIC[] = "mask";
static const double BASE = 0.1;
static const double MULTIPLIER = 0.2;
static const double EPSILON = std::numeric_limits<float>::epsilon();
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class InfoServerWrapper : public nav2_map_server::CostmapFilterInfoServer
{
public:
void start()
{
on_configure(get_current_state());
on_activate(get_current_state());
}
void stop()
{
on_deactivate(get_current_state());
on_cleanup(get_current_state());
on_shutdown(get_current_state());
}
void deactivate()
{
on_deactivate(get_current_state());
}
void activate()
{
on_activate(get_current_state());
}
};
class InfoServerTester : public ::testing::Test
{
public:
InfoServerTester()
: info_server_(nullptr), info_(nullptr), subscription_(nullptr)
{
access_ = new mutex_t();
info_server_ = std::make_shared<InfoServerWrapper>();
try {
info_server_->set_parameter(rclcpp::Parameter("filter_info_topic", FILTER_INFO_TOPIC));
info_server_->set_parameter(rclcpp::Parameter("type", TYPE));
info_server_->set_parameter(rclcpp::Parameter("mask_topic", MASK_TOPIC));
info_server_->set_parameter(rclcpp::Parameter("base", BASE));
info_server_->set_parameter(rclcpp::Parameter("multiplier", MULTIPLIER));
} catch (rclcpp::exceptions::ParameterNotDeclaredException & ex) {
RCLCPP_ERROR(
info_server_->get_logger(),
"Error while setting parameters for CostmapFilterInfoServer: %s", ex.what());
throw;
}
info_server_->start();
subscription_ = info_server_->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
FILTER_INFO_TOPIC, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&InfoServerTester::infoCallback, this, std::placeholders::_1));
}
~InfoServerTester()
{
info_server_->stop();
info_server_.reset();
subscription_.reset();
}
bool isReceived()
{
std::lock_guard<mutex_t> guard(*getMutex());
if (info_) {
return true;
} else {
return false;
}
}
mutex_t * getMutex()
{
return access_;
}
protected:
std::shared_ptr<InfoServerWrapper> info_server_;
nav2_msgs::msg::CostmapFilterInfo::SharedPtr info_;
private:
void infoCallback(const nav2_msgs::msg::CostmapFilterInfo::SharedPtr msg)
{
std::lock_guard<mutex_t> guard(*getMutex());
info_ = msg;
}
rclcpp::Subscription<nav2_msgs::msg::CostmapFilterInfo>::SharedPtr subscription_;
mutex_t * access_;
};
TEST_F(InfoServerTester, testCostmapFilterInfoPublish)
{
rclcpp::Time start_time = info_server_->now();
while (!isReceived()) {
rclcpp::spin_some(info_server_->get_node_base_interface());
std::this_thread::sleep_for(100ms);
// Waiting no more than 5 seconds
ASSERT_TRUE((info_server_->now() - start_time) <= rclcpp::Duration(5000ms));
}
// Checking received CostmapFilterInfo for consistency
EXPECT_EQ(info_->type, TYPE);
EXPECT_EQ(info_->filter_mask_topic, MASK_TOPIC);
EXPECT_NEAR(info_->base, BASE, EPSILON);
EXPECT_NEAR(info_->multiplier, MULTIPLIER, EPSILON);
}
TEST_F(InfoServerTester, testCostmapFilterInfoDeactivateActivate)
{
info_server_->deactivate();
info_ = nullptr;
info_server_->activate();
rclcpp::Time start_time = info_server_->now();
while (!isReceived()) {
rclcpp::spin_some(info_server_->get_node_base_interface());
std::this_thread::sleep_for(100ms);
// Waiting no more than 5 seconds
ASSERT_TRUE((info_server_->now() - start_time) <= rclcpp::Duration(5000ms));
}
// Checking received CostmapFilterInfo for consistency
EXPECT_EQ(info_->type, TYPE);
EXPECT_EQ(info_->filter_mask_topic, MASK_TOPIC);
EXPECT_NEAR(info_->base, BASE, EPSILON);
EXPECT_NEAR(info_->multiplier, MULTIPLIER, EPSILON);
}
@@ -0,0 +1,340 @@
// Copyright 2019 Rover Robotics
/*
* Copyright (c) 2008, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* Author: Brian Gerkey */
#include <gtest/gtest.h>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <fstream>
#include "yaml-cpp/yaml.h"
#include "nav2_map_server/map_io.hpp"
#include "nav2_map_server/map_server.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "test_constants/test_constants.h"
#define TEST_DIR TEST_DIRECTORY
using namespace std; // NOLINT
using namespace nav2_map_server; // NOLINT
using std::filesystem::path;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class MapIOTester : public ::testing::Test
{
protected:
// Fill LoadParameters with standard for testing values
// Input: image_file_name
// Output: load_parameters
void fillLoadParameters(
const std::string & image_file_name,
LoadParameters & load_parameters)
{
load_parameters.image_file_name = image_file_name;
load_parameters.resolution = g_valid_image_res;
load_parameters.origin = g_valid_origin;
load_parameters.free_thresh = g_default_free_thresh;
load_parameters.occupied_thresh = g_default_occupied_thresh;
load_parameters.mode = MapMode::Trinary;
load_parameters.negate = 0;
}
// Fill SaveParameters with standard for testing values
// Input: map_file_name, image_format
// Output: save_parameters
void fillSaveParameters(
const std::string & map_file_name,
const std::string & image_format,
SaveParameters & save_parameters)
{
save_parameters.map_file_name = map_file_name;
save_parameters.image_format = image_format;
save_parameters.free_thresh = g_default_free_thresh;
save_parameters.occupied_thresh = g_default_occupied_thresh;
save_parameters.mode = MapMode::Trinary;
}
// Check that map_msg corresponds to reference pattern
// Input: map_msg
void verifyMapMsg(const nav_msgs::msg::OccupancyGrid & map_msg)
{
ASSERT_FLOAT_EQ(map_msg.info.resolution, g_valid_image_res);
ASSERT_EQ(map_msg.info.width, g_valid_image_width);
ASSERT_EQ(map_msg.info.height, g_valid_image_height);
for (unsigned int i = 0; i < map_msg.info.width * map_msg.info.height; i++) {
ASSERT_EQ(g_valid_image_content[i], map_msg.data[i]);
}
}
};
// Load a valid reference PGM file. Check obtained OccupancyGrid message for consistency:
// loaded image should match the known dimensions and content of the file.
// Save obtained OccupancyGrid message into a tmp PGM file. Then load back saved tmp file
// and check for consistency.
// Succeeds all steps were passed without a problem or expection.
TEST_F(MapIOTester, loadSaveValidPGM)
{
// 1. Load reference map file and verify obtained OccupancyGrid
LoadParameters loadParameters;
fillLoadParameters(path(TEST_DIR) / path(g_valid_pgm_file), loadParameters);
nav_msgs::msg::OccupancyGrid map_msg;
ASSERT_NO_THROW(loadMapFromFile(loadParameters, map_msg));
verifyMapMsg(map_msg);
// 2. Save OccupancyGrid into a tmp file
SaveParameters saveParameters;
fillSaveParameters(path(g_tmp_dir) / path(g_valid_map_name), "pgm", saveParameters);
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
// 3. Load saved map and verify it
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Load a valid reference PNG file. Check obtained OccupancyGrid message for consistency:
// loaded image should match the known dimensions and content of the file.
// Save obtained OccupancyGrid message into a tmp PNG file. Then load back saved tmp file
// and check for consistency.
// Succeeds all steps were passed without a problem or expection.
TEST_F(MapIOTester, loadSaveValidPNG)
{
// 1. Load reference map file and verify obtained OccupancyGrid
LoadParameters loadParameters;
fillLoadParameters(path(TEST_DIR) / path(g_valid_png_file), loadParameters);
nav_msgs::msg::OccupancyGrid map_msg;
ASSERT_NO_THROW(loadMapFromFile(loadParameters, map_msg));
verifyMapMsg(map_msg);
// 2. Save OccupancyGrid into a tmp file
SaveParameters saveParameters;
fillSaveParameters(path(g_tmp_dir) / path(g_valid_map_name), "png", saveParameters);
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
// 3. Load saved map and verify it
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Load a valid reference BMP file. Check obtained OccupancyGrid message for consistency:
// loaded image should match the known dimensions and content of the file.
// Save obtained OccupancyGrid message into a tmp BMP file. Then load back saved tmp file
// and check for consistency.
// Succeeds all steps were passed without a problem or expection.
TEST_F(MapIOTester, loadSaveValidBMP)
{
// 1. Load reference map file and verify obtained OccupancyGrid
auto test_bmp = path(TEST_DIR) / path(g_valid_bmp_file);
LoadParameters loadParameters;
fillLoadParameters(test_bmp, loadParameters);
nav_msgs::msg::OccupancyGrid map_msg;
ASSERT_NO_THROW(loadMapFromFile(loadParameters, map_msg));
verifyMapMsg(map_msg);
// 2. Save OccupancyGrid into a tmp file
SaveParameters saveParameters;
fillSaveParameters(path(g_tmp_dir) / path(g_valid_map_name), "bmp", saveParameters);
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
// 3. Load saved map and verify it
LOAD_MAP_STATUS status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Load map from a valid file. Trying to save map with different modes.
// Succeeds all steps were passed without a problem or expection.
TEST_F(MapIOTester, loadSaveMapModes)
{
// 1. Load map from YAML file
nav_msgs::msg::OccupancyGrid map_msg;
LOAD_MAP_STATUS status = loadMapFromYaml(path(TEST_DIR) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
// No need to check Trinary mode. This already verified in previous testcases.
// 2. Save map in Scale mode.
SaveParameters saveParameters;
fillSaveParameters(path(g_tmp_dir) / path(g_valid_map_name), "png", saveParameters);
saveParameters.mode = MapMode::Scale;
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
// 3. Load saved map and verify it
status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
// 4. Save map in Raw mode.
saveParameters.mode = MapMode::Raw;
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
// 5. Load saved map and verify it
status = loadMapFromYaml(path(g_tmp_dir) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
verifyMapMsg(map_msg);
}
// Try to load an invalid file with different ways.
// Succeeds if all cases are got expected fail behaviours.
TEST_F(MapIOTester, loadInvalidFile)
{
// 1. Trying to load incorrect map by loadMapFromFile()
auto test_invalid = path(TEST_DIR) / path("foo");
LoadParameters loadParameters;
fillLoadParameters(test_invalid, loadParameters);
nav_msgs::msg::OccupancyGrid map_msg;
ASSERT_ANY_THROW(loadMapFromFile(loadParameters, map_msg));
// 2. Trying to load incorrect map by loadMapFromYaml()
LOAD_MAP_STATUS status = loadMapFromYaml("", map_msg);
ASSERT_EQ(status, MAP_DOES_NOT_EXIST);
status = loadMapFromYaml(std::string(test_invalid) + ".yaml", map_msg);
ASSERT_EQ(status, INVALID_MAP_METADATA);
}
// Load map from a valid file. Trying to save map with different sets of parameters.
// Succeeds if all cases got expected behaviours.
TEST_F(MapIOTester, saveInvalidParameters)
{
// 1. Load map from YAML file
nav_msgs::msg::OccupancyGrid map_msg;
LOAD_MAP_STATUS status = loadMapFromYaml(path(TEST_DIR) / path(g_valid_yaml_file), map_msg);
ASSERT_EQ(status, LOAD_MAP_SUCCESS);
// 2. Trying to save map with different sets of parameters
SaveParameters saveParameters;
saveParameters.map_file_name = path(g_tmp_dir) / path(g_valid_map_name);
saveParameters.image_format = "";
saveParameters.free_thresh = 2.0;
saveParameters.occupied_thresh = 2.0;
saveParameters.mode = MapMode::Trinary;
ASSERT_FALSE(saveMapToFile(map_msg, saveParameters));
saveParameters.free_thresh = -2.0;
saveParameters.occupied_thresh = -2.0;
ASSERT_FALSE(saveMapToFile(map_msg, saveParameters));
saveParameters.free_thresh = 0.7;
saveParameters.occupied_thresh = 0.2;
ASSERT_FALSE(saveMapToFile(map_msg, saveParameters));
saveParameters.free_thresh = 0.0;
saveParameters.occupied_thresh = 0.0;
ASSERT_TRUE(saveMapToFile(map_msg, saveParameters));
saveParameters.map_file_name = path("/invalid_path") / path(g_valid_map_name);
ASSERT_FALSE(saveMapToFile(map_msg, saveParameters));
}
// Load valid YAML file and check for consistency
TEST_F(MapIOTester, loadValidYAML)
{
LoadParameters loadParameters;
ASSERT_NO_THROW(loadParameters = loadMapYaml(path(TEST_DIR) / path(g_valid_yaml_file)));
LoadParameters refLoadParameters;
fillLoadParameters(path(TEST_DIR) / path(g_valid_png_file), refLoadParameters);
ASSERT_EQ(loadParameters.image_file_name, refLoadParameters.image_file_name);
ASSERT_FLOAT_EQ(loadParameters.resolution, refLoadParameters.resolution);
ASSERT_EQ(loadParameters.origin, refLoadParameters.origin);
ASSERT_FLOAT_EQ(loadParameters.free_thresh, refLoadParameters.free_thresh);
ASSERT_FLOAT_EQ(loadParameters.occupied_thresh, refLoadParameters.occupied_thresh);
ASSERT_EQ(loadParameters.mode, refLoadParameters.mode);
ASSERT_EQ(loadParameters.negate, refLoadParameters.negate);
}
// Try to load invalid YAML file
TEST_F(MapIOTester, loadInvalidYAML)
{
LoadParameters loadParameters;
ASSERT_ANY_THROW(loadParameters = loadMapYaml(path(TEST_DIR) / path("invalid_file.yaml")));
}
TEST(HomeUserExpanderTestSuite, homeUserExpanderShouldNotChangeInputStringWhenShorterThanTwo)
{
const std::string emptyFileName{};
ASSERT_EQ(emptyFileName, expand_user_home_dir_if_needed(emptyFileName, "/home/user"));
}
TEST(
HomeUserExpanderTestSuite,
homeUserExpanderShouldNotChangeInputStringWhenInputStringDoesNotStartWithHomeSequence)
{
const std::string fileName{"valid_file.yaml"};
ASSERT_EQ(fileName, expand_user_home_dir_if_needed(fileName, "/home/user"));
}
TEST(HomeUserExpanderTestSuite, homeUserExpanderShouldNotChangeInputStringWhenHomeVariableNotFound)
{
const std::string fileName{"~/valid_file.yaml"};
ASSERT_EQ(fileName, expand_user_home_dir_if_needed(fileName, ""));
}
TEST(HomeUserExpanderTestSuite, homeUserExpanderShouldExpandHomeSequenceWhenHomeVariableSet)
{
const std::string fileName{"~/valid_file.yaml"};
const std::string expectedOutputFileName{"/home/user/valid_file.yaml"};
ASSERT_EQ(expectedOutputFileName, expand_user_home_dir_if_needed(fileName, "/home/user"));
}