Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/posim_routing/coastal.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
MAXIMUM_STANDOFF_M = 600.
MINIMUM_DEPTH_M = 2.
REGIONAL_BUFFER_M = 200.
WAYPOINT_FORMAT = "checked-segment-ends-v5-coastal-transfer"
WAYPOINT_FORMAT = "checked-segment-ends-v7-real-shore"
MAXIMUM_COASTAL_DETOUR_RATIO = 2.0
MAXIMUM_COASTAL_DISTANCE_M = 1500.

Expand Down Expand Up @@ -140,7 +140,8 @@ def coastal_corridor(terrain, spec, start, target, timeout=25., guard=None, prog
field = TerrainField(terrain["xs"], terrain["ys"], terrain["heights"])
xs, ys, heights = field.xs, field.ys, field.heights
finite = np.isfinite(heights)
land = finite & (heights > -MINIMUM_DEPTH_M+DEPTH_EPSILON_M)
# Follow actual land; submerged banks constrain safety without attracting coastal waypoints.
land = finite & (heights >= 0)
if guard is not None:
land |= ~guard.grid(xs, ys)
if not land.any():
Expand Down Expand Up @@ -295,7 +296,8 @@ def shorten(route, costs, weights, lookahead=None, shore_bound=False):
old = costs[route[candidate]]-costs[route[index]]
if (safe[iy, ix].all()
and (not shore_bound or (coast_distance[iy, ix] <= shore_limit).all())
and weighted <= old*1.08
and (not shore_bound or length <= 7500.)
and (shore_bound or weighted <= old*1.08)
and validator.segment(a, b)):
chosen = candidate
break
Expand Down Expand Up @@ -386,7 +388,7 @@ def route_length(points):
endpoint = nodes[end]
try:
approach = shorten(nodes[:entry+1], costs, penalty, lookahead=10)
following = shorten(nodes[entry:end+1], costs, penalty, lookahead=10, shore_bound=True)
following = shorten(nodes[entry:end+1], costs, penalty, shore_bound=True)
points = approach[:-1] + following
onward = shorten(nodes[end:], costs, penalty)
coastal_onward = shorten(nodes[end:follow_end+1], costs, penalty, shore_bound=True)
Expand Down Expand Up @@ -424,6 +426,7 @@ def route_length(points):
index=0, mode="coastal_transfer" if transfer_required else "coastal", planning_seconds=time.monotonic()-started,
search_nodes=expanded, minimum_regional_clearance_m=validator.minimum,
preferred_standoff_m=STANDOFF_M,
coastal_reference_basis="land_shoreline",
shallow_water_buffer_m=REGIONAL_BUFFER_M,
shallow_boundary_depth_m=MINIMUM_DEPTH_M,
endpoint_standoff_m=float(coast_distance[endpoint]),
Expand Down
2 changes: 1 addition & 1 deletion src/posim_routing/regional.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import asyncio,math,time
from pyproj import Geod
GEO=Geod(ellps='WGS84')
REGIONAL_POLICY='buffered-water-v21-shallow-200m-coastal-320m'
REGIONAL_POLICY='buffered-water-v22-real-shore-shallow-200m-coastal-320m'
COMPATIBLE_POLICIES={REGIONAL_POLICY}
PLANNING_BUDGET_SECONDS=660.
REGIONAL_BUFFER_M=200.
Expand Down
47 changes: 43 additions & 4 deletions tests/test_coastal_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ def test_doubled_detour_budget_can_join_previously_rejected_coast(self):
self.assertLessEqual(plan['coastal_through_length_m']/plan['shortest_checked_length_m'], 2.)
self.assert_coastal_band(grid(), start, plan)

def test_clear_coastal_run_does_not_keep_grid_staircases(self):
for amplitude, period in ((0., 1000.), (500., 2000.)):
with self.subTest(shore_amplitude=amplitude):
terrain = grid()
x, y = np.meshgrid(terrain['xs'], terrain['ys'])
terrain['heights'] = np.where(
x <= amplitude*np.sin(y/period*2*np.pi), 20., -15.).tolist()
start = geo(1200, -4000)
plan = coastal_corridor(terrain, SPEC, start, geo(1200, 9000))
self.assertEqual(plan['mode'], 'coastal')
through = plan['points'] + plan['onward_points']
# A clear run needs bounded segment ends, not one turn per
# ten grid cells or every small shoreline indentation.
self.assertLessEqual(len(through), 4)
self.assertLess(plan['coastal_through_length_m'], 14500.)
self.assertTrue(all(distance(a, b) <= 7501.
for a, b in zip([start]+through[:-1], through)))
self.assert_coastal_band(terrain, start, plan)
self.assert_swept_safe(terrain, start, dict(points=through))

def test_safe_backward_frontier_is_rejected(self):
axis=np.linspace(-10000,10000,257)
x,y=np.meshgrid(axis,axis)
Expand Down Expand Up @@ -195,14 +215,33 @@ def test_shallow_and_unknown_coast_is_not_a_destination(self):
heights[:, (x > 0) & (x < 1700)] = -1.
terrain['heights'] = heights.tolist()
plan=coastal_corridor(terrain, SPEC, geo(3000,-4000),geo(6500,9000))
self.assertIsNotNone(plan)
self.assertEqual(plan['shallow_water_buffer_m'],200.)
self.assert_swept_safe(terrain,geo(3000,-4000),plan)
self.assertGreater(FORWARD.transform(*plan['points'][-1])[0],1900.)
# The bank blocks the coastal band around real land; use checked open water.
self.assertIsNone(plan)
water = corridor(terrain, SPEC, geo(3000,-4000),geo(6500,9000))
self.assert_swept_safe(terrain,geo(3000,-4000),water)
self.assertGreater(FORWARD.transform(*water['points'][-1])[0],1900.)
heights[:, (x > 0) & (x < 1700)] = np.nan
terrain['heights'] = heights.tolist()
self.assertIsNone(coastal_corridor(terrain, SPEC, geo(3000, -4000), geo(6500, 9000)))

def test_submerged_banks_do_not_attract_coastal_waypoints(self):
for actual_land in (False, True):
with self.subTest(actual_land=actual_land):
terrain = grid()
x, y = np.meshgrid(terrain['xs'], terrain['ys'])
heights = np.where(x <= 0, -1., -15.)
if actual_land:
heights[x <= -6000] = 20.
terrain['heights'] = heights.tolist()
# The safe side of this bank is open water, kilometres from
# real land. It must not become a scenic coastline to follow.
self.assertIsNone(coastal_corridor(
terrain, SPEC, geo(800, -4000), geo(800, 9000)))
water = corridor(terrain, SPEC, geo(800, -4000), geo(800, 9000))
self.assertEqual(len(water['points']), 1)
end_x, _ = FORWARD.transform(*water['points'][0])
self.assertLess(abs(end_x-800.), 1.)

def test_two_point_five_metre_coast_is_available_to_wamv(self):
terrain=grid();heights=np.array(terrain['heights'])
heights[heights<0]=-2.5;terrain['heights']=heights.tolist()
Expand Down