feat(561-2): D 예정노선 지형 격자 2 m · B 공용 1 m 그대로 (grid_m 인자)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -344,6 +344,7 @@ def _write_method_model(
|
|||||||
spec: Any,
|
spec: Any,
|
||||||
surface: np.ndarray,
|
surface: np.ndarray,
|
||||||
method_key: str,
|
method_key: str,
|
||||||
|
grid_m: float = SHEET_SURFACE_GRID_M,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
"""방식 하나의 표고 격자를 npz·프리뷰 glb로 저장하고 등록용 dict를 만든다."""
|
"""방식 하나의 표고 격자를 npz·프리뷰 glb로 저장하고 등록용 dict를 만든다."""
|
||||||
# DtmGridSampler 규약에 맞춰 y 오름차순으로 뒤집어 저장한다.
|
# DtmGridSampler 규약에 맞춰 y 오름차순으로 뒤집어 저장한다.
|
||||||
@@ -379,15 +380,20 @@ def _write_method_model(
|
|||||||
valid_mask=valid_grid,
|
valid_mask=valid_grid,
|
||||||
bounds=bounds,
|
bounds=bounds,
|
||||||
scene_bounds=scene,
|
scene_bounds=scene,
|
||||||
resolution=np.array([SHEET_SURFACE_GRID_M], np.float32),
|
resolution=np.array([grid_m], np.float32),
|
||||||
)
|
)
|
||||||
vertices, faces = _preview_mesh(x_coords, y_coords, z_grid, valid_grid)
|
vertices, faces = _preview_mesh(x_coords, y_coords, z_grid, valid_grid)
|
||||||
write_glb(preview_path, vertices, faces, scene)
|
write_glb(preview_path, vertices, faces, scene)
|
||||||
_write_smoothed(models_dir, stem, x_coords, y_coords, z_grid, valid_grid, bounds, scene)
|
_write_smoothed(models_dir, stem, x_coords, y_coords, z_grid, valid_grid, bounds, scene)
|
||||||
return _method_model_entry(project_root, models_dir, method_key)
|
return _method_model_entry(project_root, models_dir, method_key, grid_m)
|
||||||
|
|
||||||
|
|
||||||
def _method_model_entry(project_root: Path, models_dir: Path, method_key: str) -> dict[str, Any]:
|
def _method_model_entry(
|
||||||
|
project_root: Path,
|
||||||
|
models_dir: Path,
|
||||||
|
method_key: str,
|
||||||
|
grid_m: float = SHEET_SURFACE_GRID_M,
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""방식 하나의 등록용 dict — 저장본 그대로 쓸 때도 같은 모양."""
|
"""방식 하나의 등록용 dict — 저장본 그대로 쓸 때도 같은 모양."""
|
||||||
source_filter = f"{SHEET_SOURCE_FILTER}_{method_key}"
|
source_filter = f"{SHEET_SOURCE_FILTER}_{method_key}"
|
||||||
stem = f"dtm_{source_filter}"
|
stem = f"dtm_{source_filter}"
|
||||||
@@ -398,7 +404,7 @@ def _method_model_entry(project_root: Path, models_dir: Path, method_key: str) -
|
|||||||
"source_filter": source_filter,
|
"source_filter": source_filter,
|
||||||
"representation": "regular_grid",
|
"representation": "regular_grid",
|
||||||
"model_file_path": str(model_path.relative_to(project_root)).replace("\\", "/"),
|
"model_file_path": str(model_path.relative_to(project_root)).replace("\\", "/"),
|
||||||
"resolution_m": SHEET_SURFACE_GRID_M,
|
"resolution_m": grid_m,
|
||||||
"generation_params": {
|
"generation_params": {
|
||||||
"source_filter": source_filter,
|
"source_filter": source_filter,
|
||||||
"representation": "regular_grid",
|
"representation": "regular_grid",
|
||||||
@@ -420,7 +426,12 @@ def _method_model_entry(project_root: Path, models_dir: Path, method_key: str) -
|
|||||||
|
|
||||||
|
|
||||||
def _sheet_key(
|
def _sheet_key(
|
||||||
project_root: Path, processed_dir: Path, route_xy: np.ndarray, crs: str, method_key: str
|
project_root: Path,
|
||||||
|
processed_dir: Path,
|
||||||
|
route_xy: np.ndarray,
|
||||||
|
crs: str,
|
||||||
|
method_key: str,
|
||||||
|
grid_m: float = SHEET_SURFACE_GRID_M,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
"""도엽 3D 면 열쇠 — 등고선 파일 · 노선 · 좌표계 · 격자 · 여유 · 스무딩 · 화면 상자 · 판."""
|
"""도엽 3D 면 열쇠 — 등고선 파일 · 노선 · 좌표계 · 격자 · 여유 · 스무딩 · 화면 상자 · 판."""
|
||||||
contour = processed_dir / _CONTOUR_FILE
|
contour = processed_dir / _CONTOUR_FILE
|
||||||
@@ -435,7 +446,7 @@ def _sheet_key(
|
|||||||
"contour": f"{contour.name}:{stat.st_mtime_ns}:{stat.st_size}",
|
"contour": f"{contour.name}:{stat.st_mtime_ns}:{stat.st_size}",
|
||||||
"route": hashlib.sha1(np.round(route_xy, 3).tobytes()).hexdigest(),
|
"route": hashlib.sha1(np.round(route_xy, 3).tobytes()).hexdigest(),
|
||||||
"crs": str(crs),
|
"crs": str(crs),
|
||||||
"grid_m": SHEET_SURFACE_GRID_M,
|
"grid_m": grid_m,
|
||||||
"margin_m": SHEET_SURFACE_MARGIN_M,
|
"margin_m": SHEET_SURFACE_MARGIN_M,
|
||||||
"smoothing": [
|
"smoothing": [
|
||||||
SURFACE_SMOOTHING_DTM_SIGMA_M,
|
SURFACE_SMOOTHING_DTM_SIGMA_M,
|
||||||
@@ -453,12 +464,16 @@ def _key_path(models_dir: Path, method_key: str) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def _cached_method_model(
|
def _cached_method_model(
|
||||||
project_root: Path, models_dir: Path, method_key: str, key: dict[str, Any] | None
|
project_root: Path,
|
||||||
|
models_dir: Path,
|
||||||
|
method_key: str,
|
||||||
|
key: dict[str, Any] | None,
|
||||||
|
grid_m: float = SHEET_SURFACE_GRID_M,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
"""열쇠가 같고 결과 파일이 있으면 등록용 dict — 아니면 None(다시 셈)."""
|
"""열쇠가 같고 결과 파일이 있으면 등록용 dict — 아니면 None(다시 셈)."""
|
||||||
if key is None:
|
if key is None:
|
||||||
return None
|
return None
|
||||||
entry = _method_model_entry(project_root, models_dir, method_key)
|
entry = _method_model_entry(project_root, models_dir, method_key, grid_m)
|
||||||
if not all((project_root / item).is_file() for item in _entry_files(entry)):
|
if not all((project_root / item).is_file() for item in _entry_files(entry)):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@@ -479,6 +494,7 @@ def build_sheet_surface_model(
|
|||||||
route_xy: np.ndarray,
|
route_xy: np.ndarray,
|
||||||
crs: str,
|
crs: str,
|
||||||
methods: list[str] | None = None,
|
methods: list[str] | None = None,
|
||||||
|
grid_m: float = SHEET_SURFACE_GRID_M,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""도엽등고선으로 방식별 DTM npz·프리뷰 glb를 만들고 등록용 dict 목록을 돌려준다.
|
"""도엽등고선으로 방식별 DTM npz·프리뷰 glb를 만들고 등록용 dict 목록을 돌려준다.
|
||||||
|
|
||||||
@@ -486,14 +502,18 @@ def build_sheet_surface_model(
|
|||||||
화면에서 바꿔 보며 정해야 한다(2026-08-30 사용자 지시). 실패하면 빈 목록 —
|
화면에서 바꿔 보며 정해야 한다(2026-08-30 사용자 지시). 실패하면 빈 목록 —
|
||||||
호출측은 분석을 계속한다(도엽 미확보 지역 폴백).
|
호출측은 분석을 계속한다(도엽 미확보 지역 폴백).
|
||||||
|
|
||||||
`route_xy`: (N, 2) 노선 정점 XY(사업지 CRS, m), `crs`: 그 좌표계(pyproj 입력 문자열).
|
`route_xy`: (N, 2) 노선 정점 XY(사업지 CRS, m), `crs`: 그 좌표계(pyproj 입력 문자열),
|
||||||
|
`grid_m`: 격자 한 변(m · 처음 값 = B 공용 1 m · D 예정노선은 2 m).
|
||||||
"""
|
"""
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
selected = methods or list(SHEET_SURFACE_METHODS)
|
selected = methods or list(SHEET_SURFACE_METHODS)
|
||||||
# 열쇠가 같은 방식은 저장본 그대로 — 다시 셀 방식만 등고선을 읽고 굽는다.
|
# 열쇠가 같은 방식은 저장본 그대로 — 다시 셀 방식만 등고선을 읽고 굽는다.
|
||||||
keys = {key: _sheet_key(project_root, processed_dir, route_xy, crs, key) for key in selected}
|
keys = {
|
||||||
|
key: _sheet_key(project_root, processed_dir, route_xy, crs, key, grid_m) for key in selected
|
||||||
|
}
|
||||||
cached = {
|
cached = {
|
||||||
key: _cached_method_model(project_root, models_dir, key, keys[key]) for key in selected
|
key: _cached_method_model(project_root, models_dir, key, keys[key], grid_m)
|
||||||
|
for key in selected
|
||||||
}
|
}
|
||||||
pending = [key for key in selected if cached[key] is None]
|
pending = [key for key in selected if cached[key] is None]
|
||||||
if not pending:
|
if not pending:
|
||||||
@@ -507,7 +527,7 @@ def build_sheet_surface_model(
|
|||||||
x_max = float(np.max(route_xy[:, 0])) + SHEET_SURFACE_MARGIN_M
|
x_max = float(np.max(route_xy[:, 0])) + SHEET_SURFACE_MARGIN_M
|
||||||
y_min = float(np.min(route_xy[:, 1])) - SHEET_SURFACE_MARGIN_M
|
y_min = float(np.min(route_xy[:, 1])) - SHEET_SURFACE_MARGIN_M
|
||||||
y_max = float(np.max(route_xy[:, 1])) + SHEET_SURFACE_MARGIN_M
|
y_max = float(np.max(route_xy[:, 1])) + SHEET_SURFACE_MARGIN_M
|
||||||
spec = grid_spec_from_bounds(x_min, y_min, x_max, y_max, SHEET_SURFACE_GRID_M)
|
spec = grid_spec_from_bounds(x_min, y_min, x_max, y_max, grid_m)
|
||||||
|
|
||||||
# ① 2D — 등고 라인을 격자에 굽는다(라인 셀 = 표고, 그 외 NaN).
|
# ① 2D — 등고 라인을 격자에 굽는다(라인 셀 = 표고, 그 외 NaN).
|
||||||
burned = _rasterize_contour_levels(spec, features)
|
burned = _rasterize_contour_levels(spec, features)
|
||||||
@@ -536,7 +556,7 @@ def build_sheet_surface_model(
|
|||||||
continue
|
continue
|
||||||
# 쓰는 도중 끊기면 옛 열쇠가 새 파일 조각을 덮지 않게 먼저 지움.
|
# 쓰는 도중 끊기면 옛 열쇠가 새 파일 조각을 덮지 않게 먼저 지움.
|
||||||
_key_path(models_dir, method_key).unlink(missing_ok=True)
|
_key_path(models_dir, method_key).unlink(missing_ok=True)
|
||||||
model = _write_method_model(project_root, models_dir, spec, surface, method_key)
|
model = _write_method_model(project_root, models_dir, spec, surface, method_key, grid_m)
|
||||||
if model is not None:
|
if model is not None:
|
||||||
built[method_key] = model
|
built[method_key] = model
|
||||||
if keys[method_key] is not None:
|
if keys[method_key] is not None:
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ from B04_PreProcess.B04_PreProcess_Engine_SheetSurface import build_sheet_surfac
|
|||||||
from B04_PreProcess.B04_PreProcess_Engine_VWorld import download_vworld_satellite_map
|
from B04_PreProcess.B04_PreProcess_Engine_VWorld import download_vworld_satellite_map
|
||||||
from common_util.common_util_json import atomic_write_json
|
from common_util.common_util_json import atomic_write_json
|
||||||
from config.config_system_terrain import (
|
from config.config_system_terrain import (
|
||||||
|
PLAN_TERRAIN_GRID_M,
|
||||||
SHEET_SURFACE_DEFAULT_METHOD,
|
SHEET_SURFACE_DEFAULT_METHOD,
|
||||||
SHEET_SURFACE_GRID_M,
|
|
||||||
SHEET_SURFACE_MARGIN_M,
|
SHEET_SURFACE_MARGIN_M,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ def plan_extent(
|
|||||||
"covered": covers(have, box),
|
"covered": covers(have, box),
|
||||||
"sheets": sheets,
|
"sheets": sheets,
|
||||||
# DEM 칸 수 어림 — 받기 시간 · 메모리 가늠
|
# DEM 칸 수 어림 — 받기 시간 · 메모리 가늠
|
||||||
"cells": int((box[2] - box[0]) * (box[3] - box[1]) / SHEET_SURFACE_GRID_M**2),
|
"cells": int((box[2] - box[0]) * (box[3] - box[1]) / PLAN_TERRAIN_GRID_M**2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -270,6 +270,7 @@ def ensure_plan_terrain(
|
|||||||
outline,
|
outline,
|
||||||
f"EPSG:{int(epsg)}",
|
f"EPSG:{int(epsg)}",
|
||||||
[SHEET_SURFACE_DEFAULT_METHOD],
|
[SHEET_SURFACE_DEFAULT_METHOD],
|
||||||
|
PLAN_TERRAIN_GRID_M,
|
||||||
)
|
)
|
||||||
if not models:
|
if not models:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ SURFACE_CONTOUR_GRID_RESOLUTION_M = float(os.getenv("SURFACE_CONTOUR_GRID_RESOLU
|
|||||||
SHEET_SURFACE_MARGIN_M = float(os.getenv("SHEET_SURFACE_MARGIN_M", "300.0"))
|
SHEET_SURFACE_MARGIN_M = float(os.getenv("SHEET_SURFACE_MARGIN_M", "300.0"))
|
||||||
# 도엽등고선 DTM 격자 한 변(m). LAS DTM·등고선 캐시와 같은 1m(사용자 확정값).
|
# 도엽등고선 DTM 격자 한 변(m). LAS DTM·등고선 캐시와 같은 1m(사용자 확정값).
|
||||||
SHEET_SURFACE_GRID_M = float(os.getenv("SHEET_SURFACE_GRID_M", "1.0"))
|
SHEET_SURFACE_GRID_M = float(os.getenv("SHEET_SURFACE_GRID_M", "1.0"))
|
||||||
|
# D 예정노선 지형 격자(m) — 길이 · 원지반 기울기를 대충 보는 계획 단계라 2m(B 는 위 1m 그대로).
|
||||||
|
PLAN_TERRAIN_GRID_M = float(os.getenv("PLAN_TERRAIN_GRID_M", "2.0"))
|
||||||
# 만들어 둘 등고선 보간 방식 — B04 화면에서 버튼으로 바꿔 가며 비교한다
|
# 만들어 둘 등고선 보간 방식 — B04 화면에서 버튼으로 바꿔 가며 비교한다
|
||||||
# (2026-08-30 사용자 지시). 정의는 B04_PreProcess_Engine_SheetMethods.py.
|
# (2026-08-30 사용자 지시). 정의는 B04_PreProcess_Engine_SheetMethods.py.
|
||||||
SHEET_SURFACE_METHODS = [
|
SHEET_SURFACE_METHODS = [
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ def fake(tmp_path, monkeypatch):
|
|||||||
(output_dir / "vworld_satellite_meta.json").write_text(json.dumps(meta), "utf-8")
|
(output_dir / "vworld_satellite_meta.json").write_text(json.dumps(meta), "utf-8")
|
||||||
return output_dir / "vworld_satellite.png"
|
return output_dir / "vworld_satellite.png"
|
||||||
|
|
||||||
def dem(root, processed, models_dir, route_xy, crs, methods=None):
|
def dem(root, processed, models_dir, route_xy, crs, methods=None, grid_m=None):
|
||||||
seen["dem"].append((models_dir.name, route_xy.tolist(), crs))
|
seen["dem"].append((models_dir.name, route_xy.tolist(), crs))
|
||||||
|
seen["grid_m"] = grid_m
|
||||||
return [{"model_file_path": f"B04_PreProcess/{models_dir.name}/dtm.npz"}]
|
return [{"model_file_path": f"B04_PreProcess/{models_dir.name}/dtm.npz"}]
|
||||||
|
|
||||||
monkeypatch.setattr(store_mod, "download_sheet", download_sheet)
|
monkeypatch.setattr(store_mod, "download_sheet", download_sheet)
|
||||||
@@ -108,9 +109,10 @@ def test_extent_no_download_and_covered(fake) -> None:
|
|||||||
assert seen["download"] == [] and before["have"] is None and before["covered"] is False
|
assert seen["download"] == [] and before["have"] is None and before["covered"] is False
|
||||||
assert before["margin_m"] == terrain.DEFAULT_MARGIN_M and len(before["box"]) == 4
|
assert before["margin_m"] == terrain.DEFAULT_MARGIN_M and len(before["box"]) == 4
|
||||||
x0, y0, x1, y1 = before["box_m"]
|
x0, y0, x1, y1 = before["box_m"]
|
||||||
assert before["cells"] == int((x1 - x0) * (y1 - y0))
|
assert before["cells"] == int((x1 - x0) * (y1 - y0) / 4) # D 지형 격자 2 m
|
||||||
assert all(not s["stored"] and len(s["ring"]) == 4 for s in before["sheets"])
|
assert all(not s["stored"] and len(s["ring"]) == 4 for s in before["sheets"])
|
||||||
terrain.ensure_plan_terrain(root, [A, B], EPSG)
|
terrain.ensure_plan_terrain(root, [A, B], EPSG)
|
||||||
|
assert seen["grid_m"] == 2.0 # B 공용 1 m 가 아니라 D 전용 값
|
||||||
after = terrain.plan_extent(root, [A, B], EPSG)
|
after = terrain.plan_extent(root, [A, B], EPSG)
|
||||||
assert after["covered"] and after["have_m"] == after["box_m"]
|
assert after["covered"] and after["have_m"] == after["box_m"]
|
||||||
assert all(s["stored"] for s in after["sheets"])
|
assert all(s["stored"] for s in after["sheets"])
|
||||||
|
|||||||
Reference in New Issue
Block a user