fix(754-1): B04 등고선 · 격자 외곽 = 지금 지표면 외곽 — 어느 표현을 지어도 낡은 dtm 같이 다시 지음 · 등고선 캐시 판단에 dtm · 굳힌 격자 때

- 지표면 짓기 차례에 dtm 을 늘 맨 앞 — manifest 항목 없음 · 지문 다름이면 같이 다시 지음(화면 [서피스 생성] = 표현 하나만 짓던 길)
- 등고선 캐시 낡음 = 모델 · 같은 필터 dtm · implicit 굳힌 격자 중 하나라도 더 새것(짓기 차례 · 등고선 API 한 함수)
- 등고선 추출이 모델 · dtm 파일을 닫음 — 실패 때 열린 채 남아 Windows 에서 다시 짓기(교체)가 막히던 흠
- ptd 처럼 dtm 없던 필터도 dtm · 굳힌 격자가 생겨 ptd · implicit 확정 길 B05 · B06 열림
- 새 시험 test_754_1_contour_outline.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 23:13:13 +09:00
co-authored by Claude Opus 5.5
parent d87c7b14fd
commit 3f4fd188e8
4 changed files with 150 additions and 15 deletions
@@ -122,6 +122,23 @@ def extract_contours_from_grid(
return contours_geojson_list
def contour_cache_is_stale(contour_path: Path, model_path: Path) -> bool:
"""등고선 캐시가 없거나 그 면을 낳은 파일보다 옛것이면 True(754-1).
낳은 파일 = 모델 npz · 같은 필터 `dtm_{필터}.npz`(외곽 자르기) · implicit 굳힌 격자.
"""
stem = Path(model_path).stem.removesuffix("_smooth")
folder = Path(model_path).parent
sources = [Path(model_path), folder / f"dtm_{stem.split('_', 1)[-1]}.npz"]
if stem.startswith("implicit_"):
sources.append(folder / f"{stem}_grid.npz")
try:
built = Path(contour_path).stat().st_mtime_ns
except OSError:
return True
return any(path.is_file() and path.stat().st_mtime_ns > built for path in sources)
def _load_footprint_mask(
model_npz_path: Path, x_coords: np.ndarray, y_coords: np.ndarray
) -> np.ndarray | None:
@@ -137,10 +154,10 @@ def _load_footprint_mask(
if not dtm_path.exists():
return None
try:
d = np.load(dtm_path)
dtm_x = np.asarray(d["x"]).ravel()
dtm_y = np.asarray(d["y"]).ravel()
dtm_mask = np.asarray(d["valid_mask"], dtype=bool)
with np.load(dtm_path) as d:
dtm_x = np.asarray(d["x"]).ravel()
dtm_y = np.asarray(d["y"]).ravel()
dtm_mask = np.asarray(d["valid_mask"], dtype=bool)
except Exception:
return None
if len(dtm_x) < 2 or len(dtm_y) < 2:
@@ -360,7 +377,21 @@ def extract_contours(
model_npz_path = Path(model_npz_path)
if not model_npz_path.exists():
raise FileNotFoundError(f"모델 파일이 존재하지 않습니다: {model_npz_path}")
data = np.load(model_npz_path)
# 실패해도 모델 파일을 닫음 — 열린 채 남으면 Windows 에서 다시 짓기(교체)가 막힘(754-1)
with np.load(model_npz_path) as data:
return _extract_loaded(
data, model_npz_path, representation, interval, target_grid_m, scene_center
)
def _extract_loaded(
data: Any,
model_npz_path: Path,
representation: str,
interval: float,
target_grid_m: float,
scene_center: tuple[float, float, float] | None,
) -> list[dict[str, Any]]:
if representation == "regular_grid":
x_coords, y_coords, z_grid, valid_mask = (