feat(D 예정노선): 도엽 조각 — 공용 창고 storage/_shared/sheets · 보이는 도엽만 받기 (PLAN 606-1 · 606-2 · 요구 477)

- 새 D03_PlanAnalysis_SheetTiles.py — 도엽 도곽으로 자른 선 겹 조각(등고 · 계곡 = 전국 등고선 · 세류 · 물 면 · 도로 · 건물 · 지명 = 도엽 zip) · zip 은 프로젝트 · 공용 지형 창고에서 복사(원본 안 지움) 뒤 받기 · 빈 도엽 = 빈 조각 · 받기 실패 저장 안 함 · B04 함수 부르기만
- 라우터 길 둘 — 보이는 도엽 목록(bbox) · 조각 하나
- 새 UI_SheetTiles.ts — 보기 멈춘 뒤 보이는 도엽 · 없는 조각만 하나씩 · 12 장 이하 다 · 13~30 계곡선만 · 넘음 숨김 · 서클
- UI_Layers — 도엽 선 겹 원천을 조각으로 · 시험 606 새 · 475 옛 겹 길 단언 바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 22:14:11 +09:00
co-authored by Claude Opus 5.5
parent a3f4576802
commit bc1d5165ff
7 changed files with 646 additions and 17 deletions
@@ -96,9 +96,9 @@ def test_layers_turn_drops_old_answers() -> None:
assert (
"pending.get(slot) === job && pending.delete(slot)" in layers
) # 옛 일이 새 기다림을 안 지움
# 등고 · 세류 = 예정노선 겹 길(서버가 받은 범위로 자름) · 화면 상자 · 보관함 지문 걷음
assert 'fetchPlanSheetLayer(projectId, "등고선")' in layers
assert 'fetchPlanSheetLayer(projectId, "하천중심선")' in layers
# 등고 · 세류 = 보이는 도엽 조각(606) · 옛 화면 상자 · 보관함 지문 걷음
assert 'tiles.collection("등고선")' in layers
assert 'tiles.collection("하천중심선")' in layers
for gone in ("/geojson?layer=", "CONTOUR_PAD_M", "haveTag", "opts.have"):
assert gone not in layers, gone
+121
View File
@@ -0,0 +1,121 @@
"""도엽 조각(PLAN 606) — 보이는 도엽 수 · 차례 · 30 장 넘음 = 목록 없음 · 조각 = 도곽 안 ·
처음만 만듦 · 계곡선 = 등고선 중 25 m 배수 · 빈 도엽 = 빈 조각 · 받기 실패는 저장 안 함 ·
화면 = 보이는 도엽만(12 · 30 문턱) · 옛 받은 범위 겹 길 안 씀.
./venv/Scripts/python.exe -m pytest resources/tester/test_606_sheet_tiles.py -q
"""
import json
from pathlib import Path
import pytest
from B04_PreProcess.B04_PreProcess_Engine_MapSheet import sheet5k_center, sheet5k_to_bounds
from D03_PlanAnalysis import D03_PlanAnalysis_SheetTiles as st
ROOT = Path(__file__).resolve().parents[2]
SHEET = "36901074"
def _inside(collection, rect, eps=1e-6):
lon0, lat0, lon1, lat1 = rect
def walk(c):
if isinstance(c[0], (int, float)):
assert lon0 - eps <= c[0] <= lon1 + eps and lat0 - eps <= c[1] <= lat1 + eps
else:
for part in c:
walk(part)
for feature in collection["features"]:
walk(feature["geometry"]["coordinates"])
def test_sheets_in_box_count_order_cap() -> None:
lat, lon = sheet5k_center(SHEET)
assert st.sheets_in_box((lon - 0.001, lat - 0.001, lon + 0.001, lat + 0.001)) == (1, [SHEET])
count, sheets = st.sheets_in_box((lon - 0.02, lat - 0.02, lon + 0.02, lat + 0.02))
assert count == len(sheets) == 9 and sheets[0] == SHEET # 가운데부터
count, sheets = st.sheets_in_box((lon - 0.1, lat - 0.1, lon + 0.1, lat + 0.1))
assert count > st.INDEX_MAX and sheets == []
@pytest.fixture()
def shelf(tmp_path, monkeypatch):
found = sorted(ROOT.glob(f"storage/*/*/*/B04_PreProcess/processed/map_sheets/{SHEET}.zip"))
found += sorted((ROOT / "storage" / "_shared" / "sheets" / SHEET).glob("*.zip"))
if not found:
pytest.skip("도엽 zip 없음")
monkeypatch.setattr(st, "SHEETS_ROOT", tmp_path)
monkeypatch.setattr(st, "_project_zips", lambda no: found[:1] if no == SHEET else [])
return tmp_path
def test_zip_tile_clipped_and_made_once(shelf) -> None:
path = st.ensure_tile(SHEET, "하천중심선")
collection = json.loads(path.read_text(encoding="utf-8"))
assert collection["features"]
_inside(collection, sheet5k_to_bounds(SHEET))
stamp = path.stat().st_mtime_ns
assert st.ensure_tile(SHEET, "하천중심선") == path and path.stat().st_mtime_ns == stamp
assert (shelf / SHEET / f"{SHEET}.zip").is_file() # 옮겨 담음(원본은 그대로)
def test_contour_and_index(shelf) -> None:
full = json.loads(st.ensure_tile(SHEET, st.CONTOUR).read_text(encoding="utf-8"))
index = json.loads(st.ensure_tile(SHEET, st.INDEX).read_text(encoding="utf-8"))
levels = [f["properties"]["등고수치"] for f in full["features"]]
assert levels and all(isinstance(v, (int, float)) for v in levels)
assert index["features"] and all(
f["properties"]["등고수치"] % 25 == 0 for f in index["features"]
)
assert len(index["features"]) == sum(1 for v in levels if v % 25 == 0)
_inside(full, sheet5k_to_bounds(SHEET))
def test_bad_name_rejected(shelf) -> None:
with pytest.raises(KeyError):
st.ensure_tile(SHEET, "../x")
with pytest.raises(KeyError):
st.ensure_tile("1234", "건물")
def _no_download(monkeypatch, reason):
import B04_PreProcess.B04_PreProcess_Engine_SheetStore as sheet_store
monkeypatch.setattr(
sheet_store,
"ensure_sheets",
lambda folder, nos: {"available": {}, "failed": {nos[0]: reason}},
)
def test_empty_sheet_gives_empty_tile(tmp_path, monkeypatch) -> None:
monkeypatch.setattr(st, "SHEETS_ROOT", tmp_path)
monkeypatch.setattr(st, "_project_zips", lambda no: [])
_no_download(monkeypatch, "LookupError: 검색 결과 없음")
path = st.ensure_tile("37507039", "건물")
assert json.loads(path.read_text(encoding="utf-8"))["features"] == []
def test_download_failure_not_saved(tmp_path, monkeypatch) -> None:
monkeypatch.setattr(st, "SHEETS_ROOT", tmp_path)
monkeypatch.setattr(st, "_project_zips", lambda no: [])
_no_download(monkeypatch, "PermissionError: 로그인")
with pytest.raises(RuntimeError):
st.ensure_tile("37507039", "건물")
assert not list(tmp_path.rglob("*.geojson"))
def _src(name: str) -> str:
return (ROOT / "D03_PlanAnalysis" / name).read_text(encoding="utf-8")
def test_screen_uses_visible_sheets() -> None:
tiles = _src("D03_PlanAnalysis_UI_SheetTiles.ts")
assert "export const FULL_MAX = 12;" in tiles and "export const INDEX_MAX = 30;" in tiles
assert '"등고선" ? "계곡선" : null' in tiles # 13~30 = 계곡선만
layers = _src("D03_PlanAnalysis_UI_Layers.ts")
assert "fetchPlanSheetLayer" not in layers
assert "tiles.view(m, meta)" in layers
assert len(layers.splitlines()) <= 700