Files
Aislo/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Lidar.py
T
eomsangdonandClaude Opus 5.5 11c75f022a refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌
- 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음)
- 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음
- 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나
- 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정
- 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400
- 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명
- 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것)
- 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터)
- 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:35:12 +09:00

65 lines
2.8 KiB
Python

"""B07 지표면 음영기복 그림 — 계획평면도 배경 · D03 지형 층이 같이 씀.
이미 만들어 둔 **지표면 격자(DTM)** 로 음영기복 PNG 를 서버에서 만든다 — 점구름을 그대로
그리면 수천만 점이라 느려진다(용화_LAS 실측 4,900만 점). 옛 「계획평면도(라이다)」 짓기는 걷음(608).
"""
import base64
import io
import math
import numpy as np
# 음영기복 광원 — 도면 관행대로 북서(방위각 315°)에서 45° 높이로 비춘다.
_AZIMUTH_DEG = 315.0
_ALTITUDE_DEG = 45.0
# 그림이 지나치게 커지지 않도록 한 변 최대 픽셀 수 (A1 에 인쇄하면 1,200 px 이면 충분하다).
_MAX_PIXELS = 1600
def hillshade_png(z: np.ndarray, valid: np.ndarray, resolution_m: float) -> tuple[str, int, int]:
"""지표면 격자에서 음영기복 PNG(data URL)를 만든다. (data_url, 가로 px, 세로 px).
입력 `z`는 행이 남→북 순서(격자 y 오름차순)다. 그림은 위가 북이어야 하므로 뒤집는다.
빈 칸(`valid`가 False)은 흰색으로 두어 도면에서 배경과 구분되게 한다.
"""
from PIL import Image
grid = np.asarray(z, dtype=np.float64)
mask = np.asarray(valid, dtype=bool)
if grid.ndim != 2 or grid.size == 0:
raise ValueError("지표면 격자가 비어 있습니다.")
# 큰 격자는 미리 솎는다 — A1 한 장에 1,600 px 이상은 눈으로 구분되지 않는다.
rows, columns = grid.shape
stride = max(1, math.ceil(max(rows, columns) / _MAX_PIXELS))
if stride > 1:
grid = grid[::stride, ::stride]
mask = mask[::stride, ::stride]
resolution_m *= stride
filled = np.where(mask, grid, np.nan)
# 빈 칸이 기울기를 망치지 않도록 평균으로 메운 뒤 기울기를 잰다.
mean = float(np.nanmean(filled)) if np.isfinite(filled).any() else 0.0
filled = np.nan_to_num(filled, nan=mean)
dz_dy, dz_dx = np.gradient(filled, max(resolution_m, 1e-6))
slope = np.arctan(np.hypot(dz_dx, dz_dy))
aspect = np.arctan2(-dz_dx, dz_dy)
azimuth = math.radians(360.0 - _AZIMUTH_DEG + 90.0)
altitude = math.radians(_ALTITUDE_DEG)
shade = np.sin(altitude) * np.cos(slope) + np.cos(altitude) * np.sin(slope) * np.cos(
azimuth - aspect
)
shade = np.clip(shade, 0.0, 1.0)
# 배경이므로 완전히 검지 않게 누르되, 능선·계곡이 인쇄에서 보일 만큼은 대비를 준다
# (2026-09-04 실측: 120~255 는 너무 흐렸음).
pixels = (90 + 160 * shade).astype(np.uint8)
pixels[~mask] = 255
image = Image.fromarray(np.flipud(pixels), mode="L")
buffer = io.BytesIO()
image.save(buffer, format="PNG", optimize=True)
data_url = "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
return (data_url, image.width, image.height)