|
| 1 | +// Copyright 2021 Tier IV, Inc. All rights reserved. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "freespace_planning_algorithms/abstract_algorithm.hpp" |
| 16 | +#include "freespace_planning_algorithms/astar_search.hpp" |
| 17 | +#include "freespace_planning_algorithms/rrtstar.hpp" |
| 18 | + |
| 19 | +#include <rclcpp/rclcpp.hpp> |
| 20 | +#include <rcpputils/filesystem_helper.hpp> |
| 21 | +#include <rosbag2_cpp/writer.hpp> |
| 22 | + |
| 23 | +#include <std_msgs/msg/float64.hpp> |
| 24 | + |
| 25 | +#include <gtest/gtest.h> |
| 26 | +#include <rcutils/time.h> |
| 27 | +#include <tf2/utils.h> |
| 28 | + |
| 29 | +#include <algorithm> |
| 30 | +#include <array> |
| 31 | +#include <fstream> |
| 32 | +#include <string> |
| 33 | +#include <unordered_map> |
| 34 | +#include <vector> |
| 35 | + |
| 36 | +namespace fpa = freespace_planning_algorithms; |
| 37 | + |
| 38 | +const double length_lexus = 5.5; |
| 39 | +const double width_lexus = 2.75; |
| 40 | +const fpa::VehicleShape vehicle_shape = fpa::VehicleShape(length_lexus, width_lexus, 1.5); |
| 41 | +const double pi = 3.1415926; |
| 42 | +const std::array<double, 3> start_pose1{8., 7., pi * 0.5}; |
| 43 | +const std::array<double, 3> start_pose2{9., 12., pi * 0.25}; |
| 44 | +const std::array<double, 3> start_pose3{13.3, 9., pi * 0.5}; |
| 45 | +const std::array<double, 3> start_pose4{25.0, 16., - pi * 0.25}; |
| 46 | +const std::array<double, 3> start_pose5{20.5, 8.5, 0.0}; |
| 47 | +const std::array<double, 3> goal_pose{18.0, 10.5, pi * 0.5}; |
| 48 | +const std::array<std::array<double, 3>, 5> start_poses{ |
| 49 | + start_pose1, start_pose2, start_pose3, start_pose4, start_pose5}; |
| 50 | + |
| 51 | +geometry_msgs::msg::Pose create_pose_msg(std::array<double, 3> pose3d) |
| 52 | +{ |
| 53 | + geometry_msgs::msg::Pose pose{}; |
| 54 | + tf2::Quaternion quat{}; |
| 55 | + quat.setRPY(0, 0, pose3d[2]); |
| 56 | + tf2::convert(quat, pose.orientation); |
| 57 | + pose.position.x = pose3d[0]; |
| 58 | + pose.position.y = pose3d[1]; |
| 59 | + pose.position.z = 0.0; |
| 60 | + return pose; |
| 61 | +} |
| 62 | + |
| 63 | +std_msgs::msg::Float64 create_float_msg(double val) |
| 64 | +{ |
| 65 | + std_msgs::msg::Float64 msg; |
| 66 | + msg.data = val; |
| 67 | + return msg; |
| 68 | +} |
| 69 | + |
| 70 | +nav_msgs::msg::OccupancyGrid construct_cost_map( |
| 71 | + size_t width, size_t height, double resolution, size_t n_padding) |
| 72 | +{ |
| 73 | + nav_msgs::msg::OccupancyGrid costmap_msg{}; |
| 74 | + |
| 75 | + // create info |
| 76 | + costmap_msg.info.width = width; |
| 77 | + costmap_msg.info.height = height; |
| 78 | + costmap_msg.info.resolution = resolution; |
| 79 | + |
| 80 | + // create data |
| 81 | + const size_t n_elem = width * height; |
| 82 | + for (size_t i = 0; i < n_elem; ++i) { |
| 83 | + costmap_msg.data.push_back(0.0); |
| 84 | + } |
| 85 | + |
| 86 | + for (size_t i = 0; i < height; ++i) { |
| 87 | + for (size_t j = 0; j < width; ++j) { |
| 88 | + const double x = j * resolution; |
| 89 | + const double y = i * resolution; |
| 90 | + |
| 91 | + //road |
| 92 | + if (5.4 < x && x < 5.4 + 4.5 && 0.0 < y && y < 0.0 + 19.4) { |
| 93 | + costmap_msg.data[i * width + j] = 40.0; |
| 94 | + } |
| 95 | + |
| 96 | + if (9.0 < x && x < 40.0 && 15.4 < y && y < 15.4 + 4.0) { |
| 97 | + costmap_msg.data[i * width + j] = 40.0; |
| 98 | + } |
| 99 | + |
| 100 | + double width_p = 3.0; |
| 101 | + double height_p = 6.0; |
| 102 | + |
| 103 | + //parking 1 (16,9) |
| 104 | + if (16.0 < x && x < 16.0 + width_p && 9.0 < y && y < 9.0 + height_p) { |
| 105 | + costmap_msg.data[i * width + j] = 50.0; |
| 106 | + } |
| 107 | + |
| 108 | + //parking 2 |
| 109 | + if (19.3 < x && x < 19.3 + width_p && 9.0 < y && y < 9.0 + height_p) { |
| 110 | + costmap_msg.data[i * width + j] = 50.0; |
| 111 | + } |
| 112 | + |
| 113 | + //parking 3 |
| 114 | + if (22.6 < x && x < 22.6 + width_p && 9.0 < y && y < 9.0 + height_p) { |
| 115 | + costmap_msg.data[i * width + j] = 50.0; |
| 116 | + } |
| 117 | + |
| 118 | + //parking 4 |
| 119 | + if (25.9 < x && x < 25.9 + width_p && 9.0 < y && y < 9.0 + height_p) { |
| 120 | + costmap_msg.data[i * width + j] = 50.0; |
| 121 | + } |
| 122 | + |
| 123 | + //parking 5 |
| 124 | + if (29.2 < x && x < 29.2 + width_p && 9.0 < y && y < 9.0 + height_p) { |
| 125 | + costmap_msg.data[i * width + j] = 50.0; |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + for (size_t i = 0; i < n_padding; ++i) { |
| 131 | + // fill left |
| 132 | + for (size_t j = width * i; j <= width * (i + 1); ++j) { |
| 133 | + costmap_msg.data[j] = 100.0; |
| 134 | + } |
| 135 | + // fill right |
| 136 | + for (size_t j = width * (height - n_padding + i); j <= width * (height - n_padding + i + 1); |
| 137 | + ++j) { |
| 138 | + costmap_msg.data[j] = 100.0; |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + for (size_t i = 0; i < height; ++i) { |
| 143 | + // fill bottom |
| 144 | + for (size_t j = i * width; j <= i * width + n_padding; ++j) { |
| 145 | + costmap_msg.data[j] = 100.0; |
| 146 | + } |
| 147 | + for (size_t j = (i + 1) * width - n_padding; j <= (i + 1) * width; ++j) { |
| 148 | + costmap_msg.data[j] = 100.0; |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + return costmap_msg; |
| 153 | +} |
| 154 | + |
| 155 | +template <typename MessageT> |
| 156 | +void add_message_to_rosbag( |
| 157 | + rosbag2_cpp::Writer & writer, const MessageT & message, const std::string & name, |
| 158 | + const std::string & type) |
| 159 | +{ |
| 160 | + rclcpp::SerializedMessage serialized_msg; |
| 161 | + rclcpp::Serialization<MessageT> serialization; |
| 162 | + serialization.serialize_message(&message, &serialized_msg); |
| 163 | + |
| 164 | + rosbag2_storage::TopicMetadata tm; |
| 165 | + tm.name = name; |
| 166 | + tm.type = type; |
| 167 | + tm.serialization_format = "cdr"; |
| 168 | + writer.create_topic(tm); |
| 169 | + |
| 170 | + auto bag_message = std::make_shared<rosbag2_storage::SerializedBagMessage>(); |
| 171 | + auto ret = rcutils_system_time_now(&bag_message->time_stamp); |
| 172 | + if (ret != RCL_RET_OK) { |
| 173 | + RCLCPP_ERROR(rclcpp::get_logger("saveToBag"), "couldn't assign time rosbag message"); |
| 174 | + } |
| 175 | + |
| 176 | + bag_message->topic_name = tm.name; |
| 177 | + bag_message->serialized_data = std::shared_ptr<rcutils_uint8_array_t>( |
| 178 | + &serialized_msg.get_rcl_serialized_message(), [](rcutils_uint8_array_t * /* data */) {}); |
| 179 | + writer.write(bag_message); |
| 180 | +} |
| 181 | + |
| 182 | +fpa::PlannerCommonParam get_default_planner_params() |
| 183 | +{ |
| 184 | + // set problem configuration |
| 185 | + const double time_limit = 10 * 1000.0; |
| 186 | + const double minimum_turning_radius = 9.0; |
| 187 | + const double maximum_turning_radius = 9.0; |
| 188 | + const int turning_radius_size = 1; |
| 189 | + |
| 190 | + // Transitions every 2.5 deg |
| 191 | + const int theta_size = 144; |
| 192 | + |
| 193 | + const double curve_weight = 1.5; |
| 194 | + const double reverse_weight = 2.0; |
| 195 | + |
| 196 | + const double lateral_goal_range = 0.5; |
| 197 | + const double longitudinal_goal_range = 2.0; |
| 198 | + const double angle_goal_range = 6.0; |
| 199 | + const int obstacle_threshold = 100; |
| 200 | + |
| 201 | + return fpa::PlannerCommonParam{ |
| 202 | + time_limit, |
| 203 | + minimum_turning_radius, |
| 204 | + maximum_turning_radius, |
| 205 | + turning_radius_size, |
| 206 | + theta_size, |
| 207 | + curve_weight, |
| 208 | + reverse_weight, |
| 209 | + lateral_goal_range, |
| 210 | + longitudinal_goal_range, |
| 211 | + angle_goal_range, |
| 212 | + obstacle_threshold}; |
| 213 | +} |
| 214 | + |
| 215 | +std::unique_ptr<fpa::AbstractPlanningAlgorithm> configure_astar(bool use_multi, bool cw) |
| 216 | +{ |
| 217 | + auto planner_common_param = get_default_planner_params(); |
| 218 | + |
| 219 | + // configure astar param |
| 220 | + const bool only_behind_solutions = false; |
| 221 | + const bool use_back = true; |
| 222 | + const bool use_curve_weight = false; |
| 223 | + const bool use_complete_astar = false; |
| 224 | + const double distance_heuristic_weight = 1.0; |
| 225 | + auto astar_param = |
| 226 | + fpa::AstarParam{only_behind_solutions, use_back, use_curve_weight, use_complete_astar, distance_heuristic_weight}; |
| 227 | + |
| 228 | + if (use_multi) { |
| 229 | + planner_common_param.minimum_turning_radius = 5.0; |
| 230 | + planner_common_param.maximum_turning_radius = 9.0; |
| 231 | + planner_common_param.turning_radius_size = 3; |
| 232 | + // if multi-curvature and using curve weight |
| 233 | + if (cw){ |
| 234 | + astar_param.use_curve_weight = true; |
| 235 | + } |
| 236 | + astar_param.use_complete_astar = true; |
| 237 | + } |
| 238 | + |
| 239 | + auto algo = std::make_unique<fpa::AstarSearch>(planner_common_param, vehicle_shape, astar_param); |
| 240 | + return algo; |
| 241 | +} |
| 242 | + |
| 243 | +enum AlgorithmType { |
| 244 | + ASTAR_SINGLE, |
| 245 | + ASTAR_MULTI, |
| 246 | + ASTAR_MULTI_NOCW, |
| 247 | +}; |
| 248 | +// cspell: ignore fpalgos |
| 249 | +std::unordered_map<AlgorithmType, std::string> rosbag_dir_prefix_table( |
| 250 | + {{ASTAR_SINGLE, "fpalgos-astar_single"}, |
| 251 | + {ASTAR_MULTI, "fpalgos-astar_multi"}, |
| 252 | + {ASTAR_MULTI_NOCW, "fpalgos-astar_multi_nocw"}, |
| 253 | + }); |
| 254 | + |
| 255 | +bool test_algorithm(enum AlgorithmType algo_type, bool dump_rosbag = false) |
| 256 | +{ |
| 257 | + std::unique_ptr<fpa::AbstractPlanningAlgorithm> algo; |
| 258 | + if (algo_type == AlgorithmType::ASTAR_SINGLE) { |
| 259 | + algo = configure_astar(false, false); |
| 260 | + } else if (algo_type == AlgorithmType::ASTAR_MULTI) { |
| 261 | + algo = configure_astar(true, true); |
| 262 | + } else if (algo_type == AlgorithmType::ASTAR_MULTI_NOCW) { |
| 263 | + algo = configure_astar(true, false); |
| 264 | + } else { |
| 265 | + throw std::runtime_error("invalid algorithm time"); |
| 266 | + } |
| 267 | + |
| 268 | + // All algorithms have the same interface. |
| 269 | + const auto costmap_msg = construct_cost_map(200, 120, 0.2, 10); |
| 270 | + bool success_all = true; // if any local test below fails, overwrite this function |
| 271 | + |
| 272 | + rclcpp::Clock clock{RCL_SYSTEM_TIME}; |
| 273 | + for (size_t i = 0; i < start_poses.size(); ++i) { |
| 274 | + const auto start_pose = start_poses.at(i); |
| 275 | + |
| 276 | + algo->setMap(costmap_msg); |
| 277 | + double msec; |
| 278 | + double cost; |
| 279 | + |
| 280 | + { |
| 281 | + const rclcpp::Time begin = clock.now(); |
| 282 | + if (!algo->makePlan(create_pose_msg(start_pose), create_pose_msg(goal_pose))) { |
| 283 | + success_all = false; |
| 284 | + std::cout << "plan fail" << std::endl; |
| 285 | + continue; |
| 286 | + } |
| 287 | + const rclcpp::Time now = clock.now(); |
| 288 | + msec = (now - begin).seconds() * 1000.0; |
| 289 | + cost = algo->getWaypoints().compute_length(); |
| 290 | + } |
| 291 | + |
| 292 | + std::cout << "plan success : " << msec << "[msec]" |
| 293 | + << ", solution cost : " << cost << std::endl; |
| 294 | + const auto result = algo->getWaypoints(); |
| 295 | + geometry_msgs::msg::PoseArray trajectory; |
| 296 | + for (const auto & pose : result.waypoints) { |
| 297 | + trajectory.poses.push_back(pose.pose.pose); |
| 298 | + } |
| 299 | + if (algo->hasObstacleOnTrajectory(trajectory)) { |
| 300 | + std::cout << "not feasible trajectory" << std::endl; |
| 301 | + success_all = false; |
| 302 | + } |
| 303 | + |
| 304 | + if (dump_rosbag) { |
| 305 | + // dump rosbag for visualization using python script |
| 306 | + const std::string dir_name = |
| 307 | + "/tmp/" + rosbag_dir_prefix_table[algo_type] + "-case" + std::to_string(i); |
| 308 | + |
| 309 | + rcpputils::fs::remove_all(dir_name); |
| 310 | + |
| 311 | + rosbag2_storage::StorageOptions storage_options; |
| 312 | + storage_options.uri = dir_name; |
| 313 | + storage_options.storage_id = "sqlite3"; |
| 314 | + |
| 315 | + rosbag2_cpp::ConverterOptions converter_options; |
| 316 | + converter_options.input_serialization_format = "cdr"; |
| 317 | + converter_options.output_serialization_format = "cdr"; |
| 318 | + |
| 319 | + rosbag2_cpp::Writer writer(std::make_unique<rosbag2_cpp::writers::SequentialWriter>()); |
| 320 | + writer.open(storage_options, converter_options); |
| 321 | + |
| 322 | + add_message_to_rosbag( |
| 323 | + writer, create_float_msg(vehicle_shape.length), "vehicle_length", "std_msgs/msg/Float64"); |
| 324 | + add_message_to_rosbag( |
| 325 | + writer, create_float_msg(vehicle_shape.width), "vehicle_width", "std_msgs/msg/Float64"); |
| 326 | + add_message_to_rosbag( |
| 327 | + writer, create_float_msg(vehicle_shape.base2back), "vehicle_base2back", |
| 328 | + "std_msgs/msg/Float64"); |
| 329 | + |
| 330 | + add_message_to_rosbag(writer, costmap_msg, "costmap", "nav_msgs/msg/OccupancyGrid"); |
| 331 | + add_message_to_rosbag(writer, create_pose_msg(start_pose), "start", "geometry_msgs/msg/Pose"); |
| 332 | + add_message_to_rosbag(writer, create_pose_msg(goal_pose), "goal", "geometry_msgs/msg/Pose"); |
| 333 | + add_message_to_rosbag(writer, trajectory, "trajectory", "geometry_msgs/msg/PoseArray"); |
| 334 | + add_message_to_rosbag(writer, create_float_msg(msec), "elapsed_time", "std_msgs/msg/Float64"); |
| 335 | + } |
| 336 | + } |
| 337 | + return success_all; |
| 338 | +} |
| 339 | + |
| 340 | +TEST(AstarSearchTestSuite, SingleCurvature) |
| 341 | +{ |
| 342 | + EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_SINGLE)); |
| 343 | +} |
| 344 | + |
| 345 | +TEST(AstarSearchTestSuite, MultiCurvature) |
| 346 | +{ |
| 347 | + EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_MULTI)); |
| 348 | +} |
| 349 | + |
| 350 | +TEST(AstarSearchTestSuite, MultiCurvaturenocw) |
| 351 | +{ |
| 352 | + EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_MULTI_NOCW)); |
| 353 | +} |
| 354 | + |
| 355 | +int main(int argc, char ** argv) |
| 356 | +{ |
| 357 | + testing::InitGoogleTest(&argc, argv); |
| 358 | + return RUN_ALL_TESTS(); |
| 359 | +} |
0 commit comments