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
9 changes: 7 additions & 2 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ path = planner.route(start, goal)
|---|---|
| 수상 차량 로컬 수심 여유 | 1.5 m |
| 수상 차량 반경 + 추적 여유 | 3.5 m + 4 m |
| 해안 선호 거리 | 350 m |
| 해안 선호 거리 | 400 m |
| 해안 추종 중 육지와의 최대 거리 | 1.5 km |
| 해안 선호에 따른 추가 거리 한도 | 100% (전체 거리 2.0배) |
| 해안 지역 여유 폭 / 수심 | 40 m / 2 m |
Expand Down Expand Up @@ -126,11 +126,16 @@ path = planner.route(start, goal)
`state['regional_waiting']`이 있으면 검증된 다음 경로가 준비될 때까지 추진을
멈춥니다. 물리 계산과 센서 수신은 계속합니다. 선택적인 해안 경로 준비에 실패하거나
결과가 비어 있어도 검증된 해안 경로를 외해 목표로 바꾸지 않습니다.
계획 감시는 제한 시간 안의 작업을 유지하고 완료된 결과부터 반영합니다.
경로 준비 지연만으로 차량의 물리적 후퇴를 요청하지 않습니다.

기본 지형 자료 서버에 연결할 수 없으면 더 작은 구역으로 경로를 계산합니다.
검증된 자료를 사용하며 수심과 육지 확인 기준은 같습니다.

- 일반 구역: 20 km × 20 km
- 해안에서 1.5 km 이내인 구역: 6 km × 6 km부터 검사
- 정밀 구역에서 안전한 연결 경로를 찾지 못하면: 20 km × 20 km로 확장
- 일반 지역 검사 범위: 20 km × 20 km
- 전체 백그라운드 경로 준비 제한 시간: 660초
- 자료 서버 장애 시 재시도 구역: 6 km × 6 km
- 두 구역 모두 자료 부족: 추진을 멈추고 재시도

Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Optional renewals keep the current checked coastal runway until a coastal contin
|---|---|
| Surface local depth clearance | 1.5 m |
| Surface hull radius + tracking margin | 3.5 m + 4 m |
| Coastal preferred distance | 350 m |
| Coastal preferred distance | 400 m |
| Maximum coastal-following distance from land | 1.5 km |
| Maximum extra distance for coastal preference | 100% (2.0× total length) |
| Coastal regional buffer / depth | 40 m / 2 m |
Expand Down Expand Up @@ -126,12 +126,17 @@ Pass the final port as `progress_target` when the local target is a course waypo
When `state['regional_waiting']` is present, the caller holds thrust until a
checked replacement is ready. Physics and sensor sampling continue during the
wait. Failed or empty optional renewal cannot replace a checked coastal route
with an offshore goal.
with an offshore goal. A planning watchdog preserves pending work within its
budget and consumes completed results before retrying. Planning delays do not
request a physical retreat.

When the baseline terrain provider is unavailable, regional planning tries a
smaller region. It uses verified source data and the same depth and land checks.

- Normal region: 20 km × 20 km
- Near-shore region, within 1.5 km of shore: 6 km × 6 km first
- Wider region when the fine grid has no checked connection: 20 km × 20 km
- Ordinary regional coverage: 20 km × 20 km
- Total background route preparation budget: 660 seconds
- Source outage retry: 6 km × 6 km
- Missing data in both regions: hold thrust and retry

Expand Down
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.4"
version = "0.1.5"
description = "Global maritime and terrain checked coastal route planning"
requires-python = ">=3.10"
license = {text = "Apache-2.0"}
Expand Down
2 changes: 1 addition & 1 deletion src/posim_routing/coastal.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

GEO = Geod(ellps="WGS84")
COAST_DISCOVERY_M = 80000.
STANDOFF_M = 350.
STANDOFF_M = 400.
MAXIMUM_STANDOFF_M = 600.
MINIMUM_DEPTH_M = 2.
REGIONAL_BUFFER_M = 40.
Expand Down
53 changes: 38 additions & 15 deletions src/posim_routing/regional.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import asyncio,math,time
from pyproj import Geod
GEO=Geod(ellps='WGS84')
REGIONAL_POLICY='buffered-water-v18-coastal-350m'
REGIONAL_POLICY='buffered-water-v19-coastal-400m'
COMPATIBLE_POLICIES={REGIONAL_POLICY,'buffered-water-v18-coastal-350m'}
PLANNING_BUDGET_SECONDS=660.
REGIONAL_BUFFER_M=40.
REGIONAL_DEPTH_M=2.

Expand Down Expand Up @@ -61,9 +63,23 @@ def __init__(self,rpc,coastal=False,before_prepare=None):
self.rpc=rpc;self.task=None;self.key=None;self.retry=0
self.coastal=coastal;self.coast_index=None;self.index_task=None;self.probed=None
self.progress=None;self.coastal_target=None
self.recovery_extent=None
def recover_wait(self,state):
"""Retry stalled planning at another resolution without issuing movement."""
self.recovery_extent=None;self.task_started=None
def start_task(self,work):
# The controller watchdog must not cancel terrain preparation before
# its own bounded provider deadline. One budget includes all retries.
self.task_started=time.monotonic()
work_task=asyncio.create_task(work)
self.task=asyncio.create_task(asyncio.wait_for(work_task,PLANNING_BUDGET_SECONDS))
# Cancellation before the wrapper starts must still close its work.
self.task.add_done_callback(lambda _: work_task.cancel() if not work_task.done() else None)
def recover_wait(self,state,preserve_pending=False):
"""Retry expired work; keep bounded pending or completed work consumable."""
if preserve_pending and self.task is not None:
elapsed=time.monotonic()-self.task_started if self.task_started is not None else PLANNING_BUDGET_SECONDS
if self.task.done() or elapsed<PLANNING_BUDGET_SECONDS:
state.setdefault('regional_planning',{}).update(elapsed_seconds=max(0.,elapsed),
budget_seconds=PLANNING_BUDGET_SECONDS)
return dict(pending=True)
if self.task:self.task.cancel()
self.task=None;self.retry=0.;self.probed=None;self.progress=None
previous=state.get('navigation_failsafe') or {}
Expand All @@ -88,7 +104,7 @@ def active_point(self,state,key):
if plan.get('mode')=='port_approach' and plan.get('arrival_radius_m')!=ARRIVAL_RADIUS_M:
self.discard(state,'port_arrival_region_changed')
return None
if plan.get('policy')!=REGIONAL_POLICY:
if plan.get('policy') not in COMPATIBLE_POLICIES:
self.discard(state,'regional_clearance_policy_changed')
return None
from .course import passed_vertex
Expand Down Expand Up @@ -168,7 +184,7 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
and plan.get('mode') in ('coastal','coastal_transfer','coastal_approach'))
if ((port_handoff or coast_handoff) and old and len(old)==3
and old[:2]==key[:2] and old[2]<key[2]
and plan.get('policy')==REGIONAL_POLICY):
and plan.get('policy') in COMPATIBLE_POLICIES):
plan['route_key']=list(key);plan.pop('continuation',None)
state['regional_handoff']=dict(previous_key=old,route_key=list(key),
position=list(state['position']),reason='same_port_course_advance' if port_handoff else 'measured_coastal_shape_advance')
Expand Down Expand Up @@ -219,7 +235,7 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
from .coastal import WAYPOINT_FORMAT
renew_coast=renew_coast or (point is not None and nearest is not None
and plan.get('mode')=='coastal'
and (not plan.get('onward_verified') or plan.get('waypoint_format')!=WAYPOINT_FORMAT))
and (plan.get('policy')!=REGIONAL_POLICY or not plan.get('onward_verified') or plan.get('waypoint_format')!=WAYPOINT_FORMAT))
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 @@ -236,7 +252,7 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
# A failed optional preference must preserve a previously
# checked detour while waiting for a better coastal region.
coast_plan=(plan.get('route_key')==key and plan.get('mode') in ('coastal','coastal_transfer','coastal_approach')
and plan.get('policy')==REGIONAL_POLICY)
and plan.get('policy') in COMPATIBLE_POLICIES)
preserve_empty=(not value['points'] and (point is not None or
(coast_plan and nearest is not None and not terminal and not needed)))
# An optional scenic renewal cannot replace a checked coastal
Expand Down Expand Up @@ -264,11 +280,11 @@ def update(self,state,target,needed=False,terminal=False,retain_coast=False,prog
coast_needed=nearest is not None and (self.probed is None or distance(self.probed,state['position'])>=3000)
if (prefer_port or needed or coast_needed or renew_coast) and not self.task and time.monotonic()>=self.retry:
state['regional_planning']=dict(status='background',mode='port_approach' if terminal else 'coastal' if coast_needed else 'detour')
self.task=asyncio.create_task(self.prepare_port(state,target,key) if terminal else self.prepare(state,target,key,nearest,needed,extent=self.recovery_extent,progress_target=progress_target,coastal_target=coastal_target))
self.start_task(self.prepare_port(state,target,key) if terminal else self.prepare(state,target,key,nearest,needed,extent=self.recovery_extent,progress_target=progress_target,coastal_target=coastal_target))
self.recovery_extent=None
if point is not None:return point
plan=state.get('regional_navigation') or {}
if (plan.get('route_key')==key and plan.get('policy')==REGIONAL_POLICY
if (plan.get('route_key')==key and plan.get('policy') in COMPATIBLE_POLICIES
and (plan.get('mode') in ('coastal','coastal_transfer','coastal_approach') or plan.get('mapped_guidance')) and plan.get('points')
and plan['index']>=len(plan['points'])
and distance(state['position'],plan['points'][-1])<1500
Expand Down Expand Up @@ -305,7 +321,7 @@ def departure_update(self,state,target,key):
self.task=None
if point is not None:return point
if self.task is None and time.monotonic()>=self.retry:
self.task=asyncio.create_task(self.prepare_departure(state,target,key))
self.start_task(self.prepare_departure(state,target,key))
state['regional_planning']=dict(status='background',mode='port_departure')
state['regional_waiting']=dict(reason='resident_departure_corridor_pending')
return state['position']
Expand Down Expand Up @@ -412,7 +428,7 @@ async def terrain_region(self, centre, extent):
except Exception:break
if (sources.get('baseline_status') or {}).get('status')!='unavailable':break
raise RuntimeError('regional_terrain_unavailable')
async def prepare(self,state,target,key,nearest=None,needed=True,extent=None,progress_target=None,coastal_target=None):
async def prepare(self,state,target,key,nearest=None,needed=True,extent=None,progress_target=None,coastal_target=None,fine_checked=False):
from posim_terrain.canal_override import in_region
canal=in_region(state['position'])
if self.coastal and not canal and nearest is None and self.index_task is not None:
Expand All @@ -433,7 +449,11 @@ async def prepare(self,state,target,key,nearest=None,needed=True,extent=None,pro
# distant coast. The 1:10M discovery line never supplies depth.
heading,_,_=GEO.inv(*pos,*nearest[1])
centre=list(GEO.fwd(*pos,heading,min(6000.,nearest[0]-6000.))[:2])
extent=extent or (4000 if canal else 20000)
initial_extent=extent is None
# Fine coastal triangles first: a 20 km grid can close navigable water
# beside a headland and waste an entire provider/planner cycle.
extent=extent or (4000 if canal else 6000 if nearest and nearest[0]<1500. else 20000)
fine_checked=fine_checked or extent==6000
if self.before_prepare is not None:await self.before_prepare()
job=await self.terrain_region(centre,extent)
grid=await self.rpc('/terrain/jobs/'+job['id']+'/files/terrain.json',timeout=20)
Expand All @@ -450,7 +470,7 @@ async def prepare(self,state,target,key,nearest=None,needed=True,extent=None,pro
try:value=await asyncio.to_thread(coastal_corridor,grid,job['spec'],current,coastal_goal,guard=guard,progress_target=progress_target)
except RuntimeError as error:coast_error=str(error)[:160]
water_fallback=value if value is not None and value.get('mode')=='detour' else None
if nearest and (value is None or water_fallback is not None) and extent>6000 and nearest[0]<1500.:
if nearest and not fine_checked and (value is None or water_fallback is not None) and extent>6000 and nearest[0]<1500.:
# A coarse buffered pixel can disconnect otherwise navigable coastal
# water. Resolve the current shoreline before accepting a water exit.
return await self.prepare(state,course_target,key,nearest,needed,extent=6000,
Expand Down Expand Up @@ -506,8 +526,11 @@ async def prepare(self,state,target,key,nearest=None,needed=True,extent=None,pro
# Coarse regional pixels can close a harbour entrance.
# Resolve it on a smaller real terrain grid before retrying;
# never substitute an arbitrary offshore frontier.
if not canal and extent>6000 and str(error).startswith('regional_corridor_unavailable'):
if not canal and not fine_checked and extent>6000 and str(error).startswith('regional_corridor_unavailable'):
return await self.prepare(state,course_target,key,nearest,needed,extent=6000,progress_target=progress_target,coastal_target=coastal_target)
if not canal and initial_extent and extent==6000:
return await self.prepare(state,course_target,key,nearest,needed,extent=20000,
progress_target=progress_target,coastal_target=coastal_target,fine_checked=True)
raise
if value['points']:value['mode']='detour'
if canal and value['points']:value.update(mode='canal',assumed_depth_m=10,measured_bathymetry=False)
Expand Down
Loading