lanelet2_route_planning v2.0.0
Loading...
Searching...
No Matches
geometry.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#include <algorithm>
5#include <cmath>
6#include <limits>
7#include <numeric>
8#include <utility>
9
10#include <lanelet2_core/geometry/LineString.h>
11#include <rclcpp/rclcpp.hpp>
12
15
17namespace {
18
19double pointToSegmentDistance(const Eigen::Vector2d& point,
20 const Eigen::Vector2d& segment_start,
21 const Eigen::Vector2d& segment_end) {
22 const Eigen::Vector2d segment = segment_end - segment_start;
23 const double squared_length = segment.squaredNorm();
24 if (squared_length == 0.0) {
25 return (point - segment_start).norm();
26 }
27 const double interpolation = std::clamp((point - segment_start).dot(segment) / squared_length, 0.0, 1.0);
28 return (point - (segment_start + interpolation * segment)).norm();
29}
30
31void adaptivelySampleLineStringRange(const std::vector<Eigen::Vector2d>& line_string,
32 const size_t first,
33 const size_t last,
34 const double max_lateral_error,
35 std::vector<bool>& keep) {
36 // retain the endpoints of each continuous range
37 keep[first] = true;
38 keep[last] = true;
39 if (last <= first + 1) {
40 return;
41 }
42
43 // iteratively split ranges at their largest lateral deviation
44 std::vector<std::pair<size_t, size_t>> ranges = {{first, last}};
45 while (!ranges.empty()) {
46 const auto [range_first, range_last] = ranges.back();
47 ranges.pop_back();
48
49 double largest_distance = -1.0;
50 size_t largest_distance_idx = range_first;
51 for (size_t i = range_first + 1; i < range_last; ++i) {
52 const double distance = pointToSegmentDistance(line_string[i], line_string[range_first], line_string[range_last]);
53 if (distance > largest_distance) {
54 largest_distance = distance;
55 largest_distance_idx = i;
56 }
57 }
58
59 if (largest_distance > max_lateral_error) {
60 keep[largest_distance_idx] = true;
61 if (largest_distance_idx > range_first + 1) {
62 ranges.emplace_back(range_first, largest_distance_idx);
63 }
64 if (range_last > largest_distance_idx + 1) {
65 ranges.emplace_back(largest_distance_idx, range_last);
66 }
67 }
68 }
69}
70
71} // namespace
72
73double wrapAngle(const double angle) {
74 double wrapped_angle = angle;
75 while (wrapped_angle > M_PI) wrapped_angle -= 2 * M_PI;
76 while (wrapped_angle < -M_PI) wrapped_angle += 2 * M_PI;
77 return wrapped_angle;
78}
79
80double angleBetweenVectors(const Eigen::Vector2d& v1, const Eigen::Vector2d& v2) {
81 double dot_product = v1.dot(v2);
82 double determinant = v1.x() * v2.y() - v1.y() * v2.x();
83 double angle = std::atan2(determinant, dot_product);
84 return wrapAngle(angle);
85}
86
87std::optional<IntersectionOfLinesResult> intersectionOfLines(const std::vector<Eigen::Vector2d>& line1,
88 const std::vector<Eigen::Vector2d>& line2) {
90
91 // check if lines are valid
92 if (line1.size() < 2 || line2.size() < 2) {
93 return std::nullopt;
94 }
95
96 // define straight lines (a1 x + b1 y = c1) and (a2 x + b2 y = c2) through line points
97 const double a1 = line1[1].y() - line1[0].y();
98 const double b1 = line1[0].x() - line1[1].x();
99 const double c1 = line1[0].x() * line1[1].y() - line1[1].x() * line1[0].y();
100 const double a2 = line2[1].y() - line2[0].y();
101 const double b2 = line2[0].x() - line2[1].x();
102 const double c2 = line2[0].x() * line2[1].y() - line2[1].x() * line2[0].y();
103
104 // find intersection of both lines by solving for x and y
105 const double det = a1 * b2 - a2 * b1;
106 if (det != 0) {
107 const double x = (b2 * c1 - b1 * c2) / det;
108 const double y = (a1 * c2 - a2 * c1) / det;
109 result.intersection = Eigen::Vector2d(x, y);
110
111 // check if intersection point is within line segments
112 result.intersects_line1 = (x >= std::min(line1[0].x(), line1[1].x()) && x <= std::max(line1[0].x(), line1[1].x()) &&
113 y >= std::min(line1[0].y(), line1[1].y()) && y <= std::max(line1[0].y(), line1[1].y()));
114 result.intersects_line2 = (x >= std::min(line2[0].x(), line2[1].x()) && x <= std::max(line2[0].x(), line2[1].x()) &&
115 y >= std::min(line2[0].y(), line2[1].y()) && y <= std::max(line2[0].y(), line2[1].y()));
116 } else {
117 return std::nullopt;
118 }
119
120 return result;
121}
122
123Eigen::Vector2d tangentOfPointAlongLineString(const Eigen::Vector2d& point,
124 const Eigen::Vector2d& prev_point,
125 const Eigen::Vector2d& next_point) {
126 Eigen::Vector2d tangent;
127
128 if (point == prev_point && point != next_point) { // single line segment
129 tangent = (next_point - point).normalized();
130 } else if (point != prev_point && point == next_point) { // single line segment
131 tangent = (point - prev_point).normalized();
132 } else if (point == prev_point && point == next_point) { // single point
133 tangent = Eigen::Vector2d(0.0, 0.0);
134 } else { // proper two line segments with previous and next point
135 const Eigen::Vector2d prev_to_point_unit = (point - prev_point).normalized();
136 const Eigen::Vector2d point_to_next_unit = (next_point - point).normalized();
137 tangent = (prev_to_point_unit + point_to_next_unit).normalized();
138 }
139
140 return tangent;
141}
142
143Eigen::Vector2d normalOfPointAlongLineString(const Eigen::Vector2d& point,
144 const Eigen::Vector2d& prev_point,
145 const Eigen::Vector2d& next_point) {
146 Eigen::Vector2d tangent = tangentOfPointAlongLineString(point, prev_point, next_point);
147 Eigen::Vector2d normal = Eigen::Vector2d(tangent.y(), -tangent.x());
148
149 return normal;
150}
151
152std::vector<Eigen::Vector3d> resampleLineString(const std::vector<Eigen::Vector3d>& line_string,
153 const double delta,
154 double& offset) {
155 std::vector<Eigen::Vector3d> resampled_line_string;
156
157 // set initial sampling distance
158 double sampling_distance = delta;
159 if (offset == 0.0) { // if no offset, start with first line point
160 resampled_line_string.push_back(line_string.front());
161 } else { // else sample first point with offset != delta
162 sampling_distance = offset;
163 }
164
165 // loop over all line segments
166 for (size_t i = 1; i < line_string.size(); ++i) {
167 // determine 2D segment length and 3D direction
168 double segment_length = (to2d(line_string[i]) - to2d(line_string[i - 1])).norm();
169 const Eigen::Vector3d segment_direction = (line_string[i] - line_string[i - 1]) / segment_length;
170
171 // sample points along segment, increasing sampling_distance by delta
172 while (segment_length >= sampling_distance) {
173 const Eigen::Vector3d resampled_point = line_string[i - 1] + sampling_distance * segment_direction;
174 resampled_line_string.push_back(resampled_point);
175 sampling_distance += delta;
176 }
177
178 // reset sampling_distance for next segment, including overshoot
179 sampling_distance = sampling_distance - segment_length;
180 }
181
182 // save overshoot in outgoing offset parameter
183 offset = sampling_distance;
184
185 return resampled_line_string;
186}
187
188std::vector<size_t> adaptivelySampleLineString(const std::vector<Eigen::Vector2d>& line_string,
189 const double max_lateral_error,
190 const std::vector<size_t>& break_after_indices) {
191 std::vector<size_t> retained_indices;
192 if (line_string.empty()) {
193 return retained_indices;
194 }
195 if (max_lateral_error <= 0.0) {
196 retained_indices.resize(line_string.size());
197 std::iota(retained_indices.begin(), retained_indices.end(), 0);
198 return retained_indices;
199 }
200
201 // retain discontinuities, such as lane changes, as separate ranges
202 std::vector<size_t> valid_breaks;
203 for (const size_t index : break_after_indices) {
204 if (index < line_string.size() - 1) {
205 valid_breaks.push_back(index);
206 }
207 }
208 std::sort(valid_breaks.begin(), valid_breaks.end());
209 valid_breaks.erase(std::unique(valid_breaks.begin(), valid_breaks.end()), valid_breaks.end());
210
211 // sample each continuous range independently
212 std::vector<bool> keep(line_string.size(), false);
213 size_t segment_start = 0;
214 for (const size_t break_after : valid_breaks) {
215 if (break_after < segment_start) {
216 continue;
217 }
218 adaptivelySampleLineStringRange(line_string, segment_start, break_after, max_lateral_error, keep);
219 segment_start = break_after + 1;
220 }
221 adaptivelySampleLineStringRange(line_string, segment_start, line_string.size() - 1, max_lateral_error, keep);
222
223 for (size_t i = 0; i < keep.size(); ++i) {
224 if (keep[i]) {
225 retained_indices.push_back(i);
226 }
227 }
228 return retained_indices;
229}
230
231Eigen::Vector2d projectPointToLineString(const Eigen::Vector2d& point, const std::vector<Eigen::Vector2d>& line_string) {
232 return lanelet::geometry::project(toLanelet(line_string), toLanelet(point));
233}
234
235Eigen::Vector3d projectPointToLineString(const Eigen::Vector3d& point, const std::vector<Eigen::Vector3d>& line_string) {
236 return lanelet::geometry::project(toLanelet(line_string), toLanelet(point));
237}
238
239std::optional<ProjectPointToLineStringAlongAxisResult> projectPointToLineStringAlongAxis(
240 const Eigen::Vector2d& point, const Eigen::Vector2d& axis, const std::vector<Eigen::Vector2d>& line_string) {
243 double closest_distance_to_line_segment = std::numeric_limits<double>::max();
244 bool found_at_least_one_intersection = false;
245
246 // define straight line along axis at point
247 std::vector<Eigen::Vector2d> axis_line = {point, point + axis};
248
249 // loop over line segments
250 for (size_t i = 0; i < line_string.size() - 1; ++i) {
251 std::vector<Eigen::Vector2d> line_segment = {line_string[i], line_string[i + 1]};
252
253 // find intersection of axis line and line segment
254 if (auto inner_result = intersectionOfLines(axis_line, line_segment)) {
255 const Eigen::Vector2d& intersection = inner_result->intersection;
256 const bool intersects_line_segment = inner_result->intersects_line2;
257 found_at_least_one_intersection = true;
258 if (intersects_line_segment) {
260 result.projected_point = intersection;
261 break;
262 }
263 double distance_to_line_segment =
264 std::min((intersection - line_string[i]).norm(), (intersection - line_string[i + 1]).norm());
265 if (distance_to_line_segment < closest_distance_to_line_segment) {
266 closest_distance_to_line_segment = distance_to_line_segment;
267 result.projected_point = intersection;
268 }
269 }
270 }
271
272 if (!found_at_least_one_intersection) {
273 return std::nullopt;
274 }
275
276 return result;
277}
278
279std::optional<ProjectPointToLineStringAlongAxisResult> projectPointToLineStringAlongNormal(
280 const Eigen::Vector2d& point,
281 const Eigen::Vector2d& prev_point,
282 const Eigen::Vector2d& next_point,
283 const std::vector<Eigen::Vector2d>& line_string) {
284 // find normal to tangent at point
285 Eigen::Vector2d normal = normalOfPointAlongLineString(point, prev_point, next_point);
286 if (normal == Eigen::Vector2d(0.0, 0.0)) {
287 return std::nullopt;
288 }
289
290 // project current point to other line along normal to tangent
291 if (auto result = projectPointToLineStringAlongAxis(point, normal, line_string)) {
292 return result;
293 }
294 return std::nullopt;
295}
296
297} // namespace lanelet2_route_planning
std::optional< ProjectPointToLineStringAlongAxisResult > projectPointToLineStringAlongAxis(const Eigen::Vector2d &point, const Eigen::Vector2d &axis, const std::vector< Eigen::Vector2d > &line_string)
Projects a point along an axis to the closest line segment of a line string.
Definition geometry.cpp:239
std::optional< ProjectPointToLineStringAlongAxisResult > projectPointToLineStringAlongNormal(const Eigen::Vector2d &point, const Eigen::Vector2d &prev_point, const Eigen::Vector2d &next_point, const std::vector< Eigen::Vector2d > &line_string)
Projects a point to the closest line segment of a line string along the normal to the tangent at the ...
Definition geometry.cpp:279
double angleBetweenVectors(const Eigen::Vector2d &v1, const Eigen::Vector2d &v2)
Computes the angle between two 2D vectors.
Definition geometry.cpp:80
Eigen::Vector2d tangentOfPointAlongLineString(const Eigen::Vector2d &point, const Eigen::Vector2d &prev_point, const Eigen::Vector2d &next_point)
Computes a unit vector tangential to a point along a line string.
Definition geometry.cpp:123
Eigen::Vector2d projectPointToLineString(const Eigen::Vector2d &point, const std::vector< Eigen::Vector2d > &line_string)
Projects a point to the closest line segment of a line string.
Definition geometry.cpp:231
std::vector< size_t > adaptivelySampleLineString(const std::vector< Eigen::Vector2d > &line_string, double max_lateral_error, const std::vector< size_t > &break_after_indices={})
Simplifies a 2D line string within a maximum lateral error.
Definition geometry.cpp:188
lanelet::BasicPoint2d toLanelet(const Eigen::Vector2d &point)
Converts a 2D Eigen point to a Lanelet point.
double wrapAngle(const double angle)
Wraps an angle to the range [-π, π].
Definition geometry.cpp:73
Eigen::Vector2d normalOfPointAlongLineString(const Eigen::Vector2d &point, const Eigen::Vector2d &prev_point, const Eigen::Vector2d &next_point)
Computes a unit vector normal to a point along a line string.
Definition geometry.cpp:143
std::vector< Eigen::Vector3d > resampleLineString(const std::vector< Eigen::Vector3d > &line_string, const double delta, double &offset)
Resamples a 3D line string with a constant sampling distance.
Definition geometry.cpp:152
Eigen::Vector2d to2d(const Eigen::Vector3d &point)
Converts a 3D Eigen point to a 2D Eigen point.
std::optional< IntersectionOfLinesResult > intersectionOfLines(const std::vector< Eigen::Vector2d > &line1, const std::vector< Eigen::Vector2d > &line2)
Computes the intersection of two 2D lines.
Definition geometry.cpp:87
Return type of intersectionOfLines.
Definition geometry.hpp:35
bool intersects_line2
whether intersection is on second line segment
Definition geometry.hpp:38
bool intersects_line1
whether intersection is on first line segment
Definition geometry.hpp:37
Eigen::Vector2d intersection
intersection point
Definition geometry.hpp:36
Return type of projectPointToLineStringAlongAxis.
Definition geometry.hpp:133
bool found_intersection_with_line_segment
whether intersection is on line segment
Definition geometry.hpp:135