Files
Aislo/resources/tester/test_509_1_sheet_clip_route.py
T

158 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
"""509-1 평면도(B05 노선 편집)용 도엽 — 노선 둘레 띠로 잘라 둠(서버 · 파생 저장본).
- 범위 = 저장 노선 bbox + 띠 300 + 여유 300 m(50 m 눈금 바깥 맞춤) · `clip_lonlat` 붙음
- 안에 다 든 가닥 그대로 · 밖 가닥 빠짐 · 걸친 가닥은 범위 안 꼭짓점만
- 같은 입력 = 같은 저장본(다시 안 셈) · 사본이 바뀌면 다시 셈
- 노선 없음 · 모르는 범위 = 사본 그대로
- 원본 · 사본 파일은 안 바뀜 · 저장본은 `_derived/sheet_route/` 에만
- 화면 쪽 `sheetCoversBand` — 띠가 범위 밖이면 통째 사본을 받음 · 상수 짝(띠 300 · 줌아웃 2 배)
임시 폴더에 사업지(1 m = 0.00001 도)를 지어 시험 — 공유 storage · DB 안 씀.
"""
import json
import os
import re
import subprocess
from pathlib import Path
import pytest
from B04_PreProcess import B04_PreProcess_Engine_SheetClip as clip
from common_util.common_util_route_geometry import planned_route_working_path, write_route_csv
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
META = {
"x_min": 0,
"y_min": 0,
"width_meters": 10000,
"height_meters": 10000,
"lon_min": 127.0,
"lon_max": 127.1,
"lat_min": 37.0,
"lat_max": 37.1,
}
def lonlat(x: float, y: float) -> list[float]:
return [127.0 + x * 1e-5, 37.0 + y * 1e-5]
def line_feature(points: list[tuple[float, float]], level: int) -> dict:
return {
"type": "Feature",
"properties": {"등고수치": level},
"geometry": {"type": "LineString", "coordinates": [lonlat(x, y) for x, y in points]},
}
@pytest.fixture()
def project(tmp_path: Path) -> tuple[Path, Path]:
processed = tmp_path / "B04_PreProcess" / "processed"
processed.mkdir(parents=True)
(processed / "vworld_satellite_meta.json").write_text(json.dumps(META), encoding="utf-8")
write_route_csv(
planned_route_working_path(tmp_path), [{"x": 5000, "y": 5000}, {"x": 5500, "y": 5200}]
)
source = processed / "도엽_등고선.display.geojson"
source.write_text(
json.dumps(
{
"type": "FeatureCollection",
"features": [
line_feature([(5100, 5100), (5200, 5150), (5300, 5100)], 100), # 안
line_feature([(9000, 9000), (9500, 9100)], 105), # 밖
line_feature([(x, 5100) for x in range(0, 10001, 100)], 110), # 걸침
],
}
),
encoding="utf-8",
)
return tmp_path, source
def test_범위는_노선_bbox_더하기_600m(project):
root, _ = project
assert clip.clip_rect_m(root, "route", META) == (4400, 4400, 6100, 5800)
assert clip.ROUTE_BAND_M + clip.ROUTE_SPARE_M == 600
def test_자르기(project):
root, source = project
before = source.read_bytes()
out = clip.clipped_sheet(root, source, "route")
assert out.parent == root / "_derived" / "sheet_route"
data = json.loads(out.read_text(encoding="utf-8"))
assert data["clip_lonlat"] == pytest.approx([127.044, 37.044, 127.061, 37.058])
levels = [f["properties"]["등고수치"] for f in data["features"]]
assert levels == [100, 110]
inside = data["features"][0]["geometry"]
assert inside["coordinates"] == [lonlat(5100, 5100), lonlat(5200, 5150), lonlat(5300, 5100)]
crossing = data["features"][1]["geometry"]["coordinates"]
assert 15 <= len(crossing) <= 20 # 101 꼭짓점 → 범위 안(4400~6100)만 + 두 끝
assert all(127.044 - 1e-9 <= lon <= 127.061 + 1e-9 for lon, _ in crossing)
assert source.read_bytes() == before
def test_같은_입력은_다시_안_셈(project):
root, source = project
first = clip.clipped_sheet(root, source, "route")
stamp = first.stat().st_mtime_ns
again = clip.clipped_sheet(root, source, "route")
assert again == first and again.stat().st_mtime_ns == stamp
def test_사본이_바뀌면_다시_셈(project):
root, source = project
first = clip.clipped_sheet(root, source, "route")
data = json.loads(source.read_text(encoding="utf-8"))
data["features"] = data["features"][:1]
source.write_text(json.dumps(data), encoding="utf-8")
os.utime(source, ns=(source.stat().st_atime_ns, source.stat().st_mtime_ns + 10**9))
second = clip.clipped_sheet(root, source, "route")
assert second != first
assert len(json.loads(second.read_text(encoding="utf-8"))["features"]) == 1
def test_노선을_조금_옮기면_같은_열쇠(project):
root, source = project
first = clip.clipped_sheet(root, source, "route")
write_route_csv(
planned_route_working_path(root), [{"x": 5010, "y": 5010}, {"x": 5490, "y": 5190}]
)
assert clip.clipped_sheet(root, source, "route") == first
def test_범위를_못_정하면_사본_그대로(project):
root, source = project
assert clip.clipped_sheet(root, source, "nope") == source
planned_route_working_path(root).unlink()
assert clip.clipped_sheet(root, source, "route") == source
assert not (root / "_derived" / "sheet_route").exists()
def test_화면_띠_덮음_판정():
proc = subprocess.run(
["node", str(HERE / "helper_509_1_sheet_covers.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
r = json.loads(proc.stdout)
assert r["saved"] and r["within"]
assert not r["beyondEast"] and not r["beyondSouth"]
assert r["noClip"] and r["noSheet"] and r["noLine"]
def test_상수_짝():
render = (ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Render.ts").read_text("utf-8")
zoom = (ROOT / "B05_Profile" / "B05_Profile_UI_Drainage_ZoomRange.ts").read_text("utf-8")
band = re.search(r"export const CONTOUR_BAND_M = (\d+)", render)
factor = re.search(r"export const ZOOM_OUT_ROUTE_FACTOR = (\d+)", zoom)
assert band and float(band.group(1)) == clip.ROUTE_BAND_M
assert factor and float(factor.group(1)) == clip.ZOOM_OUT_ROUTE_FACTOR