feat(D 예정노선): 받기 상한 = 칸 500 만 → 공용 창고 도엽 36 장 (PLAN 613-1 · 요구 493)
- 한 번에 이어 붙이는 도엽 수 36 장(6 × 6) 넘으면 413 + 알림 글(몇 장 · 줄이는 법) - 범위 보기 응답에 store_sheets · max_sheets - 시험 530_1 · 530_2 갱신 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,8 +63,9 @@ export interface TerrainExtent {
|
||||
covered: boolean;
|
||||
sheets: Array<{ no: string; ring: Array<[number, number]>; stored: boolean }>;
|
||||
cells: number;
|
||||
/** 받기 칸 상한(넘으면 받기 413). */
|
||||
max_cells: number;
|
||||
/** 공용 창고 이어 붙일 도엽 수 · 한 번에 상한(넘으면 받기 413). */
|
||||
store_sheets: number;
|
||||
max_sheets: number;
|
||||
}
|
||||
|
||||
/** 그은 선 서버 꼴(562-2) — 마크 = 조각 경계 · 조각 없는 구역 = 연도 없음. */
|
||||
@@ -130,7 +131,7 @@ export const fetchTerrainExtent = (
|
||||
return request(`${base(projectId)}/terrain/extent?${query}`);
|
||||
};
|
||||
|
||||
/** 받기 — 칸 상한 넘음 = 오류(413 글). */
|
||||
/** 받기 — 도엽 상한 넘음 = 오류(413 글). */
|
||||
export const startTerrain = (
|
||||
projectId: string,
|
||||
marginM: number | null,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
(PLAN 591-2 · 기준점 = 처음 받는 기준일 뿐).
|
||||
위치 바꿈(PLAN 595 · 꼴 = PLAN 595 절) = 몸 `center`(위치 칸 캐시 기준점) 둘레를 더함 ·
|
||||
받은 범위에서 `FAR_M` 넘음 = 「far」 물음 · 다른 TM 띠 = 「band」 물음 → `reset` = 옛 범위 버리고
|
||||
center 둘레만 · 칸 `MAX_CELLS` 넘음 = 413 · 진행은 `models_plan/plan_job_<일>.json` 에도 적어
|
||||
center 둘레만 · 창고 도엽 `MAX_SHEETS` 넘음 = 413 · 진행은 `models_plan/plan_job_<일>.json` 에도 적어
|
||||
서버가 다시 켜지면 「끊김」(interrupted).
|
||||
노선 정본(손 그은 선)은
|
||||
`D03_PlanAnalysis_Network`(PLAN 562-2 · 옛 추천 엔진 · 꺾임점 R 걷음).
|
||||
@@ -221,7 +221,7 @@ async def plan_terrain_extent(
|
||||
@router.post("/{project_id}/plan/terrain", response_model=None)
|
||||
async def plan_terrain(project_id: UUID, body: TerrainBody | None = None) -> dict[str, Any]:
|
||||
"""사업 전체 수치지형도 · 위성 · DEM 받기(뒤에서) — 진행은 `GET …/plan/progress` ·
|
||||
위치 칸 가운데가 멀거나 다른 띠 = 물음(`far` · `band`) · 칸 상한 넘음 = 413."""
|
||||
위치 칸 가운데가 멀거나 다른 띠 = 물음(`far` · `band`) · 도엽 상한 넘음 = 413."""
|
||||
body = body or TerrainBody()
|
||||
center = (body.center.lon, body.center.lat) if body.center else None
|
||||
root, epsg, ends, band = await _plan_ends(project_id, center, body.reset)
|
||||
@@ -243,13 +243,10 @@ async def plan_terrain(project_id: UUID, body: TerrainBody | None = None) -> dic
|
||||
"옛 받은 범위를 버리고 새로 받을까요?",
|
||||
}
|
||||
points = [*ends, *body.extra]
|
||||
cells = terrain.cells_of(terrain.wanted_box(points, terrain.clamp_margin(body.margin_m), have))
|
||||
if cells > terrain.MAX_CELLS:
|
||||
raise HTTPException(
|
||||
status_code=413,
|
||||
detail=f"받을 범위가 너무 큽니다 — 칸 {cells / 1e4:.0f}만 · "
|
||||
f"상한 {terrain.MAX_CELLS / 1e4:.0f}만(지점 · 선을 모으거나 위치를 옮겨 새로 받으세요)",
|
||||
)
|
||||
want = terrain.wanted_box(points, terrain.clamp_margin(body.margin_m), have)
|
||||
count = terrain.store_sheet_count(want, epsg)
|
||||
if count > terrain.MAX_SHEETS:
|
||||
raise HTTPException(status_code=413, detail=terrain.sheet_limit_message(count))
|
||||
|
||||
async def work(report: Any) -> None:
|
||||
got = await asyncio.to_thread(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
- 범위 = 전체 시점 · 종점(+ 그은 점) 상자 + 여유 · 받은 범위가 있으면 그 범위와 합침(줄지 않음) ·
|
||||
새로(`reset` · 위치를 멀리 옮김) = 옛 받은 범위 · 병합 도엽 · 위성 범위를 버리고 새 상자만 ·
|
||||
칸 상한 `MAX_CELLS`(넘으면 라우터가 거절)
|
||||
칸 상한 도엽 `MAX_SHEETS`(공용 창고 · 넘으면 라우터가 거절)
|
||||
- 도엽 = 범위 상자에 닿는 도엽 모두 → `ensure_sheets`(없는 것만 · prune 없음)
|
||||
- 병합 = 지난번 병합 도엽 ∪ 이번 도엽(옛 도엽 유지 · 열쇠 같으면 건너뜀)
|
||||
- 위성 = 범위 도엽 도곽 ∪ 옛 위성 범위(덮으면 건너뜀)
|
||||
@@ -65,9 +65,9 @@ DEFAULT_MARGIN_M = 350.0
|
||||
MAX_MARGIN_M = 5000.0
|
||||
# 처음 받는 범위 = 등록 기준점 둘레 ± 1 km(요구 453 · PLAN 591-2)
|
||||
SITE_RADIUS_M = 1000.0
|
||||
# 위치 바꿈이 받은 범위에서 이만큼 넘게 멀면 묻고 새로 · 받을 DEM 칸 상한(요구서_463 3장 4)
|
||||
# 위치 바꿈이 받은 범위에서 이만큼 넘게 멀면 묻고 새로 · 받을 도엽 상한(요구서_463 3장 4 · 613-1)
|
||||
FAR_M = 5000.0
|
||||
MAX_CELLS = 5_000_000
|
||||
MAX_SHEETS = 36 # 한 번에 이어 붙이는 공용 창고 도엽 수 상한(6 × 6)
|
||||
# ponytail: 새 도엽 함께 셀 수 — 장당 메모리 1 GB 안팎 · 넘으면 차례로
|
||||
WARM_WORKERS = 4
|
||||
STAGE = Path("B04_PreProcess")
|
||||
@@ -125,10 +125,23 @@ def wanted_box(points: list[XY], margin_m: float, have: Box | None) -> Box:
|
||||
|
||||
|
||||
def cells_of(box: Box) -> int:
|
||||
"""DEM 칸 수 어림 — 받기 시간 · 메모리 가늠 · 상한 판정."""
|
||||
"""DEM 칸 수 어림 — 받기 시간 · 메모리 가늠."""
|
||||
return int((box[2] - box[0]) * (box[3] - box[1]) / PLAN_TERRAIN_GRID_M**2)
|
||||
|
||||
|
||||
def store_sheet_count(box: Box, epsg: int) -> int:
|
||||
"""공용 창고에서 이어 붙일 도엽 수 — 상한 판정."""
|
||||
return len(terrain_store.sheets_for_box(box, epsg))
|
||||
|
||||
|
||||
def sheet_limit_message(count: int) -> str:
|
||||
"""도엽 상한 넘음 알림 글 — 몇 장 · 줄이는 법."""
|
||||
return (
|
||||
f"받을 범위가 너무 넓습니다 — 도엽 {count}장 · 한 번에 {MAX_SHEETS}장까지"
|
||||
"(지점 · 선을 모으거나 위치를 옮겨 새로 받으세요)"
|
||||
)
|
||||
|
||||
|
||||
def distance_to_box(xy: XY, box: Box) -> float:
|
||||
"""점 ~ 상자 거리(m · 안이면 0)."""
|
||||
dx = max(box[0] - xy[0], 0.0, xy[0] - box[2])
|
||||
@@ -195,7 +208,8 @@ def plan_extent(
|
||||
"covered": covers(have, box),
|
||||
"sheets": sheets,
|
||||
"cells": cells_of(box),
|
||||
"max_cells": MAX_CELLS,
|
||||
"store_sheets": store_sheet_count(box, epsg),
|
||||
"max_sheets": MAX_SHEETS,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -157,8 +157,28 @@ def test_cells_distance_and_limit(fake) -> None:
|
||||
assert terrain.cells_of((0.0, 0.0, 2000.0, 2000.0)) == 1_000_000 # 2 m 격자
|
||||
assert terrain.distance_to_box((5.0, 5.0), (0.0, 0.0, 10.0, 10.0)) == 0
|
||||
assert terrain.distance_to_box((13.0, 14.0), (0.0, 0.0, 10.0, 10.0)) == 5
|
||||
assert terrain.MAX_CELLS == 5_000_000 and terrain.FAR_M == 5000
|
||||
assert terrain.plan_extent(root, [A, B], EPSG)["max_cells"] == terrain.MAX_CELLS
|
||||
assert terrain.MAX_SHEETS == 36 and terrain.FAR_M == 5000
|
||||
view = terrain.plan_extent(root, [A, B], EPSG)
|
||||
assert view["max_sheets"] == terrain.MAX_SHEETS and "max_cells" not in view
|
||||
assert view["store_sheets"] == terrain.store_sheet_count(tuple(view["box_m"]), EPSG)
|
||||
|
||||
|
||||
def test_sheet_limit_counts_store_sheets(fake) -> None:
|
||||
"""613-1 — 상한 = 공용 창고 도엽 수(칸 수 아님) · 36 장까지 · 넘으면 알림 글에 장 수."""
|
||||
root, _ = fake
|
||||
small = terrain.plan_extent(root, [A, B], EPSG)
|
||||
assert 1 <= small["store_sheets"] <= 4 and small["cells"] < 2_000_000
|
||||
# 남쪽 1.5 km 늘림 — 옛 칸 상한(500 만 칸)은 넘지만 도엽은 상한 안
|
||||
south = (A[0], A[1] - 6000.0)
|
||||
wide = terrain.plan_extent(root, [A, B, south], EPSG)
|
||||
assert wide["store_sheets"] <= terrain.MAX_SHEETS
|
||||
# 37 장 이상 — 약 6 도엽 폭 넘게 벌림
|
||||
huge = terrain.plan_extent(root, [A, (A[0] + 20000.0, A[1] + 22000.0)], EPSG)
|
||||
assert huge["store_sheets"] > terrain.MAX_SHEETS
|
||||
text = terrain.sheet_limit_message(huge["store_sheets"])
|
||||
assert (
|
||||
f"도엽 {huge['store_sheets']}장" in text and "36장까지" in text and "새로 받으세요" in text
|
||||
)
|
||||
|
||||
|
||||
def test_extent_no_download_and_covered(fake) -> None:
|
||||
|
||||
@@ -227,13 +227,16 @@ def test_center_other_band_asks_then_moves(posting) -> None:
|
||||
assert got["epsg"] == 5187 and got["covered"] is True
|
||||
|
||||
|
||||
def test_cells_limit_413(posting) -> None:
|
||||
posting.plan["points"].append(
|
||||
{"x": OX + 6000.0, "y": OY + 6000.0}
|
||||
) # 6.7 × 6.7 km = 1,100 만 칸
|
||||
def test_sheet_limit_413(posting) -> None:
|
||||
"""613-1 — 6.7 × 6.7 km(옛 칸 상한 넘음)는 받아짐 · 도엽 37 장 이상은 413 + 장 수 알림."""
|
||||
posting.plan["points"].append({"x": OX + 6000.0, "y": OY + 6000.0})
|
||||
assert posting.run_post({}).status_code == 200
|
||||
assert len(posting.calls) == 1
|
||||
posting.plan["points"].append({"x": OX + 22000.0, "y": OY + 24000.0})
|
||||
res = posting.run_post({})
|
||||
assert res.status_code == 413 and "상한 500만" in res.json()["detail"]
|
||||
assert posting.calls == []
|
||||
detail = res.json()["detail"]
|
||||
assert res.status_code == 413 and "한 번에 36장까지" in detail and "도엽 " in detail
|
||||
assert len(posting.calls) == 1
|
||||
|
||||
|
||||
def test_progress_interrupted_after_restart(client, monkeypatch) -> None:
|
||||
|
||||
Reference in New Issue
Block a user