158 lines
5.9 KiB
Python
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
|