PLAN 0-10 이어짐. 첫 판에서는 창이 모자란 단계를 **격자 전체**로 되돌렸는데, 격자가
커질수록 그 폴백이 25/91 까지 늘어 이득이 대부분 사라졌음(실측 확장 97.7s).
- 모자란 타일만 창을 4배로 넓혀 다시 잼. 단계 전체를 되돌리지 않음.
- 타일 256 → 128 (실측: 128/여유32 2.1s vs 256/여유32 2.7s).
- 넓힘 횟수를 계측 줄에 남김(폴백 대신).
자체검증 — 전체격자 5.2s → 2.1s(넓힘 15/91), 거리·목표행·목표열 전부 동일.
`build_contour_descent` 결과도 옛 코드와 완전 동일(8.0s → 5.1s). 시험 500 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN 0-10. 확장 95.8s 중 밴드별 EDT 가 50.4s 였음 — 단계마다 격자 전체를 훑는데
정작 쓰는 값은 그 단계 밴드 셀뿐임.
- 격자를 256칸 타일로 나눠 **셀이 있는 타일만** 둘레 32칸까지 잘라 EDT 실행.
`analyze_domain` 이 domain 을 안 넘겨 밴드가 전역에 흩어지므로 바운딩박스 하나로는
안 좁아짐 — 타일이라야 좁아짐.
- 창 안 최대 거리가 여유에 닿거나 창에 낮은 라인이 없으면 **그 단계를 전체 격자로 다시**
— 근사가 아니라 같은 값을 싸게 구하는 것. 폴백 횟수는 계측 줄에 찍음.
- `return_indices` 가 주는 창 좌표에 창 원점을 더해 전체 격자 좌표로 되돌림.
자체검증 — 실제 자료(745×1035·166단, EDT 도는 91단)에서 전체격자 10.8s → **2.9s**,
폴백 9/91, 거리·목표행·목표열 **전부 `np.array_equal` 동일**. `build_contour_descent`
결과(band_elevation·valid·receiver·step_length·azimuth·levels)도 옛 코드와 완전 동일.
여유·타일 선택 근거는 파일 주석의 실측표 참조. 시험 500 통과·18 건너뜀
(기존 실패 8건은 다른 환경의 JS 도우미 문제로 무관).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN 0-10. 1차 영역이 2.6초로 떨어진 뒤 남은 병목이 `expand_by_red_boundary` 102초라
그 안을 가르기 위한 계측. 로직 불변.
- 확장 회차별 시간·격자·셀 수·방향장 재사용 여부를 한 줄씩
- 격자 해석 1회: 하강 방향장(재사용/새로) / 지형 조립 / 도로 굽기 / 세류 새김 / 도달 판정
- 하강 방향장: 등고선 굽기 / 밴드 표고(EDT 1회) / 밴드별 하강거리(EDT N회) /
위치에너지 조립 / 흐름 경로
실측 로그상 방향장이 회차마다 새로 만들어지고 밴드가 100단이라, 격자 전체 EDT 가
회차당 100회씩 도는 구조로 보임 — 그 가설을 수치로 가르려는 것.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
보간 구름을 물어볼 자리 둘레 100m 로 좁힌 뒤(9317ad31), 그 둘레에
등고선이 없는 자리는 볼록껍질 밖이라 최근접 정점 값으로 빠짐. 그 값은
등고선 간격(5m)만큼 틀릴 수 있고 그러면 그 조각의 상·하류 판정이 갈림.
용화 자료에서는 안 일어났으나(판정값 일치) 다른 현장에서 조용히 갈릴 수
있어 몇 점이 빠졌는지 한 줄로 남김. 계산은 안 바뀜.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
노선 [확인] 이 293초였고 그중 285초가 배수유역 분석, 다시 그중 256초가
1차 영역 만들기였음. 범인은 ElevationSampler 하나임 —
LinearNDInterpolator 가 들로네 삼각망을 첫 호출 때 만드는데 등고선 구름을
통째로(용화 317,348점) 넘겨 그 한 번이 256~1,481초로 뛰었음(편차 큼).
표고를 묻는 자리는 도로 교차 노드에 닿은 세류 조각 몇 개뿐임. 그 조각들만
먼저 골라 그 둘레 100m 안 등고선만 남기고 삼각망을 만듦. 여유 100m 는
등고선 재샘플 간격(5m)의 스무 배라 물어볼 점은 언제나 볼록껍질 안쪽이고
보간값이 그대로임.
실측(용화 5601e828) — 보간 구름 317,348 → 4,367점,
1차 영역 256.6s → 43.8s(측정 중 기기 부하가 9배였음, 준비 5.1s → 45.7s).
결과 완전 동일 — 1차 영역 266,697㎡ · 격자 545×835 · 268,456셀 ·
노선 이탈 0m/1079m · 상류망 1 / 하류망 109 / 미연결 1153.
pytest 409 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN 0-10. 8000 에서 1회 실측 결과 재확정 체인 292.9s 중 배수유역 분석이 285.0s(97%)로
나와, 그 안쪽을 가르기 위한 마크만 추가. 로직 불변.
- B06 확정: 조회 / 기본설계 계산 / 확정 저장 / 서버 재계산 / 측구 역반영
- 서버 재계산 내부: 상세+DB 병렬 조회 / 포장·세월교 보정 / Node 번들 / 정본 저장
- 배수유역 분석 내부: _prepare(도엽 읽기·좌표변환) / preview_stages / 응답 만들기·저장
- preview_stages: 1차 영역 / 격자 확장·흐름 판정 / 흐름 강도 / 외곽선·기본 관·화살표
`_log_steps` 는 체인 모듈 것을 그대로 씀(지연 임포트로 순환 회피).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DB 가 원격이라 질의 하나가 곧 왕복 12ms 임. 서로 기다릴 이유가 없는 읽기를
한 커넥션에서 순차로 내면 그 왕복이 그대로 더해짐.
- config_db.run_with_connection: 저장소 함수를 자기 커넥션으로 돌려 gather 로
묶을 수 있게 하는 공용 헬퍼. 정의를 한 곳에 둠(drainage_context 의 _query 는
이 함수를 가리키는 이름으로 정리).
머리에 경고 적음 — 순서가 필요한 쓰기는 이걸로 묶으면 트랜잭션이 깨짐.
- B04_PreProcess_Router_Watershed._prepare: 저장경로·좌표계·지표면확정값
세 건을 gather 로. 약 24ms 절약.
common_util_auth_repository.py:318(decide_join_request)은 FOR UPDATE + 순서 있는
UPDATE 라 묶지 않음 — 읽기 블록만 대상.
자체검증 — 관 드래그 왕복 305 -> 259ms(최소 193), 관 11 · 유역 11 로 값 동일.
전체 484 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DB·파일에는 뜻 없는 자릿수가 붙어 있고(1.4000000000000001 꼴) 서버는 응답을
전혀 압축하지 않고 있었음. B05 한 번 진입에 118MB 수신(2026-09-06 실측).
- main.py: 압축 미들웨어(1KB 이상). 3D 예상형상(/corridor)은 예외 —
숫자 배열이라 절반만 줄면서 압축에 493ms 듦(18.6->9.4MB).
- common_util_json: round_floats(value, digits) + LONLAT_DIGITS 7(1.1cm) ·
METRE_DIGITS 6. 저장 파일은 그대로 두고 내려보낼 때만 줄임.
브라우저가 다시 계산에 넣는 값(종횡단 지반선)은 줄이면 안 된다고 머리에 경고 적음.
- 도엽 GeoJSON: 자릿수만 줄인 표시용 사본(.display.geojson)을 한 번 만들어 서빙.
원본이 새로 깔리면 자동 재생성, 실패하면 원본 그대로.
등고선 도엽 65.9->36.8MB, 압축까지 11.3MB.
- 배수유역 응답: 위경도 7자리. 151->100KB, 압축 22KB.
ruff 통과, 테스트 390 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 지형 파일 개수 제한 해제(정확히 1개 → 1장 이상), 한 카드에 여러 장 담기
(화면 표시 「용화_서편.las 외 1장」, 업로드는 한 장씩 차례로 전송)
- 구조화 엔진이 여러 파일을 합친 범위로 한 벌 생성 — WF1 자동 전처리·B04 재분석 모두
프로젝트 지형 파일 전부를 대상으로 실행
- 점이 1억 개를 넘으면 씨닝 — 지면 분류점은 전부 남기고 나머지만 0.5m 칸 최저점으로 축소
(용화 실측: 4,900만점 → 249만점, 지면점 1,140,716개 그대로, 1m 지면격자 표고차 0.0000m)
- 머리글만 읽어 5km 넘게 떨어진 파일은 업로드 거부 (임도는 길어도 2~3km)
- 화면 조립부 700줄 준수를 위해 terrainCoverage 를 판정 모듈로 이동
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 지시(2026-09-04) — 「3D 는 보조라 후순위. 조용히 뒤에서 뜨고 다 되면
팝업으로 알릴 것. 로딩 도넛은 진행률과 무관하게 계속 돌고 숫자만 갱신」,
그리고 「직교/원근 전환 단추는 넣음」.
진입 로딩
- 단계 5 → 4. ④ 종·횡단 자료까지 끝나면 로딩 표시를 걷어 화면을 바로 쓰게 함.
- 3D 지형은 finally 뒤 배경 작업으로 돌리고 끝나면 토스트 「3D 지형 준비 완료」.
실패해도 나머지 화면은 그대로 씀. [초기화]는 재진입이라 같은 규칙이 걸림.
- 「확정 지표면 없음」 판정을 ③ 뒤로 앞당김.
진행 서클(공용)
- 회전 껍데기(`__spin`)를 한 겹 두고 CSS 회전을 거기에만 검. 껍데기는 늘 돌고
안쪽 svg 는 12시 고정이라 호·숫자가 제자리에서 갱신됨. `is-indeterminate` 제거.
직교/원근 전환
- 카메라 두 벌을 `B05_Profile_UI_Viewer_Camera.ts` 가 쥐고 갈아 끼움. 위치·시선·
근평면·먼평면과 보이는 크기를 옮겨 화면이 튀지 않음.
- 마커 입력·구조물 클릭·커서 피벗 유틸이 카메라를 함수로 받게 바꿈(공용 유틸은
값도 그대로 받아 B04 호출부는 무변경).
자체검증(공용 브라우저 5173, 용화_LAS) — 진행률 25→50→75%, 회전 애니메이션
running 유지(변환행렬 표본 전부 다름), 로딩 걷힌 순간 그래프선 2·좌측단추 61·
3D 메쉬 2, 1.8초 뒤 메쉬 489 + 토스트. 초기화 재진입도 같음. 전환 단추는
Ortho ↔ Perspective 왕복 후 값 완전 일치, B04 페이지 오류 0건.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 계획노선 사용 범위: B02 등록에 시작·종료 누가거리 두 칸 추가, B01 수정 모달에서도
변경. projects.route_start_m·route_end_m 신설(015_route_range.sql).
load_design_route 가 범위 절단 → 서피스 트림 순서로 적용. 시작 >= 종료는 화면·서버
양쪽에서 차단. 비우면 전 구간으로 종전과 같음.
- 서피스 절단 여유 기본값 30m → 3m (SURFACE_ROUTE_EDGE_TRIM_M).
- B04 지도·B05 배수유역도 줌 상한을 「화면 폭 20m」 기준으로 계산(고정 8배·16배 폐지).
4배를 넘으면 배경 그림 흐림 보간 해제.
- 계획선 위 측점 눈금·번호 표기(측점번호+잔여거리). 관 마커와 겹치면 반대쪽으로 밀고,
되꺾임 구간에서 라벨이 겹치면 건너뜀. 그리기 코드는 두 화면 공용.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 워크플로 상태 응답에 project_name 추가. 좌측 제목 패널 같은 행 오른쪽 끝에
프로젝트 이름 표기(길면 말줄임, 전체는 툴팁). 이름표를 오버레이 쪽에 두어
레이아웃을 직접 조립하는 B03 까지 여섯 화면이 한 곳으로 반영됨.
- fetchProjectWorkflowState 가 {status, workflow_state} 껍데기를 벗기도록 수정.
- 계획노선을 「업로드한 CSV」로 적은 주석을 「계획노선(정본)」으로 정리.
업로드 판정이 .csv 개수만 세어 shapefile 을 막던 것도 .shp 포함으로 맞춤.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
700줄 분리 때 _Router_Progress · _Router_Status 로 옮겨 진입 파일에서 안 보이게 된
PROGRESS_FILE_RELATIVE · _progress_file_path · read_surface_progress ·
get_wf1_analysis_status · get_surface_model_preview 를 as 재노출로 되살림.
옛 경로를 보던 호출부·테스트가 그대로 동작하게 하려는 것(laptop-main 의 B07 사례와 같은 사고 예방).
검증: 분리 전 모듈 정의 이름을 전수 대조해 누락 0개(_Router 16개 · _Router_Watershed 30개),
라우트 9개 유지, ruff check/format 통과, tmp/tests 183 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B04_PreProcess_Router_Watershed.py 714줄 -> 518줄. 산출물 기록(.npz·스테이지 기하)과
응답 좌표 변환 헬퍼 9개를 B04_PreProcess_Router_Watershed_Output.py(225줄)로 그대로 옮김.
공개 심볼 router · get_primary_region · CONTOUR_FILE · STREAM_FILE 유지
(main.py · B03_FileInput_Service_Chain · B07_DesignDetail_Router_Support 참조).
검증: ruff check 전체 통과·format 적용, tmp/tests 183 passed, 백엔드 재시작 후
경로 2개 유지, /drainage/rainfall 200, /drainage/primary-region 200(schema_version 3),
B05 화면 유역 11개·측점 66개 정상, 옮긴 순수 헬퍼 6개 직접 호출 스모크 통과.
분석 재실행(쓰기 경로)은 작업본 산출물을 덮어써서 돌리지 않음.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B04_PreProcess_Router.py 718줄 -> 493줄.
- B04_PreProcess_Router_Status.py(221줄) — 분석 상태 조회·모델 프리뷰 엔드포인트.
prefix 없이 만들고 본체가 include_router 로 실어 붙임(본체 prefix 를 그대로 상속).
- B04_PreProcess_Router_Progress.py(47줄) — progress.json 읽기·쓰기 헬퍼.
본체와 상태 라우터가 함께 써야 해 순환 임포트를 피하려고 별도 모듈로 둠.
본체는 write_surface_progress 를 계속 재노출(B03_FileInput_Service_WF1 이 참조).
검증: ruff check/format 통과. 라우터 경로 9개가 분리 전과 동일(중복 prefix 없음 확인).
백엔드 재시작(8001) 후 openapi 에 surface 경로 12개 그대로, 브라우저에서
/surface/status 200(진행률 100·모델 3), /surface/models 200,
/surface/models/415/preview 200 model/gltf-binary 7,946,756바이트.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B04_PreProcess_UI_Style.css 870줄 -> 565줄. 하단 2D 지도·도구줄·시트 툴바 규칙을
B04_PreProcess_UI_Style_Map.css(312줄)로 값·순서 그대로 옮김. 진입 TS 에서 본체 다음에
불러 캐스케이드 순서 유지.
검증: 두 파일을 다시 이으면 원본과 **경계 빈 줄 1개 외 완전 동일**(텍스트 대조).
tsc --noEmit 통과. 공용 브라우저(5174) B04 화면 실측 — 좌측 폼 flex/gap 16px,
.b04-map flex column gap 12px(896x828), 헤더·컨트롤 정상, 3D 서피스·2D 지도 렌더 정상.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B05 3D 뷰어에는 방위 표시가 없어 방향을 읽을 수 없었음(사용자 지적, 메인 창 경유).
B04 가 오늘 새로 만든 「지면에 누운 나침반 링」을 그대로 쓰되, 두 페이지가 함께 쓰도록
공용으로 옮김.
- `B04_PreProcess_UI_Compass.ts` → `ui_template/ui_template_compass.ts` (git mv).
`createTerrainCompass({ sizePx, className })` 로 크기·자리를 호출부가 정함.
- 스타일도 `ui_template/ui_template_compass.css` 로 옮기고 모듈이 직접 물고 옴.
**놓을 자리만** 페이지 CSS 몫 — B04 우하단(`b04-surface__compass`),
B05 는 ISO 버튼 아래(`b05-route__compass`).
- B05 뷰어는 104px 로 쓰고 렌더 루프에서 카메라 오프셋으로 갱신, 지형이 없으면 감춤.
좌표 규약이 B04 와 같음(북 −z · 동 +x · 표고 +y, `Viewer.ts:295` 정점 변환) — 그대로 맞음.
화면 검증(5174): B05 나침반 104×104, ISO 버튼 아래 8px. 도북 화살이 뷰 전환에 반응 —
TOP 에서 `M0.00 -33.30`(수직), ISO 에서 `M23.56 -13.60`. 후자는 카메라 오프셋 (d,d,d)
에 대한 투영 이론값(x=R/√2=23.56, y=−R/√6=−13.60)과 일치. B04 쪽도 그대로(84×84,
우하단 16px, 링·화살 색 토큰 유지). `tsc --noEmit` 통과, 뷰어 693줄로 제한 안.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
시안 3종 비교 뒤 사용자 선택(2026-09-03) — TerriaJS·cesium-navigation 계열 표기.
링·눈금·도북 화살을 지면 평면 도형으로 두고 카메라에 투영하므로, 링이 눌린 정도가
곧 시점 기울기이고 도북은 늘 화면의 실제 북쪽을 가리킴.
- 15° 눈금(90°마다 긴 눈금), N·E·S·W 라벨(뒤로 넘어가면 흐림).
- 표고축 침은 내려다볼수록 짧아지고, 점으로 눌리면 Z 라벨을 감춤.
- 위젯 56 → 84px — 라벨이 읽히는 최소 크기.
검증: typecheck·prettier 통과. 리로드 실측 — 위젯 84×84, 링 경로 `M0.00 −33.98…`,
도북 화살 `M0.00 −33.30 L−4.28 −17.14 L4.28 −17.14Z` 로 기본 시점 계산값과 일치.
회전 중 거동은 사용자 화면 검증 대기.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
옛 콤파스는 방위각(`atan2(ox, oz)`)만 써서 경사에 따른 남북 압축이 빠졌음 —
사시도 프리셋(오프셋 120·95·135, 앙각 27.7°)에서 바늘 41.6°, 화면 실제 북쪽 62.4°로
20.8° 어긋났음(2026-09-03 사용자 지적).
세계 축 N(−z)·E(+x)·Z(+y)를 카메라 화면 기저(오른쪽 r, 위 u)에 투영해 선과 글자를
그림. 기저는 카메라 오프셋만으로 구함(up = +y 고정). 뒤로 누운 축(깊이 > 0.25)과
정면으로 와 짧아진 축은 흐리게 그려 겹침을 줄임.
검증: typecheck 통과. 기본 시점에서 DOM 좌표가 해석값과 일치 —
N (28.0, 11.0) · E (45.0, 28.0) · Z (28.0, 27.4). 회전 중 거동은 사용자 화면 검증 대기.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
같은 폴백 사다리가 네 곳에 서로 다른 모양으로 흩어져 있었고, 그중 배수유역 라우터
`_route_center_lonlat` 은 노선 CSV 의 `crs_epsg` **라벨**을 그대로 변환에 썼다(라벨과
실좌표계가 다른 사례 실측 — 2026-09-01 용화 라벨 5179 / 실제 5176).
`common_util_crs.resolve_project_crs()` 신설 — 사다리는 ① 노선 crs_input ② 파일 라벨
(원본 좌표를 읽는 자리만) ③ 지형 PRJ(작업 좌표계 정본) ④ DB epsg ⑤ EPSG:5186.
작업 좌표계는 **지형 PRJ 우선으로 확정**(2026-09-03 사용자 결정 — 서피스 격자가 모델좌표의
주인이라 현행 유지). `project_epsg_from_prj()` 도 이 창구로 위임.
더해 지표면 트림이 노선을 통째로 지울 때 노선·지표면 bbox 를 함께 로그에 남긴다 —
"겹치지 않습니다" 만으로는 좌표계 문제인지 측량 범위 문제인지 갈리지 않았다.
검증 — `tmp/tests/test_project_crs_resolution.py` 5건 신설(사다리 4·진단 1),
전체 375 passed·17 skipped, ruff format 무변경.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
단일 링 자료형이 곧 중첩·빈공간이었다. 아래 유역이 위 유역을 감싸면 구멍이 사라져 위
유역을 통째로 덮고(합성 실측 256㎡ 전량), 한 관의 유역이 두 조각이면 작은 쪽이 사라져
빈공간이 됐다(144㎡).
- `polygon_parts()` 신설 — 조각마다 [외곽 링, 구멍 링...], 넓은 조각부터.
- `WatershedBasin.boundary_parts` 로 교체. `boundary_xy`(가장 넓은 조각의 외곽)와
`boundary_rings`(편 링 목록)는 파생 속성이라 옛 소비처는 그대로 동작한다.
- API 에 `polygon_rings_lonlat` 추가. 저장 GeoJSON 은 구멍을 가진 Polygon, 조각이
여럿이면 MultiPolygon.
- 캔버스 4곳(B04 유역화면 채움·선택, B05 배수유역도 채움·선택)은 even-odd 로 한 번에
채우고 `pointInRings()` 로 판정 — 구멍 안을 눌러도 바깥 유역이 잡히지 않는다.
중복이던 지역 `pointInRing` 은 삭제하고 공용 것으로 통일.
검증: 단위 5건 신규(도넛 구멍 보존·조각 2개 보존·파생 속성·빈 경계·조립 경로 전체에서
링 2개), 전체 168 passed, typecheck 통과. 브라우저 — API 가 유역 22개에 링 목록을 실어
보내고, 캔버스 even-odd 실측(가운데 알파 0 / 고리 255)으로 구멍이 실제로 뚫린다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
뷰어 좌표 규약이 `x, 높이, -y` 라 세계 북쪽은 -z 다. 카메라 오프셋(카메라 − 타깃)의
수평 성분으로 도북이 화면에서 몇 도 돌아갔는지 계산해(atan2(offset.x, offset.z)) 바늘을
돌린다. 뷰어 우하단 — 축척 막대(좌하단)의 반대편에 둔다.
뷰어 파일이 이미 946줄이라 콤파스는 별도 모듈로 분리(700줄 제한). 애니메이션 루프에서
0.5° 이상 움직였을 때만 transform 을 다시 쓴다.
검증: typecheck 통과. 공용 브라우저 실측 — [상단] 시점 0°(바늘이 북쪽=화면 위),
[사시도] 41.6°, 캔버스 220px 드래그 시 218.6° 로 카메라와 함께 회전.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 자동설계 체인이 초기값 스냅샷에 `planned_route.csv` 를 함께 남김. 값은 체인이
이미 만든 정점(사업지 좌표계·트림·조밀화 후)이라 재계산 없음.
- `load_design_route()` 는 surface_params 가 주어진 호출에 한해 그 CSV 정본을 읽음.
트림 전 원본이 필요한 호출(도엽 범위)은 종전 경로 유지. 지표면·노선이 바뀌면
`discard_initial_snapshot()` 이 폴더째 지워 경로가 저절로 닫힘.
- `run_sheet_surface_analysis()` 가 노선을 원본 좌표계로 읽어 도엽 서피스를 딴 자리에
만들던 문제 교정 — `load_design_route()` 로 창구 통일, 도엽 확보 PRJ 탐색도 지형
PRJ 폴더로 교정. shapefile 노선(5179) + 지형 PRJ(5176) 조합에서 트림이 노선을
통째로 지우던 원인.
검증: tmp/tests 158 passed(신규 4건 포함), 공용 브라우저 실사용 경로로 용화 shapefile
한 벌 업로드 → 자동 체인 완주 → CSV 정본 643점, 원본 경로와 최대 좌표차 0.00007m.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
파일마다 포맷 폭이 달라(≈80 대 100) 한 줄만 고쳐도 포맷터가 무관한 줄을 대량
재포맷했음. 사용자 지시로 전체를 한 번에 맞춤. 코드 동작 변경 없음 — 포맷만.
- 프론트엔드 `.ts/.css/.html` → 저장소 prettier (`.prettierrc`, printWidth 100)
- `B07_DesignDetail/openwebcad/**` → 자체 biome (tab 들여쓰기·single quote·lineWidth 100).
`biome format` 만 사용 — `biome lint --write` 는 포맷 아닌 코드 수정까지 하므로 제외
- 파이썬 → `ruff format` (엔진 코드는 이미 정합, resources·scratch 스크립트 24개만 변경)
두 포맷터가 서로 되돌리지 않도록 `.prettierignore` 신규 — openwebcad 와 빌드·산출물
폴더를 prettier 대상에서 뺌. `.prettierrc` 에 `endOfLine: "auto"` 추가 — 기본값 `lf` 가
`core.autocrlf=true` 로 받은 CRLF 파일을 매번 전부 다시 써서 `--list-different` 가
실제 포맷 차이를 가리고 있었음.
검증: `tsc --noEmit` 통과(루트·openwebcad 둘 다), pytest 349 passed / 17 skipped /
0 failed, CAD vitest 87건 중 81 passed / 6 failed(laptop-sub 기준선과 동일, 회귀 없음).
포맷터 재실행 시 prettier·biome 모두 변경 0건.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- B04 지표면 3D 뷰어가 지표면 범위만으로 카메라 거리를 정해, 라이다가 노선의
일부만 덮으면 나머지가 화면 밖으로 잘렸음. 카메라 타깃이 늘 지표면 중심이라
한쪽만 넓혀서는 소용이 없어, 중심에서 가장 먼 노선 정점까지를 반폭으로 잡아
대칭으로 넓힌 `fitBounds()` 를 `fitCamera()` 가 씀. 노선이 없으면 종전 동작 유지.
- 계획선 1차(배관 정착)와 2차(직선 분할 폴백)가 같은 `_profile_entry()` 를 써서
저장본 `basis` 가 둘 다 `station_alignment` 로 나갔음. `PIPE_ANCHORED_BASIS`
신규 + `_profile_entry(basis=)` 인자로 1차만 값을 가름. 2차는 종전 값 유지,
편집 재구성은 저장본 값 승계.
검증: 용화 프로젝트 공용 브라우저 실측 — 노선색 픽셀 x 범위 48~399(우측 끝에 닿음)
→ 145~386(양끝 캔버스 안). pytest 349 passed / 17 skipped / 0 failed, tsc 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
① 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>
상세 배수유역 폴리곤이 지도에 안 그려졌다. 노선 CSV `crs_epsg` 열(라벨 5179)을
좌표계로 써서, 사업지 좌표계(.prj EPSG:5176)로 계산된 격자 산출물을 잘못 역투영한
탓이다 — 유역이 lon 119.79 / lat 23.08(대만 남쪽 바다)로 나가 화면 밖이었다.
`load_design_route()`는 노선을 .prj 좌표계로 재투영하며 `crs_input`만 갱신하고
`epsg` 라벨은 CSV 값 그대로 둔다(2026-08-31 확정). 그 라벨을 좌표계로 쓰던 자리를
모두 `crs_input`(= .prj 좌표계)으로 바꿨다.
- common_util_drainage_context: DrainageContext.epsg(int) → crs(str, pyproj 입력).
상세 배수유역·관 지점 응답 좌표가 노선 위로 돌아온다.
- B07_DesignDetail_Router_Support: context.crs 그대로 사용.
- SheetSurface.build_sheet_surface_from_route: load_design_route로 노선을 읽어
도엽 서피스를 라이다 지표면과 같은 좌표계에 만든다(기존엔 5179 격자로 만들어져
라이다 DTM과 다른 프레임에 놓였다). LAS 없는 WF1 경로는 라벨·좌표가 한 벌이라 그대로.
- Router_Inflow._resolve_epsg: 격자 산출물과 같은 .prj 좌표계로 역투영.
검증: tmp/tests/test_drainage_crs.py 신규(용화 샘플 회귀) + tmp/tests 전체 37 passed.
build_detail 실행 결과 유역 11개 폴리곤이 lon 129.0947~129.0988 / lat 36.8107~36.8127
(노선 위)로 나온다 — 수정 전 같은 좌표는 119.7872 / 23.0817이었다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
자동 전처리가 기본 방식(laplace) 하나만 만들도록 바꾼 뒤, 나머지 다섯 방식을 고를
길이 없었다. 버튼을 **만들어진 모델**에서 세우고 있었기 때문이다.
- GET /surface/sheet-methods : 고를 수 있는 방식과 라벨, 생성 여부
- POST /surface/sheet-surface : 한 방식만 만들어 등록 (method 검증 후)
- save_sheet_surface_models(): 방식 하나를 덧붙이는 등록. 전체 재분석과 달리 기존
행을 지우지 않는다 — 같은 파일을 가리키는 옛 행만 걷어 낸다.
- 버튼은 이제 고를 수 있는 방식 전체로 세운다. 안 만든 방식은 점선으로 표시하고,
누르면 모달로 물은 뒤 그 방식만 만든다. 만든 뒤에는 점선을 걷는다.
- 첫 진입은 만들어 둔 방식을 고른다 — 안 그러면 열자마자 모달이 뜬다.
방식마다 파일명이 갈려 있어(dtm_sheet_{방식}.npz) 한 번 만든 방식은 다시 골라도
그대로 쓰인다(2026-09-01 사용자 확정).
라벨 정본은 백엔드 SHEET_METHOD_LABELS 하나다 — 화면에 복사해 두지 않는다.
검증(용화 e418fcfc, sheet_tin 행을 지워 재현):
버튼 6종 노출, TIN 격자만 (미생성) 표시
클릭 -> 모달 -> [계산하기] -> 55초 -> 점선 걷히고 선택됨
ruff 통과, tsc 무오류, tmp/tests 322 passed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
용화 자동 체인이 끝까지 돌았는데 종단 계획선이 PVI 3개 직선으로 나왔다. 관 22개
측점이 92.6~2044.9m 로 트림 전 원본(2,136m) 기준인데 확정 노선은 1,070.4m 라
21개가 노선 밖이었다. 좌표계도 갈려 있었다 — 관은 EPSG 5179(노선 파일), 노선은
5176(프로젝트)에 저장돼 누가거리만 우연히 맞물려 있었다.
원인은 계획노선을 읽는 곳이 흩어져 있는 것이다. 트림·조밀화를 체인 한 곳에만
넣었더니 배수유역·유입·도엽은 원본을 그대로 읽었다.
- load_design_route() 신규 — 읽기·좌표계 변환·트림·조밀화를 한 곳에서 끝낸다.
설계 계통(체인·배수유역)은 전부 이 함수를 지난다.
- Router_Watershed 도 이 함수를 쓴다. 좌표계가 프로젝트 기준으로 통일돼 관과
노선이 같은 공간에 놓인다.
- 도엽 서피스는 SHEET_SURFACE_AUTO_METHODS(기본 laplace 1종)만 자동 생성한다.
여섯 방식을 매번 만들면 WF1 891초 중 655초를 여기서 쓴다. 나머지는 관리자가
화면에서 고를 때 만든다.
Router_Inflow 는 노선을 EPSG 라벨용으로만 쓰고 기하를 안 써서 제외했다.
Engine_Extent(배경 지도 범위)도 제외 — 지도는 트림 전 전 구간을 덮어야 사용자가
측량 범위 밖을 볼 수 있다.
실측(용화 route 120):
배수유역 노선 2,136m -> 1,070m 격자 889x1435 -> 545x831
1차 영역 538,574㎡ -> 263,170㎡
관 좌표계 EPSG 5179 -> 5176
관 22개(10개가 노선 밖) -> 11개 전부 노선 안
종단 PVI 3개 -> 13개, 종곡선 1 -> 11, 불균형 97.0% -> 86.2%
PVI 측점이 관 측점과 일치한다(반올림 오차 제외).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
classification 필터를 넣은 뒤 B04 화면의 포인트클라우드 패널이 "데이터가 존재하지
않습니다"로 비었다. 원인은 화이트리스트 중복이다 — point-cloud 라우터가 필터 목록을
{"grid_min_z","csf","pmf"} 로 따로 적어 두고 있어 classification 을 400으로 막았다.
GET /surface/point-cloud?filter=classification -> 400 "지원하지 않는 지면 필터입니다."
필터 등록부는 Ground._FILTERS 하나뿐이므로 available_filters() 를 쓴다. 여기 따로
적어 두면 필터가 늘 때마다 이 화면만 막힌다.
이 400이 연쇄로 3D 노선 표시까지 막았다 — 노선 평면이 referenceBounds 를 필요로 하는데
그 값이 포인트클라우드 응답에서만 왔기 때문이다. 확정 서피스 bounds(수 KB)를 먼저
받아 두도록 분리했다. 포인트클라우드는 수십 MB(실측 28MB·9.3초)라 늦거나 실패할 수
있는데, 그때 노선까지 같이 사라지면 안 된다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
실무 자료 용화.las가 계획노선 2,136m 중 일부만 덮는다. 좌표 문제가 아니라 측량
범위 자체다 — LAS(VLR EPSG 5176)와 정사영상 용화.tif 범위가 서로 일치하고, 위경도로
datum 보정까지 태워도 위도는 완전히 포함되며 경도만 동쪽 431m 초과한다.
노선 3D 표시
- 계획노선을 지표면에 드리우지 않고 데이터 최고 표고(bounds.z_max) 평면에 수평으로
얹는다. 노선과 측량 범위가 평면상 어디서 어긋나는지 보려는 것이라 지형을 따라
오르내리면 오히려 판단이 어렵다.
- 색은 2D 지도·B05 배수유역도가 쓰는 routeLineColor()를 그대로 쓴다. 같은 선을 두
화면에서 다른 색으로 그리면 같은 것인지 알아볼 수 없다.
노선 트림
- trim_route_to_surface(): DtmGridSampler 의 valid_mask 로 판정한다. bounds 사각형이
아니라 불규칙한 실제 외곽이다. 가장 긴 연속 유효 구간을 남긴다.
- 가장자리 여유 SURFACE_ROUTE_EDGE_TRIM_M(30m)은 잘라 낸 쪽 끝에만 적용한다. 노선
본래 끝점이 지표면 안이면 깎지 않는다.
- 자르는 자리는 _planned_route_points_in_project_crs() 한 곳이다. 체인의 BP·EP·CP가
전부 이 함수를 지나므로 여기서 한 번 자르면 하류가 모두 유효해진다.
- 지표면을 못 열면 자르지 않는다. 트림 실패가 설계를 막으면 안 된다.
실측(용화 노선 2,136m):
csf/dtm/smooth -> 1,310m (61%)
classification/dtm/smooth -> 1,070m (50%)
bounds 사각형 기준 추정치 1,400m보다 짧다 — 실제 외곽이 사각형보다 작기 때문이다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
csf 처리 결과를 눈으로 검증할 수 있어야 한다(사용자 지시). 지면분류가 있는 LAS는
classification 하나만 만들고 끝내면 csf가 제대로 도는지 확인할 방법이 없다.
resolve_auto_source_filters 가 이제 목록을 돌려준다 — 첫 항목이 기본 확정값이다.
분류 있음: ['classification', 'csf'] 기본 확정 classification
분류 없음: ['csf'] 기본 확정 csf
서피스 생성 로직은 두 경우가 같다. 갈리는 것은 기본 확정값 지정뿐이다.
실측: cloud_merged.las(미분류) -> ['csf'], 용화.las(class 2 2.33%) ->
['classification', 'csf'].
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
화면 검증에서 드러났다. 드롭다운으로 classification/dtm 을 요청하면 UI가 실패하고
드롭다운이 되돌아갔다. 서버는 200을 돌려주고 있었다 — 실측 114초·144초가 걸리는데
API_ANALYSIS_TIMEOUT_MS(60초)가 먼저 abort 했다.
조합 하나를 만드는 요청은 구조화된 점군(4,900만 점) 전체를 다시 보간하므로
분석용 60초로도 모자란다. API_SURFACE_BUILD_TIMEOUT_MS(15분)를 새로 두고
analyzeSurface 에만 쓴다. 사용자에게는 모달이 미리 "수 분이 걸립니다"로 알린다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
지면 필터를 고쳐 지면점이 9~26배 늘자, 필터 전체 x 표현 전체를 미리 만드는
자동 전처리가 감당 못 할 만큼 길어졌다(용화 기준 15~20 모델). 산출물 대부분은
아무도 열어 보지 않는다.
- 자동 전처리는 기본 필터 1종 x SURFACE_AUTO_METHODS(dtm) 만 만든다. 스무딩
유무 두 벌은 기존대로 같이 나온다.
- 기본 필터는 고정값이 아니라 입력 LAS를 보고 정한다 — 지면분류(class 2)가
있으면 classification, 없으면 csf. csf는 분류 없는 LAS를 필터링하기 위한
수단이므로 그때만 쓴다.
- 관리자가 B04 드롭다운을 바꾸면 그 조합이 이미 저장돼 있는지 보고, 없으면
모달로 물은 뒤 그 조합만 계산해 영구 저장한다. 취소하면 드롭다운을 되돌린다.
이미 있으면 묻지 않고 저장된 데이터를 그대로 쓴다.
- config_signature에서 source_filters·precompute를 뺀다. 이 둘은 "무엇을
만들지"를 고르는 값이라 서명에 넣으면 조합을 바꿀 때마다 manifest가 통째로
폐기돼 이전에 만들어 둔 조합이 사라진다.
- analyzeSurface가 API_ANALYSIS_TIMEOUT_MS를 쓴다 — 기본 30초로는 조합 하나를
만드는 동안 abort 된다.
detect_extra_filters()는 resolve_auto_source_filters()로 대체했다. 필터를
말없이 덧붙이는 대신, 자동 경로의 기본값을 정하는 판정으로 쓴다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
실측(2026-08-31): 업로드 후 화면에서 "계획 노선 CSV를 읽지 못했습니다: ...shp"
경고가 반복됐다. find_planned_route_file 은 shapefile을 우선 돌려주는데 받는 쪽이
아직 read_planned_route_csv 였다 - 확장자 분기가 없는 판독기라 .shp 를 CSV로 열다
실패하고 노선 없이 진행했다.
앞선 커밋에서 Service_Chain, SheetSurface, Router_GIS 는 바꿨으나 아래 세 곳을
놓쳤다(조사 당시 grep 출력이 잘려 목록에서 빠졌다):
- B04_PreProcess_Router_Watershed.py (2곳)
- B04_PreProcess_Router_Inflow.py
- common_util_drainage_context.py
셋 다 read_planned_route 로 바꿨다 - CSV/shapefile을 확장자로 갈라 읽는다.
검증: ruff check 통과, tmp/tests 302 passed (잔여 실패 11건은 기존 실패로
HEAD 사본에서 동일 재현).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
원청 정식 계획노선이 shapefile(UTM-K)로, 지형이 별도 PRJ(동부원점 Bessel)로
들어오는데 입력 경로가 shapefile 확장자를 막고 PRJ를 프로젝트당 1개로 전제했다.
- 업로드 허용에 .shp/.shx/.dbf/.cpg 추가, 한 번에 보낼 파일 수 5 -> 10
- B03_FileInput_Engine_Shapefile: ESRI 규격 직접 파싱(GDAL 미사용). 형제 파일이
아직 안 왔어도 .shp 하나로 기하를 읽는다. .cpg 내용이 949뿐인 실물을 CP949로
정규화해 한글 속성을 살린다.
- 노선 판독을 read_planned_route로 일원화(CSV/shapefile), PlannedRoute에
crs_input 추가 - 변환 입력은 EPSG 코드가 아니라 crs_input_from_prj가 주는
값(EPSG:n 또는 원문 WKT)이다. 실물 PRJ 2종 모두 to_epsg가 None이다.
- shapefile 세트를 input/shp/ 한 폴더에 모은다(GDAL 요건). 노선 PRJ가 그 안에
남으므로 지형 PRJ(input/prj/)와 파일명 정렬 운에 기대지 않고 갈린다.
find_project_prj가 지형 PRJ를 프로젝트 좌표계로 고른다.
- 필수 세트를 노선 1종(csv 또는 shp) + prj + tfw로 완화, shp면 shx/dbf 동반 필수.
- UI: 확장자 단독 슬롯 매칭을 basename 그룹핑으로 바꿔 노선 PRJ와 지형 PRJ가
같은 슬롯을 다투지 않게 하고, 노선 슬롯이 파일 한 벌을 담아 함께 전송한다.
자체검증: tmp/tests/test_route_shapefile_input.py 9개 통과, tsc --noEmit 통과,
ruff check/format 통과. 전체 스위트 잔여 실패 11건은 HEAD 사본(git archive)에서
동일하게 재현되는 기존 실패다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_epsg_from_prj()가 WKT에 항상 있는 false_easting 때문에 EAST 분기가
무조건 걸려 모든 PRJ를 EPSG:5187로 판정했다(표준 WKT 6종 실측). 새 원청
자료의 5176(비표준 TOWGS84)·5179(AUTHORITY 없는 ESRI WKT)가 이 경로로
들어오면 최대 100km 어긋난다.
- common_util/common_util_crs.py 신설: 판별 사다리(pyproj DB 대조 →
AUTHORITY 태그 → 투영 파라미터 지문)와 변환 입력 정규화. 수평 성분에
TOWGS84가 박힌 PRJ는 EPSG로 갈아타지 않고 원문 WKT로 변환해 지역 보정을
보존한다. 수직 성분의 bound(KNGeoid)는 2D 변환에 무관하므로 무시.
- B04 get_epsg_from_prj: 죽은 문자열 분기 제거, 유틸 위임. 시그니처 불변 —
기존 COMPD_CS 프로젝트는 예전과 같은 EPSG:5187 문자열이 나온다.
- B03 _component_metadata: to_epsg 실패 시 사다리 라벨 보강,
normalize_crs_metadata: BoundCRS 벗김(TOWGS84 PRJ 수평 탐색 실패 수정).
검증: tmp/tests/test_common_util_crs.py 13건 포함 스위트 29 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 지적(2026-08-30): "결국 노선 위에 위치해야 한다".
관 자리를 정한 선(계획노선 CSV)과 화면에 그려지는 선(B05 최적 경로)은 같은 자리를
지나면서 연장이 다르다 — 실측 350.11m vs 354.83m, 전 구간 이격은 평균 0.03m·최대
1.12m뿐인데 길이가 4.7m 차이난다. pipe_points.json이 누가거리만 저장해서 읽는 쪽이
쥔 선에 따라 같은 값이 3.3~4.3m 미끄러졌고, 노선 지문도 늘 어긋나 B05는 저장된 관을
매번 통째로 버렸다("노선 지문이 달라 저장된 관 지점 4건을 쓰지 않습니다").
- PipePoint에 x·y를 두고 저장 시 채운다(save_pipe_points에 노선을 넘긴다).
- 지문이 달라도 좌표가 있으면 읽는 쪽 노선에 투영해 이월한다(project_pipe_points).
구간(start_m·end_m)은 기준점이 옮겨간 만큼 같이 민다 — 시설 치수는 불변.
- 좌표 없는 구 저장분만 종전대로 버린다.
- B05는 같은 로더(load_pipe_points_file)를 쓴다 — 두 화면의 판정 기준을 하나로.
검증: 프로젝트 5d18ebe3 실데이터 — 좌표 4/4 저장, B05 계획선으로 읽어 4건 전부 이월,
관에서 계획선까지 거리 최대 0.016m(종전 4건 전량 폐기). 누가거리는 +3.45~+4.29m
이동(연장 차이 그대로). pytest 11 passed(투영 이월·구 저장분 폐기 테스트 추가).
/surface/confirmed가 지형 가장자리를 structured.npz 또는
ground_points_{filter}.npz에서만 읽었다. 둘 다 LAS 산출물이라 LAS 없이
설계한 프로젝트에는 없고, bounds가 null로 나가 B05가
"지표면 범위 정보를 찾을 수 없습니다"로 막혀 3D 지형을 못 띄웠다.
두 파일이 없으면 확정 모델 격자(npz)의 bounds를 그대로 쓴다. 도엽 서피스
npz는 이미 같은 형식의 bounds(3×2)를 담고 있다.
검증: 프로젝트 5d18ebe3(LAS 0개) B05 재진입 — /surface/confirmed 200,
preview·contour 요청이 뒤따라 나가고 안내문이 사라짐. 지형·등고선·코리도·
구조물 라벨까지 화면에 뜬 것 확인(152_b05.png).
사용자 지시(2026-08-30): IDW·거리비례·TIN 곡면 제거, 5m 도엽등고선 기준 TIN 추가.
TIN(도엽선)은 격자에 구운 라인 셀이 아니라 벡터 등고선 정점의 원좌표를 그대로
Delaunay로 잇는다. 격자 밖은 100m 여유만 물고 자르며, 길이 필터는 두지 않는다
(짧은 봉우리 폐합 링을 버리면 마루가 통째로 평평해진다).
거리비례는 버튼에서만 빼고 함수는 남긴다 — 라플라스·TPS·ANUDEM·다중해상도가
초기추정으로 계속 쓴다. 기본 방식은 distance가 빠져 multires로 옮겼다.
검증: 실데이터 767×840·1m 6종 93.5s. 서피스에서 다시 뽑은 5m 선의 평면 이탈이
TIN(도엽선) 중앙 0.12m·2m초과 8.1%로 6종 중 최소(직전 최선 다중해상도
0.34m/18.3%). pytest 8 passed(격자와 어긋난 링 위 표고 재현 테스트 추가).
DTM 스무딩(184296f5)이 표고 정본(npz)에서 이미 가우시안+B-spline을 걸므로
프리뷰에서 다시 NURBS를 적합하면 곡면 적합이 이중으로 걸린다. c2060f3b 이전의
격자 메시로 되돌리고, 쓰지 않게 된 NURBS import를 지운다.
검증: 프리뷰 glb 정점 표고 = npz 격자 표고 (|차이| 최대 0.0000m,
distance 원본·스무딩 각 161,280정점).