- --part A-B/N = 도엽 번호 crc32 % N 몫 · 지형 단계가 0차곡 이웃(목록 밖 · 띠 넘음 포함)까지 몫대로 셈 - 0차곡 = 남의 몫 이웃 지형이 창고에 없으면 미룸 · --defer-wait 초 뒤 다시 - 진행 기록 = _precompute_progress_<컴퓨터 이름>.json · 옛 한 벌은 성공만 읽어 이어 받음 · --status 모든 PC 합침 · 창고 실물 수 - --cpu-cap = 전체 CPU 평균이 넘으면 새 도엽 안 줌 · --e-cores --e-workers = 일꾼 앞선 몇 명만 그 논리 CPU 지정 - 죽은 잠금 치움 = 자기 몫 도엽만 · --root 시험용 창고 자리 - test_612: 지형 단계 이웃 일감 덧 · 기록 파일 이름 바뀜에 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
5.1 KiB
Python
126 lines
5.1 KiB
Python
"""PLAN 612-1 — 전국 미리 계산 일괄 도구(`tools/precompute_terrain.py`) 지킴 시험.
|
|
|
|
도엽 목록 칸 모음 · 고름 · 산지 차례 · 좌표계 띠 · 이어 하기(성공 건너뜀 · 실패 다시) ·
|
|
죽은 잠금 치움 — 셈 함수는 가짜로 바꿔 서버 · 전국 파일 없이 돎.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
import shapely
|
|
from pyproj import Transformer
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B04_PreProcess.B04_PreProcess_Engine_MapSheet import latlon_to_sheet5k # noqa: E402
|
|
from tools import precompute_terrain as tool # noqa: E402
|
|
|
|
|
|
def _line_5179(lon_lat: list[tuple[float, float]]) -> bytes:
|
|
to = Transformer.from_crs("EPSG:4326", "EPSG:5179", always_xy=True)
|
|
xs, ys = to.transform(*zip(*lon_lat))
|
|
return shapely.to_wkb(shapely.LineString(list(zip(xs, ys))))
|
|
|
|
|
|
def test_aggregate_cells_length_and_relief() -> None:
|
|
# 도엽 하나 안 두 줄(높이 100 · 300) + 다른 도엽 한 줄
|
|
a = [(129.0010 + 0.0001 * i, 36.9901) for i in range(200)] # 약 1.8 km
|
|
b = [(129.0010 + 0.0001 * i, 36.9951) for i in range(100)]
|
|
c = [(129.1001 + 0.0001 * i, 36.9901) for i in range(50)]
|
|
cells = tool.aggregate([_line_5179(a), _line_5179(b), _line_5179(c)], np.array([100, 300, 50]))
|
|
assert len(cells) == 2
|
|
big = max(cells.values(), key=lambda v: v[0])
|
|
assert big[1:] == [100.0, 300.0]
|
|
assert 2_300 < big[0] < 2_800 # 1.78 + 0.89 km — 솎음 현이라 조금 짧음
|
|
|
|
|
|
def test_pick_sheets_prefix_box_relief_order() -> None:
|
|
index = {
|
|
"36901001": [10.0, 100.0, 400.0],
|
|
"36901002": [30.0, 100.0, 200.0],
|
|
"36901003": [30.0, 100.0, 500.0],
|
|
"36712089": [50.0, 0.0, 20.0],
|
|
}
|
|
assert tool.pick_sheets(index) == ["36901003", "36901001"] # 높이 차 150 m 이상 · 길이 순
|
|
# 앞자리 고름은 높이 차 거름 안 씀 · 길이 같으면 높이 차 큰 것 먼저
|
|
assert tool.pick_sheets(index, sheets=["36901"]) == ["36901003", "36901002", "36901001"]
|
|
assert tool.pick_sheets(index, sheets=["36901"], limit=1) == ["36901003"]
|
|
assert tool.pick_sheets(index, sheets=["99999999"]) == [] # 전국 파일 밖 = 안 고름
|
|
assert tool.pick_sheets(index, min_relief=0, box=(127.5, 36.0, 128.0, 36.5)) == ["36712089"]
|
|
|
|
|
|
def test_epsg_band_follows_longitude() -> None:
|
|
assert tool.epsg_of(latlon_to_sheet5k(37.5, 125.5)) == 5185
|
|
assert tool.epsg_of(latlon_to_sheet5k(37.5, 127.5)) == 5186
|
|
assert tool.epsg_of("36901074") == 5187
|
|
|
|
|
|
@pytest.fixture()
|
|
def fake_store(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(tool.store, "TERRAIN_ROOT", tmp_path)
|
|
monkeypatch.setattr(tool.store, "_gpkg_stamp", lambda: "stamp-1")
|
|
calls: list[tuple[str, str]] = []
|
|
fail = {"36901002"}
|
|
|
|
def fake_run(step: str, no: str, part=None):
|
|
calls.append((step, no))
|
|
if no in fail:
|
|
return step, no, {"ok": False, "error": "RuntimeError: 받기 실패"}
|
|
return step, no, {"ok": True, "s": 0.01, "fresh": True, "empty": False}
|
|
|
|
monkeypatch.setattr(tool, "run_step", fake_run)
|
|
return tmp_path, calls, fail
|
|
|
|
|
|
def test_resume_skips_done_and_retries_failed(fake_store) -> None:
|
|
root, calls, fail = fake_store
|
|
sheets = ["36901001", "36901002", "36901003"]
|
|
progress = tool.load_progress()
|
|
tool.run(sheets, ["e07", "dem"], 1, progress, say=lambda _: None)
|
|
# 차례 고정 · 지형 단계는 0차곡 이웃(3x3)까지 셈(618-1)
|
|
assert [s for s, n in calls if n in sheets] == ["dem"] * 3 + ["e07"] * 3
|
|
saved = json.loads(tool.progress_path().read_text(encoding="utf-8"))
|
|
assert saved["steps"]["dem"]["36901001"]["ok"] is True
|
|
assert saved["steps"]["dem"]["36901002"]["ok"] is False
|
|
|
|
calls.clear()
|
|
fail.clear()
|
|
tool.run(sheets, ["dem", "e07"], 1, tool.load_progress(), say=lambda _: None)
|
|
assert calls == [("dem", "36901002"), ("e07", "36901002")] # 성공은 건너뜀 · 실패만 다시
|
|
assert tool.summary(sheets, tool.load_progress())["dem"]["ok"] == 3
|
|
|
|
|
|
def test_progress_restarts_when_source_changes(fake_store, monkeypatch) -> None:
|
|
root, calls, _ = fake_store
|
|
tool.run(["36901001"], ["dem"], 1, tool.load_progress(), say=lambda _: None)
|
|
monkeypatch.setattr(tool.store, "_gpkg_stamp", lambda: "stamp-2")
|
|
assert tool.load_progress()["steps"]["dem"] == {}
|
|
|
|
|
|
def test_clean_dead_locks_and_old_tmp(tmp_path) -> None:
|
|
folder = tmp_path / "36901001"
|
|
folder.mkdir()
|
|
dead = folder / ".dem_5187_2m.lock"
|
|
dead.write_text("999999999 0", encoding="utf-8")
|
|
live = folder / ".zero_acc_5187_2m.lock"
|
|
live.write_text(f"{os.getpid()} 0", encoding="utf-8")
|
|
old_tmp = folder / ".dem_5187_2m.npz.abc.tmp"
|
|
old_tmp.write_bytes(b"x")
|
|
stale = time.time() - tool.store.LOCK_STALE_S - 10
|
|
os.utime(old_tmp, (stale, stale))
|
|
new_tmp = folder / ".e07_5187_2m.npz.def.tmp"
|
|
new_tmp.write_bytes(b"x")
|
|
kept = folder / "dem_5187_2m.npz"
|
|
kept.write_bytes(b"x")
|
|
assert tool.clean_dead(tmp_path) == 2
|
|
assert not dead.exists() and not old_tmp.exists()
|
|
assert live.exists() and new_tmp.exists() and kept.exists()
|