fix(B04): 도엽 서피스를 라이다와 같은 원점에 놓고, 유역 저장본에 좌표계를 남긴다
① 3D 서피스 겹쳐보기 어긋남 — `write_glb()`는 넘겨받은 상자의 중심을 원점으로 삼는데 모델마다 자기 상자를 넘겨 도엽 서피스와 라이다 지표면이 다른 원점에 섰다 (2f940d8a 실측: 수평 7.85m·높이 1.28m). 도엽 서피스가 라이다 포인트 상자 (`structured.npz`, 화면 `setReferenceBounds`와 같은 값)를 화면 원점으로 쓰게 했다. 표고 격자 상자(`bounds`)는 절취 범위 그대로 두고 `scene_bounds`를 npz에 따로 남긴다 — 등고선 API도 이 값을 먼저 써서 등고선이 메시 위에 얹힌다. 라이다 없는 사업지는 기준이 자기뿐이라 종전대로 자기 상자를 쓴다. ② 세부유역 저장본 좌표계 — `04_detailed_basins.geojson`에 변환에 쓴 좌표계를 `crs_input`으로 남기고, B07 유역도가 그 값으로 되돌린다. 기록이 없는 옛 저장본은 노선 CSV의 EPSG 라벨로 쓰였으므로 그 라벨로 되돌린다(경고 로그 + 재확정 안내). 검증: tmp/tests 42 passed (신규 test_scene_origin_and_basin_crs.py 5건). 패치 코드로 도엽 서피스를 다시 만들어 GLB 정점 상자를 대조 — sheet [-426.58, -68.95, -386.41]~[411.42, 71.52, 379.59], csf [-172.12, -44.06, -199.69]~[184.11, 3.22, 165.72] 로 같은 원점. 수정 전 sheet는 [-419.5, -70.24, -383.0]~[418.5, 70.24, 383.0] (자기 중심)였다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,28 @@ def _load_features_metric(
|
||||
return converted
|
||||
|
||||
|
||||
def _scene_bounds(project_root: Path, bounds: np.ndarray) -> np.ndarray:
|
||||
"""프리뷰 메시를 놓을 **화면 원점 기준 상자**.
|
||||
|
||||
`write_glb()`는 넘겨받은 상자의 중심을 원점으로 삼아 정점을 옮긴다. 모델마다 자기
|
||||
범위를 주면 도엽 서피스와 라이다 지표면이 서로 다른 원점에 서서, 겹쳐 보기·계획선이
|
||||
어긋난다(2026-09-01 실측: 수평 7.85m·높이 1.28m). 그래서 라이다 포인트 상자가 있으면
|
||||
그 상자를 같이 쓴다 — 화면이 기준으로 삼는 상자(`setReferenceBounds`)와 같은 값이다.
|
||||
라이다가 없는 사업지(도엽만)는 기준이 이 서피스뿐이라 자기 상자를 그대로 쓴다.
|
||||
"""
|
||||
structured = project_root / "B04_PreProcess" / "processed" / "structured.npz"
|
||||
if not structured.is_file():
|
||||
return bounds
|
||||
try:
|
||||
with np.load(structured) as data:
|
||||
if "bounds" not in data:
|
||||
return bounds
|
||||
return np.asarray(data["bounds"], dtype=float)
|
||||
except (OSError, ValueError) as exc:
|
||||
logger.warning("도엽 서피스: 라이다 상자를 읽지 못해 자기 범위로 놓습니다 — %s", exc)
|
||||
return bounds
|
||||
|
||||
|
||||
def _preview_mesh(
|
||||
x: np.ndarray, y: np.ndarray, z: np.ndarray, valid: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
@@ -125,6 +147,7 @@ def _write_smoothed(
|
||||
z: np.ndarray,
|
||||
valid: np.ndarray,
|
||||
bounds: np.ndarray,
|
||||
scene: np.ndarray,
|
||||
) -> None:
|
||||
"""`{stem}_smooth.npz`·`_smooth_preview.glb`를 만든다 — LAS DTM 스무딩과 같은 절차.
|
||||
|
||||
@@ -180,10 +203,11 @@ def _write_smoothed(
|
||||
z=sz,
|
||||
valid_mask=svalid,
|
||||
bounds=bounds,
|
||||
scene_bounds=scene,
|
||||
resolution=np.array([step], np.float32),
|
||||
)
|
||||
vertices, faces = _preview_mesh(sx, sy, np.nan_to_num(sz, nan=float(bounds[2, 0])), svalid)
|
||||
write_glb(models_dir / f"{stem}_smooth_preview.glb", vertices, faces, bounds)
|
||||
write_glb(models_dir / f"{stem}_smooth_preview.glb", vertices, faces, scene)
|
||||
|
||||
|
||||
def _rasterize_contour_levels(spec: Any, features: list[dict[str, Any]]) -> np.ndarray:
|
||||
@@ -339,6 +363,8 @@ def _write_method_model(
|
||||
[float(finite_z.min()), float(finite_z.max())],
|
||||
]
|
||||
)
|
||||
# 표고 격자 상자(`bounds`)는 절취 범위 그대로 두고, 화면 원점만 라이다와 맞춘다.
|
||||
scene = _scene_bounds(project_root, bounds)
|
||||
atomic_npz(
|
||||
model_path,
|
||||
x=x_coords,
|
||||
@@ -346,11 +372,12 @@ def _write_method_model(
|
||||
z=z_grid,
|
||||
valid_mask=valid_grid,
|
||||
bounds=bounds,
|
||||
scene_bounds=scene,
|
||||
resolution=np.array([SHEET_SURFACE_GRID_M], np.float32),
|
||||
)
|
||||
vertices, faces = _preview_mesh(x_coords, y_coords, z_grid, valid_grid)
|
||||
write_glb(preview_path, vertices, faces, bounds)
|
||||
_write_smoothed(models_dir, stem, x_coords, y_coords, z_grid, valid_grid, bounds)
|
||||
write_glb(preview_path, vertices, faces, scene)
|
||||
_write_smoothed(models_dir, stem, x_coords, y_coords, z_grid, valid_grid, bounds, scene)
|
||||
return {
|
||||
"model_type": "dtm",
|
||||
"source_filter": source_filter,
|
||||
|
||||
Reference in New Issue
Block a user