Skip to content

Commit c2fabd2

Browse files
committed
Shorten checked coastal onward waypoint runs
1 parent cc8fb34 commit c2fabd2

7 files changed

Lines changed: 70 additions & 20 deletions

File tree

‎README.ko.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ path = planner.route(start, goal)
6161
구간의 끝에서는 다음 구간이 준비될 때까지 기다립니다. 날짜 변경선에서도
6262
경도가 이어지도록 처리합니다.
6363

64+
해안 경로는 다음 목표나 지역 지형의 출구까지 검증된 수역을 따라갑니다.
65+
끝점에는 만과 곶을 돌아가는 후속 경로가 이어집니다. 일반 해안 경유점은
66+
진행 방향을 안내하며, 항구와 필수 통과점은 도착 조건을 유지합니다.
67+
68+
후속 경유점은 검증된 구간의 끝을 표시합니다. 차량이 지나가는 전체 폭의
69+
수심과 지도상 육지를 확인한 뒤 직선 구간의 중간 지점을 줄입니다.
70+
출구와 해안 선호 경로는 유지합니다.
71+
6472
| 기준 | 기본값 |
6573
|---|---|
6674
| 수상 차량 로컬 수심 여유 | 1.5 m |

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ exit from the regional grid. Its endpoint has an onward route around bays and
6666
headlands. Ordinary coastal course vertices guide progress; ports and required
6767
passage points retain their arrival checks.
6868

69+
Onward waypoints mark checked segment ends. Straight grid runs are shortened
70+
when the complete swept corridor passes depth and mapped-land checks. The
71+
shortened route preserves its exit and coastline preference.
72+
6973
| Policy | Default |
7074
|---|---|
7175
| Surface local depth clearance | 1.5 m |

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "posim-routing"
7-
version = "0.1.3"
7+
version = "0.1.4"
88
description = "Global maritime and terrain checked coastal route planning"
99
requires-python = ">=3.10"
1010
license = {text = "Apache-2.0"}

‎src/posim_routing/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""POSIM-ROUTING: reusable ocean simulation components."""
2-
__version__ = "0.1.3"
2+
__version__ = "0.1.4"

‎src/posim_routing/coastal.py‎

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -259,25 +259,36 @@ def xy(node):
259259
nodes = nodes[:end+1]
260260
# Shorten the grid path without erasing its coastal preference. Long
261261
# offshore shortcuts across bays have a higher coast-weighted cost.
262-
points, index = [[*origin, 0.]], 0
263-
while index < len(nodes)-1:
264-
chosen = index+1
265-
for end in range(min(len(nodes)-1, index+10), index, -1):
266-
a, b = xy(nodes[index]), xy(nodes[end])
267-
length = math.dist(a, b)
268-
count = max(2, math.ceil(length/min(dx, dy)*3))
269-
ix = np.rint(np.linspace(nodes[index][1], nodes[end][1], count)).astype(int)
270-
iy = np.rint(np.linspace(nodes[index][0], nodes[end][0], count)).astype(int)
271-
weighted = length*float(np.mean(penalty[iy, ix]))
272-
old = costs[nodes[end]]-costs[nodes[index]]
273-
if safe[iy, ix].all() and weighted <= old*1.08:
274-
chosen = end
275-
break
276-
points.append(xy(nodes[chosen]))
277-
index = chosen
262+
def shorten(route, lookahead=None):
263+
points, index = [xy(route[0])], 0
264+
while index < len(route)-1:
265+
chosen = index+1
266+
last = min(len(route)-1, index+lookahead) if lookahead else len(route)-1
267+
for candidate in range(last, index, -1):
268+
if time.monotonic() > deadline:
269+
raise RuntimeError("coastal_planning_budget_exceeded")
270+
a, b = xy(route[index]), xy(route[candidate])
271+
length = math.dist(a, b)
272+
count = max(2, math.ceil(length/min(dx, dy)*3))
273+
ix = np.rint(np.linspace(route[index][1], route[candidate][1], count)).astype(int)
274+
iy = np.rint(np.linspace(route[index][0], route[candidate][0], count)).astype(int)
275+
weighted = length*float(np.mean(penalty[iy, ix]))
276+
old = costs[route[candidate]]-costs[route[index]]
277+
if (safe[iy, ix].all() and weighted <= old*1.08
278+
and validator.segment(a, b)):
279+
chosen = candidate
280+
break
281+
points.append(xy(route[chosen]))
282+
index = chosen
283+
return points
284+
285+
points = shorten(nodes, lookahead=10)
286+
points[0] = [*origin, 0.]
287+
onward = shorten([endpoint, *onward_nodes])
278288
if sum(math.dist(a, b) for a, b in zip(points, points[1:])) > 40000:
279289
continue
280-
if not all(validator.segment(a, b, record=True) for a, b in zip(points, points[1:])):
290+
if not all(validator.segment(a, b, record=True) for route in (points, onward)
291+
for a, b in zip(route, route[1:])):
281292
continue
282293
return dict(points=[list(inverse.transform(*point[:2])) for point in points[1:]],
283294
index=0, mode="coastal", planning_seconds=time.monotonic()-started,
@@ -286,7 +297,7 @@ def xy(node):
286297
endpoint_standoff_m=float(coast_distance[endpoint]),
287298
maximum_preferred_standoff_m=MAXIMUM_STANDOFF_M,
288299
progress_to_course_waypoint_m=float(progress[endpoint]),
289-
onward_points=[list(inverse.transform(*xy(node)[:2])) for node in onward_nodes],
300+
onward_points=[list(inverse.transform(*point[:2])) for point in onward[1:]],
290301
onward_exit=list(inverse.transform(*xy(goal)[:2])),
291302
onward_exit_progress_m=float(progress[goal]),
292303
onward_verified=True,

‎tests/test_coastal_navigation.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ def test_closed_bay_requires_an_onward_water_exit(self):
9595
self.assertLess(end_y,-1000)
9696
self.assert_swept_safe(terrain,start,dict(points=plan['points']+plan['onward_points']))
9797

98+
def test_long_onward_runs_use_segment_ends_and_keep_the_exit(self):
99+
terrain = grid()
100+
start = geo(800, -4000)
101+
plan = coastal_corridor(terrain, SPEC, start, geo(6500, 9000))
102+
self.assertIsNotNone(plan)
103+
self.assertGreater(distance(plan['points'][-1], plan['onward_exit']), 5000.)
104+
self.assertGreater(len(plan['onward_points']), 0)
105+
self.assertLessEqual(len(plan['onward_points']), 4)
106+
np.testing.assert_allclose(plan['onward_points'][-1], plan['onward_exit'], atol=1e-10)
107+
self.assert_swept_safe(terrain, start, dict(points=plan['points']+plan['onward_points']))
108+
98109
def test_shallow_and_unknown_coast_is_not_a_destination(self):
99110
terrain = grid()
100111
heights = np.array(terrain['heights'])

‎tests/test_land_exclusion.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ def test_unknown_coverage_and_missing_dataset_are_rejected(self):
6262
with self.assertRaisesRegex(RuntimeError,'data_unavailable'):
6363
guard_from_spec(SPEC)
6464

65+
def test_shortened_onward_segments_keep_a_required_mapped_land_turn(self):
66+
axis=np.linspace(-10000.,10000.,257);xx,yy=np.meshgrid(axis,axis)
67+
grid=dict(xs=axis.tolist(),ys=axis.tolist(),heights=np.where(xx<=0,20.,-20.).tolist())
68+
# Both numeric terrain and unbuffered grid vertices miss this thin bank.
69+
land=shapely.union_all([shapely.box(-11000,-11000,0,11000),
70+
shapely.box(3000,8000,3002,9500)])
71+
guard=LandGuard(land,shapely.box(-11000,-11000,11000,11000))
72+
start=list(INVERSE.transform(800,-4000));target=list(INVERSE.transform(6500,9000))
73+
plan=coastal_corridor(grid,SPEC,start,target,guard=guard)
74+
self.assertIsNotNone(plan)
75+
onward=[[*FORWARD.transform(*p),0.] for p in [plan['points'][-1],*plan['onward_points']]]
76+
self.assertFalse(guard.segment(onward[0],onward[-1],40.))
77+
self.assertGreater(len(plan['onward_points']),1)
78+
self.assertTrue(all(guard.segment(a,b,40.) for a,b in zip(onward,onward[1:])))
79+
np.testing.assert_allclose(plan['onward_points'][-1],plan['onward_exit'],atol=1e-10)
80+
6581
def test_nearer_disconnected_exits_do_not_hide_reachable_water_exit(self):
6682
axis=np.linspace(-10000.,10000.,257)
6783
grid=dict(xs=axis.tolist(),ys=axis.tolist(),heights=np.full((257,257),-20.).tolist())

0 commit comments

Comments
 (0)