Skip to content

Commit 11f4623

Browse files
committed
feat(trajectory): add populate function
Signed-off-by: Mamoru Sobue <mamoru.sobue@tier4.jp>
1 parent ac27d62 commit 11f4623

File tree

3 files changed

+951
-0
lines changed

3 files changed

+951
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright 2025 TIER IV, Inc.
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+
#ifndef AUTOWARE__TRAJECTORY__UTILS__PRETTY_TRAJECTORY_HPP_
16+
#define AUTOWARE__TRAJECTORY__UTILS__PRETTY_TRAJECTORY_HPP_
17+
18+
#include "autoware/trajectory/forward.hpp"
19+
#include "autoware/trajectory/interpolator/akima_spline.hpp"
20+
#include "autoware/trajectory/interpolator/cubic_spline.hpp"
21+
#include "autoware/trajectory/interpolator/linear.hpp"
22+
#include "autoware/trajectory/interpolator/spherical_linear.hpp"
23+
#include "autoware/trajectory/path_point_with_lane_id.hpp"
24+
#include "autoware/trajectory/trajectory_point.hpp"
25+
#include "autoware_utils_geometry/geometry.hpp"
26+
27+
#include <range/v3/to_container.hpp>
28+
#include <range/v3/view/drop.hpp>
29+
#include <range/v3/view/transform.hpp>
30+
#include <range/v3/view/zip_with.hpp>
31+
#include <tl_expected/expected.hpp>
32+
33+
#include <autoware_internal_planning_msgs/msg/path_with_lane_id.hpp>
34+
#include <autoware_planning_msgs/msg/path.hpp>
35+
#include <autoware_planning_msgs/msg/trajectory.hpp>
36+
37+
#include <optional>
38+
#include <set>
39+
#include <string>
40+
#include <vector>
41+
42+
namespace autoware::trajectory
43+
{
44+
45+
namespace detail
46+
{
47+
template <typename PointType>
48+
tl::expected<std::vector<PointType>, std::string> populate4(const std::vector<PointType> & inputs);
49+
50+
template <typename PointType>
51+
tl::expected<std::vector<PointType>, std::string> populate5(const std::vector<PointType> & inputs);
52+
} // namespace detail
53+
54+
/**
55+
* @brief if the input path point size is less than 4, linearly interpolate the given points to 4,
56+
* and then apply either cubic or akima spline. if use_akima = true, the underlying points are
57+
* increased to at least 5
58+
* @param[in] path path object
59+
* @param[in] use_akima if true, use akima spline instead
60+
* @return return nullopt if the input point size is 0 or 1, otherwise return Trajectory class
61+
*/
62+
template <typename PointType>
63+
std::optional<Trajectory<PointType>> pretty_trajectory(
64+
const std::vector<PointType> & points, const bool use_akima = false)
65+
{
66+
if (use_akima) {
67+
const auto try_input5 = detail::populate5(points);
68+
if (!try_input5) {
69+
return std::nullopt;
70+
}
71+
const auto & points_get5 = try_input5.value();
72+
73+
using Builder = typename Trajectory<PointType>::Builder;
74+
const auto try_trajectory =
75+
Builder{}.template set_xy_interpolator<interpolator::AkimaSpline>().build(points_get5);
76+
if (!try_trajectory) {
77+
return std::nullopt;
78+
}
79+
return try_trajectory.value();
80+
}
81+
const auto try_input4 = detail::populate4(points);
82+
if (!try_input4) {
83+
return std::nullopt;
84+
}
85+
const auto & points_get4 = try_input4.value();
86+
87+
using Builder = typename Trajectory<PointType>::Builder;
88+
const auto try_trajectory = Builder{}.build(points_get4);
89+
if (!try_trajectory) {
90+
return std::nullopt;
91+
}
92+
return try_trajectory.value();
93+
}
94+
95+
namespace detail
96+
{
97+
98+
template <typename T>
99+
const geometry_msgs::msg::Pose & get_geometry_msgs_pose([[maybe_unused]] const T & point)
100+
{
101+
static_assert("No specialization given");
102+
throw;
103+
}
104+
105+
template <>
106+
inline const geometry_msgs::msg::Pose & get_geometry_msgs_pose(
107+
const autoware_internal_planning_msgs::msg::PathPointWithLaneId & point)
108+
{
109+
return point.point.pose;
110+
}
111+
112+
template <>
113+
inline const geometry_msgs::msg::Pose & get_geometry_msgs_pose(
114+
const autoware_planning_msgs::msg::PathPoint & point)
115+
{
116+
return point.pose;
117+
}
118+
119+
template <>
120+
inline const geometry_msgs::msg::Pose & get_geometry_msgs_pose(
121+
const autoware_planning_msgs::msg::TrajectoryPoint & point)
122+
{
123+
return point.pose;
124+
}
125+
126+
[[maybe_unused]] inline std::vector<double> insert_middle_into_largest_interval(
127+
const std::vector<double> & bases)
128+
{
129+
const auto base_diffs =
130+
ranges::views::zip_with(
131+
[](const double a, const double b) { return b - a; }, bases, bases | ranges::views::drop(1)) |
132+
ranges::to<std::vector>();
133+
assert(base_diffs.size() == 3);
134+
const auto max_interval_start =
135+
std::distance(base_diffs.begin(), std::max_element(base_diffs.begin(), base_diffs.end()));
136+
const auto new_base = (bases.at(max_interval_start) + bases.at(max_interval_start + 1)) / 2.0;
137+
std::set<double> sorted_new_bases{bases.begin(), bases.end()};
138+
sorted_new_bases.insert(new_base);
139+
return {sorted_new_bases.begin(), sorted_new_bases.end()};
140+
}
141+
142+
/**
143+
* @brief if the input point size is less than 3, add 3rd point, otherwise return as is
144+
* @param[in] inputs vector of point whose size is at least 2
145+
* @return the vector of points whose size is at least 3, or error reason
146+
* @note {x, y, z} are interpolated by Linear. other properties as
147+
* interpolated by default
148+
*/
149+
tl::expected<std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId>, std::string>
150+
populate3(const std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId> & inputs);
151+
152+
tl::expected<std::vector<autoware_planning_msgs::msg::PathPoint>, std::string> populate3(
153+
const std::vector<autoware_planning_msgs::msg::PathPoint> & inputs);
154+
155+
tl::expected<std::vector<autoware_planning_msgs::msg::TrajectoryPoint>, std::string> populate3(
156+
const std::vector<autoware_planning_msgs::msg::TrajectoryPoint> & inputs);
157+
158+
/**
159+
* @brief if the input point size is less than 4, add 4th point, otherwise return as is
160+
* @param[in] inputs inputs
161+
* @return optional the vector of points whose size is at least 4, so that it can be interpolated by
162+
* CubicSpline, or error reason
163+
* @note {x, y, z} are interpolated by Linear. other properties as
164+
* interpolated by default
165+
*/
166+
template <typename PointType>
167+
tl::expected<std::vector<PointType>, std::string> populate4(const std::vector<PointType> & inputs)
168+
{
169+
if (inputs.size() >= 4) {
170+
return inputs;
171+
}
172+
if (inputs.size() < 2) {
173+
return tl::unexpected(std::string("cannot populate4() from less than 1 points!"));
174+
}
175+
176+
const auto try_inputs3 = populate3(inputs);
177+
if (!try_inputs3) {
178+
return tl::unexpected(try_inputs3.error());
179+
}
180+
const auto & inputs3 = inputs.size() == 2 ? try_inputs3.value() : inputs;
181+
182+
using Builder = typename Trajectory<PointType>::Builder;
183+
184+
const auto input3_interpolation_result = Builder{}
185+
.template set_xy_interpolator<interpolator::Linear>()
186+
.template set_z_interpolator<interpolator::Linear>()
187+
.build(inputs3);
188+
189+
// LCOV_EXCL_START
190+
if (!input3_interpolation_result) {
191+
// actually this block is impossible because 3 points are given, which is sufficient for Linear
192+
// interpolation
193+
return tl::unexpected(std::string("failed Linear interpolation in populate4()!"));
194+
}
195+
// LCOV_EXCL_END
196+
197+
const auto & interpolation = input3_interpolation_result.value();
198+
199+
const auto new_bases = insert_middle_into_largest_interval(interpolation.get_internal_bases());
200+
return new_bases |
201+
ranges::views::transform([&](const double s) { return interpolation.compute(s); }) |
202+
ranges::to<std::vector>();
203+
}
204+
205+
extern template tl::expected<
206+
std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId>, std::string>
207+
populate4(const std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId> & inputs);
208+
209+
extern template tl::expected<std::vector<autoware_planning_msgs::msg::PathPoint>, std::string>
210+
populate4(const std::vector<autoware_planning_msgs::msg::PathPoint> & inputs);
211+
212+
extern template tl::expected<std::vector<autoware_planning_msgs::msg::TrajectoryPoint>, std::string>
213+
populate4(const std::vector<autoware_planning_msgs::msg::TrajectoryPoint> & inputs);
214+
215+
/**
216+
* @brief if the input point size is less than 5, add 5th point, otherwise return as is
217+
* @param[in] inputs inputs
218+
* @return the vector of points whose size is at least 5, so that it can be interpolated by
219+
* AkimaSpline, or error reason
220+
* @note all fields are internally interpolated by default
221+
*/
222+
template <typename PointType>
223+
tl::expected<std::vector<PointType>, std::string> populate5(const std::vector<PointType> & inputs)
224+
{
225+
if (inputs.size() >= 5) {
226+
return inputs;
227+
}
228+
if (inputs.size() < 2) {
229+
return tl::unexpected(std::string("cannot populate5() from less than 1 points!"));
230+
}
231+
232+
const auto try_inputs4 = populate4(inputs);
233+
if (!try_inputs4) {
234+
return tl::unexpected(try_inputs4.error());
235+
}
236+
const auto & inputs4 = inputs.size() == 4 ? inputs : try_inputs4.value();
237+
238+
using Builder = typename Trajectory<PointType>::Builder;
239+
const auto input4_interpolation_result = Builder{}.build(inputs4);
240+
241+
// LCOV_EXCL_START
242+
if (!input4_interpolation_result) {
243+
// actually this block is impossible because 3 points are given, which is sufficient for Linear
244+
// interpolation
245+
return tl::unexpected(std::string("failed Linear interpolation in populate4()!"));
246+
}
247+
// LCOV_EXCL_END
248+
249+
const auto & interpolation = input4_interpolation_result.value();
250+
251+
const auto new_bases = insert_middle_into_largest_interval(interpolation.get_internal_bases());
252+
// assert(bases.size() == 4);
253+
return new_bases |
254+
ranges::views::transform([&](const double s) { return interpolation.compute(s); }) |
255+
ranges::to<std::vector>();
256+
}
257+
258+
extern template tl::expected<
259+
std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId>, std::string>
260+
populate5(const std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId> & inputs);
261+
262+
extern template tl::expected<std::vector<autoware_planning_msgs::msg::PathPoint>, std::string>
263+
populate5(const std::vector<autoware_planning_msgs::msg::PathPoint> & inputs);
264+
265+
extern template tl::expected<std::vector<autoware_planning_msgs::msg::TrajectoryPoint>, std::string>
266+
populate5(const std::vector<autoware_planning_msgs::msg::TrajectoryPoint> & inputs);
267+
268+
} // namespace detail
269+
270+
extern template std::optional<Trajectory<autoware_internal_planning_msgs::msg::PathPointWithLaneId>>
271+
pretty_trajectory(
272+
const std::vector<autoware_internal_planning_msgs::msg::PathPointWithLaneId> & points,
273+
const bool use_akima = false);
274+
275+
extern template std::optional<Trajectory<autoware_planning_msgs::msg::PathPoint>> pretty_trajectory(
276+
const std::vector<autoware_planning_msgs::msg::PathPoint> & points, const bool use_akima = false);
277+
278+
extern template std::optional<Trajectory<autoware_planning_msgs::msg::TrajectoryPoint>>
279+
pretty_trajectory(
280+
const std::vector<autoware_planning_msgs::msg::TrajectoryPoint> & points,
281+
const bool use_akima = false);
282+
283+
} // namespace autoware::trajectory
284+
#endif // AUTOWARE__TRAJECTORY__UTILS__PRETTY_TRAJECTORY_HPP_

0 commit comments

Comments
 (0)