Skip to content

Commit 9004013

Browse files
Merge branch 'main' into Jay/update-codeowner-package-json-fix
2 parents f418813 + 403f008 commit 9004013

File tree

7 files changed

+169
-16
lines changed

7 files changed

+169
-16
lines changed

src/local_pathfinding/local_pathfinding/local_path.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class LocalPathState:
2222
ais_ships (List[HelperAISShip]): Information about nearby ships.
2323
global_path (List[Tuple[float, float]]): Path to the destination that Sailbot is
2424
navigating along.
25+
target_global_waypoint (ci.HelperLatLon): The global waypoint that we are heading towards.
26+
The global waypoint is the same as the reference latlon.
2527
wind_speed (float): Wind speed.
2628
wind_direction (int): Wind direction.
2729
planner (str): Planner to use for the OMPL query.
@@ -34,6 +36,7 @@ def __init__(
3436
gps: ci.GPS,
3537
ais_ships: ci.AISShips,
3638
global_path: ci.Path,
39+
target_global_waypoint: ci.HelperLatLon,
3740
filtered_wind_sensor: ci.WindSensor,
3841
planner: str,
3942
):
@@ -55,7 +58,7 @@ def __init__(
5558
if not (global_path and global_path.waypoints):
5659
raise ValueError("Cannot create a LocalPathState with an empty global_path")
5760
self.global_path = global_path
58-
self.reference_latlon = self.global_path.waypoints[-1]
61+
self.reference_latlon = target_global_waypoint
5962

6063
if not planner:
6164
raise ValueError("planner must not be None")
@@ -87,6 +90,9 @@ def update_if_needed(
8790
gps: ci.GPS,
8891
ais_ships: ci.AISShips,
8992
global_path: ci.Path,
93+
received_new_global_waypoint: bool,
94+
# ^ Placeholder; will be used for conditions to update local path
95+
target_global_waypoint: ci.HelperLatLon,
9096
filtered_wind_sensor: ci.WindSensor,
9197
planner: str,
9298
land_multi_polygon: MultiPolygon = None,
@@ -101,7 +107,9 @@ def update_if_needed(
101107
filtered_wind_sensor (ci.WindSensor): Wind data.
102108
"""
103109
# this raises ValueError if any of the parameters are not properly initialized
104-
state = LocalPathState(gps, ais_ships, global_path, filtered_wind_sensor, planner)
110+
state = LocalPathState(
111+
gps, ais_ships, global_path, target_global_waypoint, filtered_wind_sensor, planner
112+
)
105113
self.state = state
106114
ompl_path = OMPLPath(
107115
parent_logger=self._logger,

src/local_pathfinding/local_pathfinding/node_navigate.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import local_pathfinding.obstacles as ob
1515
from local_pathfinding.local_path import LocalPath
1616

17-
WAYPOINT_REACHED_THRESH_KM = 0.5
17+
LOCAL_WAYPOINT_REACHED_THRESH_KM = 0.5
18+
GLOBAL_WAYPOINT_REACHED_THRESH_KM = 3
19+
PATHFINDING_RANGE_KM = 30
20+
REALLY_FAR_M = 100000000
1821
GEODESIC = Geod(ellps="WGS84")
1922

2023

@@ -119,7 +122,9 @@ def __init__(self):
119122

120123
# attributes
121124
self.local_path = LocalPath(parent_logger=self.get_logger())
122-
self.current_waypoint_index = 0
125+
self.local_waypoint_index = 0
126+
self.global_waypoint_index = -1
127+
self.saved_target_global_waypoint = None
123128
self.use_mock_land = self.get_parameter("use_mock_land").get_parameter_value().bool_value
124129
self.mode = self.get_parameter("mode").get_parameter_value().string_value
125130
self.planner = self.get_parameter("path_planner").get_parameter_value().string_value
@@ -154,6 +159,8 @@ def global_path_callback(self, msg: ci.Path):
154159
f"Received data from {self.global_path_sub.topic}: {gp.path_to_dict(msg)}"
155160
)
156161
self.global_path = msg
162+
if self.saved_target_global_waypoint is None:
163+
self.saved_target_global_waypoint = self.global_path.waypoints[-1]
157164

158165
def filtered_wind_sensor_callback(self, msg: ci.WindSensor):
159166
self.get_logger().debug(f"Received data from {self.filtered_wind_sensor_sub.topic}: {msg}")
@@ -251,26 +258,89 @@ def get_desired_heading(self) -> float:
251258
float: The desired heading
252259
"""
253260

254-
received_new_path = self.local_path.update_if_needed(
261+
# Extra logic for when the global waypoint changes due to receiving a new global path
262+
received_new_global_waypoint = False
263+
if (
264+
self.global_path.waypoints[self.global_waypoint_index]
265+
!= self.saved_target_global_waypoint
266+
):
267+
received_new_global_waypoint = True
268+
self.global_waypoint_index = self.determine_start_point_in_new_global_path(
269+
self.global_path, self.gps.lat_lon
270+
)
271+
self.saved_target_global_waypoint = self.global_path.waypoints[
272+
self.global_waypoint_index
273+
]
274+
275+
# Check if we're close enough to the global waypoint to head to the next one
276+
_, _, distance_to_waypoint_m = GEODESIC.inv(
277+
self.gps.lat_lon.longitude,
278+
self.gps.lat_lon.latitude,
279+
self.saved_target_global_waypoint.longitude,
280+
self.saved_target_global_waypoint.latitude,
281+
)
282+
if distance_to_waypoint_m < GLOBAL_WAYPOINT_REACHED_THRESH_KM:
283+
received_new_global_waypoint = True
284+
self.global_waypoint_index -= 1 # Since global waypoints are in reverse order
285+
286+
# At the end of the global path, alternate between the last two waypoints
287+
if self.global_waypoint_index < 0:
288+
self.global_waypoint_index = 1
289+
290+
self.saved_target_global_waypoint = self.global_path.waypoints[
291+
self.global_waypoint_index
292+
]
293+
294+
received_new_local_path = self.local_path.update_if_needed(
255295
self.gps,
256296
self.ais_ships,
257297
self.global_path,
298+
received_new_global_waypoint,
299+
self.saved_target_global_waypoint,
258300
self.filtered_wind_sensor,
259301
self.planner,
260302
self.land_multi_polygon,
261303
)
262304

263-
if received_new_path:
264-
self.current_waypoint_index = 0
305+
if received_new_local_path:
306+
self.local_waypoint_index = 0
265307

266-
desired_heading, self.current_waypoint_index = (
308+
desired_heading, self.local_waypoint_index = (
267309
self.calculate_desired_heading_and_waypoint_index(
268-
self.local_path.path, self.current_waypoint_index, self.gps.lat_lon
310+
self.local_path.path, self.local_waypoint_index, self.gps.lat_lon
269311
)
270312
)
271313

272314
return desired_heading
273315

316+
@staticmethod
317+
def determine_start_point_in_new_global_path(
318+
global_path: ci.Path, boat_lat_lon: ci.HelperLatLon
319+
):
320+
"""Used when we receive a new global path.
321+
Finds the index of the first waypoint within 'pathfinding range' of gps location.
322+
If none are found, it returns the index of the nearest waypoint."""
323+
324+
closest_m, index_of_closest = REALLY_FAR_M, -1
325+
for waypoint_index in range(len(global_path.waypoints) - 1, -1, -1):
326+
# Note: the global waypoints are in reverse order (index 0 is final waypoint)
327+
waypoint = global_path.waypoints[waypoint_index]
328+
329+
_, _, distance_to_waypoint_m = GEODESIC.inv(
330+
boat_lat_lon.longitude,
331+
boat_lat_lon.latitude,
332+
waypoint.longitude,
333+
waypoint.latitude,
334+
)
335+
336+
if distance_to_waypoint_m < closest_m:
337+
closest_m, index_of_closest = distance_to_waypoint_m, waypoint_index
338+
339+
if distance_to_waypoint_m < cs.km_to_meters(PATHFINDING_RANGE_KM):
340+
return waypoint_index
341+
342+
return index_of_closest
343+
274344
@staticmethod
275345
def calculate_desired_heading_and_waypoint_index(
276346
path: ci.Path, waypoint_index: int, boat_lat_lon: ci.HelperLatLon
@@ -280,7 +350,7 @@ def calculate_desired_heading_and_waypoint_index(
280350
boat_lat_lon.longitude, boat_lat_lon.latitude, waypoint.longitude, waypoint.latitude
281351
)
282352

283-
if cs.meters_to_km(distance_to_waypoint_m) < WAYPOINT_REACHED_THRESH_KM:
353+
if cs.meters_to_km(distance_to_waypoint_m) < LOCAL_WAYPOINT_REACHED_THRESH_KM:
284354
# If we reached the current local waypoint, aim for the next one
285355
waypoint_index += 1
286356
waypoint = path.waypoints[waypoint_index]
@@ -339,3 +409,4 @@ def _log_inactive_subs_warning(self):
339409

340410
if __name__ == "__main__":
341411
main()
412+
main()

src/local_pathfinding/local_pathfinding/ompl_path.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ def _init_simple_setup(self, local_path_state, land_multi_polygon) -> og.SimpleS
225225
start_x = start_position_in_xy.x
226226
start_y = start_position_in_xy.y
227227

228-
goal_position = self.state.global_path.waypoints[-1]
229-
goal_position_in_xy = cs.latlon_to_xy(self.state.reference_latlon, goal_position)
228+
goal_position_in_xy = cs.XY(0, 0) # Global waypoint is used as the reference point
230229
goal_polygon = self.create_buffer_around_position(goal_position_in_xy)
231230
goal_x, goal_y = goal_position_in_xy
232231

src/local_pathfinding/test/test_local_path.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ def test_LocalPath_update_if_needed():
1818
HelperLatLon(latitude=1.0, longitude=1.0),
1919
]
2020
),
21+
received_new_global_waypoint=False,
22+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
2123
filtered_wind_sensor=WindSensor(),
2224
planner="rrtstar",
2325
)
@@ -37,6 +39,7 @@ def test_LocalPathState_parameter_checking():
3739
HelperLatLon(latitude=1.0, longitude=1.0),
3840
]
3941
),
42+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
4043
filtered_wind_sensor=WindSensor(),
4144
planner="rrtstar",
4245
),
@@ -53,6 +56,7 @@ def test_LocalPathState_parameter_checking():
5356
HelperLatLon(latitude=1.0, longitude=1.0),
5457
]
5558
),
59+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
5660
filtered_wind_sensor=WindSensor(),
5761
planner="rrtstar",
5862
),
@@ -64,6 +68,7 @@ def test_LocalPathState_parameter_checking():
6468
gps=GPS(),
6569
ais_ships=AISShips(),
6670
global_path=Path(waypoints=[]),
71+
target_global_waypoint=HelperLatLon(),
6772
filtered_wind_sensor=WindSensor(),
6873
planner="rrtstar",
6974
),
@@ -75,6 +80,7 @@ def test_LocalPathState_parameter_checking():
7580
gps=GPS(),
7681
ais_ships=AISShips(),
7782
global_path=None,
83+
target_global_waypoint=None,
7884
filtered_wind_sensor=WindSensor(),
7985
planner="rrtstar",
8086
),
@@ -91,6 +97,7 @@ def test_LocalPathState_parameter_checking():
9197
HelperLatLon(latitude=1.0, longitude=1.0),
9298
]
9399
),
100+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
94101
filtered_wind_sensor=None,
95102
planner="rrtstar",
96103
),
@@ -106,6 +113,7 @@ def test_LocalPathState_parameter_checking():
106113
HelperLatLon(latitude=1.0, longitude=1.0),
107114
]
108115
),
116+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
109117
filtered_wind_sensor=WindSensor(),
110118
planner=None,
111119
),

src/local_pathfinding/test/test_node_navigate.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
import local_pathfinding.node_navigate as nn
55

6+
LOCAL_WAYPOINT_REACHED_THRESH_KM = 0.5
7+
GLOBAL_WAYPOINT_REACHED_THRESH_KM = 3
8+
PATHFINDING_RANGE_KM = 30
9+
ONE_DEGREE_KM = 111 # One degree longitude at equator = 111km
10+
PATH_RANGE_DEG = PATHFINDING_RANGE_KM / ONE_DEGREE_KM
11+
612

713
@pytest.mark.parametrize(
814
"path, waypoint_index, boat_lat_lon, correct_heading, new_wp_index",
@@ -55,3 +61,59 @@ def test_calculate_desired_heading_and_waypoint_index(
5561
)
5662
assert calculated_answer[0] == pytest.approx(correct_heading, abs=3e-1)
5763
assert calculated_answer[1] == new_wp_index
64+
65+
66+
@pytest.mark.parametrize(
67+
"global_path, boat_lat_lon, correct_index",
68+
[
69+
(
70+
ci.Path(waypoints=[ci.HelperLatLon(latitude=0.0, longitude=0.0)]),
71+
ci.HelperLatLon(latitude=0.0, longitude=-0.1),
72+
0,
73+
),
74+
(
75+
ci.Path(
76+
waypoints=[
77+
ci.HelperLatLon(latitude=0.0, longitude=0.0),
78+
ci.HelperLatLon(latitude=0.0, longitude=1 * PATH_RANGE_DEG),
79+
ci.HelperLatLon(latitude=0.0, longitude=2 * PATH_RANGE_DEG),
80+
ci.HelperLatLon(latitude=0.0, longitude=3 * PATH_RANGE_DEG),
81+
]
82+
),
83+
ci.HelperLatLon(latitude=0.98 * PATH_RANGE_DEG, longitude=2 * PATH_RANGE_DEG),
84+
2,
85+
),
86+
(
87+
ci.Path(
88+
waypoints=[
89+
ci.HelperLatLon(latitude=0.0, longitude=0.0),
90+
ci.HelperLatLon(latitude=0.0, longitude=1 * PATH_RANGE_DEG),
91+
ci.HelperLatLon(latitude=0.0, longitude=2 * PATH_RANGE_DEG),
92+
ci.HelperLatLon(latitude=0.0, longitude=3 * PATH_RANGE_DEG),
93+
]
94+
),
95+
ci.HelperLatLon(latitude=1.02 * PATH_RANGE_DEG, longitude=1 * PATH_RANGE_DEG),
96+
1,
97+
),
98+
(
99+
ci.Path(
100+
waypoints=[
101+
ci.HelperLatLon(latitude=1.0 * PATH_RANGE_DEG, longitude=1 * PATH_RANGE_DEG),
102+
ci.HelperLatLon(latitude=1.0 * PATH_RANGE_DEG, longitude=2 * PATH_RANGE_DEG),
103+
ci.HelperLatLon(latitude=0.0, longitude=2 * PATH_RANGE_DEG),
104+
ci.HelperLatLon(latitude=0.0, longitude=1 * PATH_RANGE_DEG),
105+
ci.HelperLatLon(latitude=0.0, longitude=0.0),
106+
]
107+
),
108+
ci.HelperLatLon(latitude=0.98 * PATH_RANGE_DEG, longitude=1 * PATH_RANGE_DEG),
109+
3,
110+
),
111+
],
112+
)
113+
def test_find_next_global_waypoint_index(
114+
global_path: ci.Path, boat_lat_lon: ci.HelperLatLon, correct_index: int
115+
):
116+
calculated_answer = nn.Sailbot.determine_start_point_in_new_global_path(
117+
global_path, boat_lat_lon
118+
)
119+
assert calculated_answer == correct_index

src/local_pathfinding/test/test_ompl_objectives.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
HelperLatLon(latitude=1.0, longitude=1.0),
2727
]
2828
),
29+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=1.0),
2930
filtered_wind_sensor=WindSensor(),
3031
planner="rrtstar",
3132
),

src/local_pathfinding/test/test_ompl_path.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@
3030
ais_ships=AISShips(),
3131
global_path=Path(
3232
waypoints=[
33-
HelperLatLon(latitude=0.0, longitude=0.0),
34-
HelperLatLon(latitude=1.0, longitude=1.0),
33+
HelperLatLon(latitude=1.0, longitude=2.0),
34+
HelperLatLon(latitude=3.0, longitude=4.0),
3535
]
3636
),
37+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=2.0),
3738
filtered_wind_sensor=WindSensor(),
3839
planner="rrtstar",
3940
),
@@ -50,10 +51,11 @@ def fresh_ompl_path():
5051
ais_ships=AISShips(),
5152
global_path=Path(
5253
waypoints=[
53-
HelperLatLon(latitude=0.0, longitude=0.0),
54-
HelperLatLon(latitude=1.0, longitude=1.0),
54+
HelperLatLon(latitude=1.0, longitude=2.0),
55+
HelperLatLon(latitude=3.0, longitude=4.0),
5556
]
5657
),
58+
target_global_waypoint=HelperLatLon(latitude=1.0, longitude=2.0),
5759
filtered_wind_sensor=WindSensor(),
5860
planner="rrtstar",
5961
),
@@ -124,6 +126,7 @@ def test_init_obstacles():
124126
goal_position,
125127
]
126128
),
129+
target_global_waypoint=goal_position,
127130
filtered_wind_sensor=WindSensor(),
128131
planner="rrtstar",
129132
)
@@ -204,6 +207,7 @@ def test_init_obstacles():
204207
goal_position,
205208
]
206209
),
210+
target_global_waypoint=HelperLatLon(latitude=0.0, longitude=0.0),
207211
filtered_wind_sensor=WindSensor(),
208212
planner="rrtstar",
209213
)

0 commit comments

Comments
 (0)