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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "posim-routing"
version = "0.1.6"
version = "0.1.7"
description = "Global maritime and terrain checked coastal route planning"
requires-python = ">=3.10"
license = {text = "Apache-2.0"}
Expand Down
18 changes: 16 additions & 2 deletions src/posim_routing/regional.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,19 @@ def active_point(self,state,key):
self.discard(state,'regional_clearance_policy_changed')
return None
from .course import passed_vertex
# Persisted renewals from the previous coordinator may label their
# entire retained runway as an exit. Only the appended tail is an exit.
if plan.get('mode')=='shallow_exit' and plan.get('retained_checked_points',0):
plan.setdefault('shallow_exit_start_index',plan['retained_checked_points'])
exit_index=plan.get('shallow_exit_start_index')
while plan['index']<len(plan['points']):
index=plan['index'];point=plan['points'][index]
reached=distance(state['position'],point)<(5 if plan.get('mode') in ('port_departure','shallow_exit') else 12 if plan.get('mode')=='port_approach' else 80)
if exit_index is not None:
plan['mode']='shallow_exit' if index>=exit_index else plan.get('retained_mode','coastal')
exit_handoff=exit_index is not None and index==exit_index-1
reached=distance(state['position'],point)<(5 if exit_handoff or plan.get('mode') in ('port_departure','shallow_exit') else 12 if plan.get('mode')=='port_approach' else 80)
previous=plan['points'][index-1] if index else plan.get('probe_position')
passed=(plan.get('mode') in ('coastal','coastal_transfer','coastal_approach') and previous is not None
passed=(not exit_handoff and plan.get('mode') in ('coastal','coastal_transfer','coastal_approach') and previous is not None
and index<len(plan['points'])-1 and passed_vertex(previous,point,plan['points'][index+1],
state['position'],maximum_offset=200.,minimum_leg=0.,corner_margin=40.))
if not reached and not passed:break
Expand Down Expand Up @@ -237,6 +245,8 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
renew_coast=renew_coast or (point is not None and nearest is not None
and plan.get('mode')=='coastal'
and (plan.get('policy')!=REGIONAL_POLICY or not plan.get('onward_verified') or plan.get('waypoint_format')!=WAYPOINT_FORMAT))
# Finish a known outward tail before asking for another continuation.
renew_coast=renew_coast and plan.get('shallow_exit_start_index') is None
coast_needed=nearest is not None and (self.probed is None or distance(self.probed,state['position'])>=3000)
prefer_coast=(point is not None and coast_needed and not needed and
(state.get('regional_navigation') or {}).get('mode') not in ('coastal','coastal_transfer','coastal_approach'))
Expand All @@ -263,6 +273,10 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
retained=plan['points'][plan['index']:end+1]
value['points']=retained+value['points'];value['index']=0
value['retained_checked_points']=len(retained)
if value.get('mode')=='shallow_exit':
value['shallow_exit_start_index']=len(retained)
value['retained_mode']=plan.get('mode','coastal')
value['mode']=value['retained_mode']
value['renewed_from']=connection
value['probe_position']=list(state['position'])
for metric in ('minimum_regional_clearance_m',):
Expand Down
38 changes: 38 additions & 0 deletions tests/test_coastal_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,44 @@ async def result():return dict(points=[new],index=0,mode='coastal',route_key=key
self.assertNotIn('regional_waiting',sample)
await navigator.close()

async def test_outward_tail_does_not_relabel_retained_forward_runway(self):
key=[0,0,1];start,first,end,exit_point=geo(800,1000),geo(800,2000),geo(800,3000),geo(1000,3000)
navigator=RegionalNavigator(None);navigator.key=key
sample=dict(lap=0,leg=0,waypoint=1,position=start,native={},
regional_navigation=dict(points=[first,end],index=0,mode='coastal',route_key=key,
policy=REGIONAL_POLICY,job='runway',probe_position=geo(800,0)))
async def result():return dict(points=[exit_point],index=0,mode='shallow_exit',route_key=key,
policy=REGIONAL_POLICY,job='exit',probe_position=end,
continuation_of=dict(job='runway',endpoint=end))
navigator.task=asyncio.create_task(result());await navigator.task
self.assertEqual(navigator.update(sample,geo(800,90000)),first)
plan=sample['regional_navigation']
self.assertEqual(plan['mode'],'coastal');self.assertEqual(plan['shallow_exit_start_index'],2)
sample['position']=first
self.assertEqual(navigator.active_point(sample,key),end)
sample['position']=geo(800,2950)
self.assertEqual(navigator.active_point(sample,key),end)
self.assertEqual(plan['mode'],'coastal')
sample['position']=end
self.assertEqual(navigator.active_point(sample,key),exit_point)
self.assertEqual(plan['mode'],'shallow_exit');self.assertEqual(plan['index'],2)
await navigator.close()

async def test_saved_exit_with_retained_points_migrates_only_its_phase(self):
key=[0,0,1];first,end,exit_point=geo(800,2000),geo(800,3000),geo(1000,3000)
plan=dict(points=[first,end,exit_point],index=0,mode='shallow_exit',route_key=key,
policy=REGIONAL_POLICY,job='saved',probe_position=geo(800,0),retained_checked_points=2)
sample=dict(position=geo(800,1000),native={},regional_navigation=plan)
navigator=RegionalNavigator(None)
self.assertEqual(navigator.active_point(sample,key),first)
self.assertEqual(plan['mode'],'coastal');self.assertEqual(plan['points'],[first,end,exit_point])
sample['position']=first
self.assertEqual(navigator.active_point(sample,key),end)
sample['position']=end
self.assertEqual(navigator.active_point(sample,key),exit_point)
self.assertEqual(plan['mode'],'shallow_exit')
await navigator.close()

async def test_exhausted_coast_accepts_checked_replacement_water_route(self):
async def rpc(*args,**kwargs):await asyncio.Event().wait()
class Near:
Expand Down