데이터 흐름 문제
- B04 해석 산출물 7개(7.2MB)가 B05로 전량 복사되고 있었고, 관 지점 정본
(pipe_points.json)은 B05가 아예 참조하지 않았다. B04에서 확정한 관이 B05에
보이지 않고 진입할 때마다 자동 배치로 되돌아갔다.
- 사본은 "B05 편집이 원본을 덮어쓴다"를 막으려던 것인데, 외곽선 편집을 제거한
뒤로 B05는 아무것도 저장하지 않아 존재 이유가 사라졌다.
일원화
- B05 전용 모듈 3개 삭제: Engine_Drainage_Store / Engine_Drainage_Basin /
Router_Drainage. 두 화면이 B04_wf1_Surface/drainage/ 한 폴더를 직접 읽는다.
- POST /drainage/basins 폐기 → GET/PUT /drainage/pipe-points ·
POST /drainage/detail-basins 한 벌로 통합. 응답에 route_lonlat ·
main_polygon_lonlat · flow_arrows · upstream_lonlat · inflow_hotspots를 추가해
B05가 그리는 데 필요한 값을 같은 응답으로 받는다.
- 경로 확정 시 관 지점을 B04와 같은 저장소에 커밋한다.
B05 화면
- 유역 폴리곤을 눌러 고르기(지도에서도 선택), [집중유역] 토글, 관 개수·유역 수·
종단 Z 출처 요약 줄 추가.
- 관 마커는 좌클릭으로만 잡는다(우클릭은 메뉴, 가운데 버튼은 팬).
- 유역 목록은 3행까지 보이고 나머지는 스크롤.
종단 테이블 구조물 라인
- 관 매설 지점을 "배관" 구조물로 실체화(origin: "pipe"). 정본은 관 지점 파일이며
목록은 그 투영이라, 어디서 옮기든 pipeEditor 한 경로로 전파된다.
- 테이블 위 세로선을 끌어 이동(배관은 세부유역까지 재산정), 우클릭으로 배관
추가/구조물 삭제.
정리
- B05_wf2_Route/drainage/ 사본과 debug/(삭제된 Subdivide 엔진 검증 스크립트) 삭제.
- config DRAINAGE_B05_DIRNAME 제거.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
등고선을 전부 해석해 얻은 경계라 사람이 다시 그릴 이유가 없다(2026-08-01 사용자 지시).
남겨 두면 손으로 옮긴 외곽선과 1m 셀 라벨로 만든 세부유역이 어긋나 면적·관경의 근거가
깨진다. 외곽선은 이제 해석 결과를 그대로 표시만 한다.
제거 대상
- B05_wf2_Route_UI_Drainage_Boundary.ts (핸들 편집기)
- "유역선 편집" 버튼과 경로 확정 시 저장 여부 모달
- PUT /{project_id}/drainage/boundary 엔드포인트
- boundary_overrides 로드·저장·재부착 로직과 resample_boundary
- DRAINAGE_BOUNDARY_OVERRIDE_FILENAME / _HANDLE_SPACING_M / _MATCH_RADIUS_M
B05 저장소 모듈은 B04 산출물 사본 동기화만 남겨 245줄 → 55줄로 줄었다.
이미 저장돼 있던 boundary_overrides.json은 지우지 않았다(사용자 데이터라 그대로 둔다).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
관 매설 지점을 기준으로 세부 배수유역을 나누는 기능을 B04 2D 지도에 추가한다.
경계는 B04 전처리 격자(03_road_routing.npz)의 road_slot — 1m 셀마다 물이 도달하는
도로 셀 — 을 담당 관으로 라벨링해 그 경계로 잡는다. 종단 Z는 경계를 긋지 않고
"도로 셀이 어느 관으로 흐르는가"만 정한다.
공용 승격 (B04 관리자 화면과 B05 사용자 화면이 같은 결과를 내야 함)
- common_util_drainage_detail.py: 관 보충(9)·세부유역 분할(10) 알고리즘
- common_util_drainage_context.py: 노선·종단 Z·좌표계 입력 준비
- common_util_drainage_pipes.py: 관 지점 정본 저장소(edits/pipe_points.json)
- common_util_route_profile.py: 종단 Z 해석기(계획고 > 경로 정점 > 지표면 > CSV)
- common_util_surface_sampler.py: B05 종횡단 sampler 이동
- B05 _prepare()의 노선 소스를 원청 계획노선 CSV로 정정(B04 격자와 누가거리 정합)
B04 신규 API
- GET /{project_id}/drainage/pipe-points 저장분 조회(없으면 자동 생성)
- POST /{project_id}/drainage/detail-basins 편집 중 목록으로 재분할(저장 안 함)
- PUT /{project_id}/drainage/pipe-points 모델 확정 시 관 지점·세부유역 커밋
B04 화면
- 관 마커 기본/자동/수동 색 구분, 계획선 스냅 드래그 이동
- 계획선 우클릭 "관 매설 추가" / 마커 우클릭 "관 매설 삭제"
- 표시 토글 2그룹(관 매설 / 세부 유역)을 유입 집중점과 분리
- "상세유역 분석" 버튼을 눌렀을 때만 재계산
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
흐름 강도 계산은 이미 trace_flow()가 원본 1m 격자에서 하고 있었고 03_road_routing.npz에
저장까지 되어 있었다. 이번 작업은 그 원본값을 밖으로 꺼내 화면에 그리는 것이다.
- 강도 곡선 출력 간격을 5m -> 1m로 바꿔 계산 원본을 그대로 내보낸다(구간 합, 평균 아님).
구간 기준을 round -> floor로 바로잡았다. 도로 셀 누가거리가 전부 0.5 배수라
numpy의 짝수 반올림이 짝수 칸에 3배를 몰아 2m 주기 빗살을 만들고 있었다
(도로가 격자축과 나란한 구간은 홀수 칸이 전부 0이라 색이 점선으로 나왔다).
- find_inflow_hotspots(): 도로를 시점·기본 관(세류 교차)·종점으로 잘라 구역을 만들고
구역마다 floor(길이 / 관 최대간격) + 3개를 뽑는다. 강도 최대점부터 고르되 고른 지점
양 편측으로 관 최소간격만큼을 후보에서 빼 한쪽 쏠림을 막는다. 관 쪽 경계에서도 같은
거리만큼 물러난다 - 그러지 않으면 그 관이 이미 받는 물 전부가 관 옆 칸에서 다시
최대값으로 잡혀, 정작 관이 없어 보충이 필요한 자리가 뽑히지 못한다.
- GET /api/projects/{id}/drainage/road-inflow 신설(B04_wf1_Surface_Router_Inflow.py).
저장된 road_slot으로 그 1m 구간에 귀속된 셀 마스크를 만들어 외곽선만 돌려준다.
새 그래프 탐색 없음. npz는 mtime 키로 프로세스 캐시에 둔다.
- 응답에 schema_version을 넣어 형식이 바뀌면 옛 저장분을 무시하고 다시 계산하게 했다.
- 프론트: B04_wf1_Surface_UI_FlowStrength.ts 신설. 계획선 위에 1m 구간 로그 레인보우와
유입 집중점 마커(크기=강도, 번호)를 얹고, 마커를 고르면 기여 셀 외곽선을 겹쳐 그린다.
마커 안쪽만 선택 대상이며 팬은 가운데 버튼 전용이라는 규칙은 그대로 둔다.
좌표 변환은 MapRender에 모았다(normalizedToScreen / lonLatToScreen / metricToScreen).
- 사이드 패널: 지면 필터·서피스·스무딩을 "지표면 분석" 한 컨테이너로 모으고 스무딩을
드롭다운(적용/미적용)으로 바꿨다. 포인트·모델 표시 옵션은 "모델 표시 옵션" 하나로
합치고 맨 아래에 점 크기·밀도를 [값][게이지] 형태로 놓았다.
- DRAINAGE_ROAD_WIDTH_M 주석에 설계 도로폭이 아니라 D8 대각 통과를 막는 해석용 굽기
폭임을 명시했다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 지시(2026-08-01): 위성사진은 수치지형도 도엽과 같은 눈금으로 확보한다.
계획노선이 걸치는 기준 도엽만 받고(1매 또는 2~3매), 주변 도엽은 받지 않는다.
- sheets_for_points(): 기준 좌표가 속한 도엽번호만 반환(주변 확장 없음)
- satellite_extent(): 기준 도엽들의 도곽 합집합을 프로젝트 좌표계로 반환.
도엽을 못 구하면 라이다∪계획노선 범위로 폴백
- 다운로더: 주변 확장 로직 제거. 요청 범위를 덮는 타일만 받되, 한 변 타일 수가
한도를 넘으면 zoom을 낮춘다(도엽 1매를 zoom 18로 받으면 한 변 19타일 = 4,864px)
- 국가 GIS 벡터는 종전대로 라이다∪계획노선 범위 사용
- 표본 실측: 기준 도엽 1매(37816093) -> 10x13 타일, 2438x3169m, 0.95m/px, 15.1MB,
실패 타일 0개 (이전 731x853m)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3D(라이다)와 2D(브이월드)는 다루는 범위가 다르다. 그런데 2D 지도가 열릴 때 라이다
범위에 맞춰 확대하고 있어서, 배경을 넓게 받아도 화면에 보이는 범위가 늘 같았다
(2026-08-01 사용자 지적).
- 확보 범위: 계획노선·기준 좌표를 덮는 기준 박스 + 주변 박스(SURFACE_MAP_NEIGHBOR_RINGS=1)
= 3x3 배치. 미터/타일 여유폭 방식(SURFACE_MAP_MARGIN_TILES) 폐기.
해상도(zoom 18)는 브이월드 기본 스케일 그대로 — 화소를 키우는 것이 아니다.
- 타일 상한 15는 양쪽에서 균등하게 줄여 기준 박스가 가장자리로 밀리지 않게 한다.
- B04 하단 지도·B05 배수유역도는 확보한 배경 전체가 보이도록 열린다.
- 표본 실측: 기준 박스 372x401m -> 12x15 타일 = 1463x1828m (이전 731x853m).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 요청 방향(2026-08-01): 해상도(zoom 18)는 그대로 두고, 계획노선과 기준 좌표를
덮는 타일 바깥으로 주변 셀을 더 확보한다.
- SURFACE_MAP_MARGIN_M(300m) 제거, SURFACE_MAP_MARGIN_TILES(3겹) 신설.
기존 하드코딩 1겹 -> 설정값. zoom 18에서 1겹 약 122m(위도 37deg).
- download_extent는 덮어야 할 범위(라이다 U 계획노선)만 돌려주고 여유는 타일 쪽에서 준다.
- 표본 실측: 덮을 범위 372x401m -> 10x11 타일 = 1220x1341m,
계획노선 사방 여유 474~588m (이전 731x853m).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
배수유역이 어디까지 뻗을지 알 수 없어 도엽 자료를 잘라 두 벌로 나누지 않는다
(2026-08-01 사용자 지시). 분석 경로는 원래부터 원본을 읽었고 사본은 전송용이었으나,
사본 자체를 없앤다.
- B04_wf1_Surface_Engine_SheetView.py 삭제, SHEET_VIEW_* 설정 제거
- /geojson의 도엽 레이어는 원본 파일 그대로 + ETag로 전송
(재직렬화 제거만으로 요청당 2.0초 -> 0.006초, 두 번째 요청부터 304)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 도엽 병합본의 화면 표시용 사본 생성(B04_wf1_Surface_Engine_SheetView):
프로젝트 + 500m로 잘라내고 좌표를 소수 7자리로 줄여 등고선 37.3MB -> 0.97MB.
분석용 원본은 그대로 둔다(배수유역은 상류까지 넓은 범위가 필요).
- /geojson의 도엽 레이어를 파일 그대로 + ETag로 전송 — 매 요청 2.0초 재직렬화 제거(0.008초).
- /vworld-map도 ETag 전송으로 바꾸고, 프론트가 붙이던 `&_t=` 시간꼬리표 제거.
- 도엽 레이어를 브라우저 보관함 경유로 조회하고 준비화면에서 미리 담는다.
- 배경 지도 범위 = 라이다 범위 + 계획노선 범위 + 여유 300m(SURFACE_MAP_MARGIN_M).
- 도엽 기준 좌표 = 계획노선 시점·종점(없으면 라이다 중심). 같은 도엽이면 9매,
이웃 도엽에 걸치면 12매. 표본 프로젝트는 기존 9매와 동일(회귀 없음).
- 선정에서 빠진 도엽 zip 정리(prune_sheets) — 표본에서 30매 중 21매가 다른 지역 잔재.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1. 재산정하지 않는 한 저장분을 그대로 쓴다
GET /drainage/primary-region 이 기본으로 영구저장소의 응답 캐시를
그대로 돌려준다(0.0004s). refresh=true 일 때만 다시 계산한다.
저장 배열에서 응답을 재조립하지 않고 **응답 자체**를 남긴다 —
재조립하면 원본과 어긋날 여지가 생긴다.
버튼 이름 배수유역 -> 배수유역 재산정. 지도를 열면 저장분이 자동으로 뜬다.
2. B05용 평균 흐름 화살표 (build_flow_arrows)
셀 화살표는 1m 라 도면 배율에서 경향이 안 보인다. 겹치지 않는 10m 블록으로
나눠 방향을 원형 평균하고, 40m 간격으로 솎아낸다.
- 세류선 셀과 도로 셀은 뺀다. 그 자리는 확정된 물길/노면을 따르는 값이라
사면 경향을 왜곡한다.
- 산술평균이 아니라 원형 평균(0도와 359도의 평균은 180도가 아니다).
평균 벡터 길이가 방향 일치도이므로 0.7 미만이면 그 블록은 버린다.
- 블록 유효 셀이 절반 미만이면 건너뛴다(가장자리 조각 방지).
실데이터 377개, 간격 40m 균일, 블록당 평균 98셀, 적 278 / 청 99.
config: ARROW_BLOCK_M / ARROW_SPACING_M / ARROW_MIN_COVERAGE / ARROW_MIN_AGREEMENT
3. 갈래별 하이드/쇼 버튼 4종
1차 유역 / 2차 유역 / 유역 방향 / 평균 흐름.
평균 화살표는 B04 에도 올려 형태를 확인할 수 있게 했다.
4. B05 는 페이지 진입 즉시 유역도를 올린다
B04 결과를 읽기만 해 0.1초면 끝나므로 버튼을 기다릴 이유가 없다.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
분석이 30초 걸리는데 B05는 일반 사용자 화면이다. 관리자 확인용 B04에서
한 번 돌려 저장하고, B05는 그 결과를 읽어 관 보충과 세부유역만 처리한다
(2026-07-31 사용자 지시).
노선 원천 변경
- B05 확정 경로 -> B03 업로드 계획 노선 파일(CSV). 분석이 노선 설계보다
먼저 끝나 있어야 하기 때문. 샘플 planned_route_sample_epsg5187.csv 로 검증.
- common_util_route_geometry.py 신설 — RouteVertex/StructureCandidate/누가거리
보간/세류 교차점/계획 노선 CSV 리더. B04와 B05가 같은 표현을 쓰도록 공용화.
열 이름은 대소문자·한글 표기를 함께 받는다(B03이 여러 형식 수용 예정).
B04 (관리자 확인용, 신규)
- Engine_Watershed_{Grid,Stream,Descent,Flow,Expand,Export} — B05에서 git mv
- Engine_Watershed_Analyze.py — 1~8단계 오케스트레이션
- Router_Watershed.py — GET /drainage/primary-region
- UI_Watershed.ts — 2D 지도 GIS 레이어 그룹에 "배수유역" 토글 추가.
격자/화살표/세류망/1차영역/2차유역/기본관을 겹쳐 그린다.
- 저장 위치 B05_wf2_Route/drainage -> B04_wf1_Surface/drainage
- 03_road_routing 단계 추가: B05가 세부유역을 나눌 최소 배열(셀->도로셀 귀속,
유하장, 강도, 도로셀 제원, 셀 표고) + 계획도로선/기본배관/2차유역 기하
B05 (일반 사용자용, 축소)
- Engine_Drainage_Basin.py — B04 산출물 로더 + 관 보충(9) + 측구 라우팅/세부유역(10,11)
- Engine_Drainage.py 는 관경 산정만 남기고 322 -> 27줄
- Router_Drainage.py 509 -> 142줄. POST /drainage/basins 만 남김
- 화살표·격자·강도 띠 렌더 제거. 계획도로선/기본배관/2차유역만 받는다
삭제
- _legacy_watershed/ 4파일 (능선 행진 방식 원본 보관본)
- Engine_Watershed_Basin.py (B04 Analyze + B05 Drainage_Basin 으로 분할)
- GET /drainage/candidates 와 propose_structure_stations (구방식 후보 제안)
E2E 검증 (실데이터)
B04 분석 28.2s -> 저장(geojson 11KB + npz 2.6MB)
B05 로드 + 세부 설계 0.11s <-- 30초가 0.1초로
면적 457,404m2 로 B04 2차 유역과 정확히 일치
관 편집 재산정 0.12s, 관 3개 -> 세부유역 3개, 면적 보존
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
적색 셀이 해석 영역 최외곽에 있다는 것은 그 바깥에서 물이 더 들어온다는 뜻이다.
거기서 멈추면 유역이 잘린다. 반대로 최외곽이 전부 파랑이면 바깥 물은 도로로
오지 않으므로 더 볼 필요가 없다.
1. 해석 영역 최외곽 셀 중 적색인 것을 찾는다
2. 그 주변으로 한 겹(기본 50m) 넓힌다
3. 넓힌 영역으로 흐름 방향/색을 다시 분석한다
4. 새로 추가한 셀에 적색이 없으면 종료 (사용자 지시)
최외곽에 적색이 아예 없어도 종료 - 둘 다 같은 판단이다
격자 bbox 에 닿으면 격자도 셀 정수배로 넓힌다. 도로 시작점 기준 격자점은
그대로 유지되므로 확장 전후 같은 자리 셀이 같은 자리에 남는다.
실데이터 결과 (33.3s)
최외곽 적색 1,184 -> 400 -> 16 -> 0 (3회차에 닫힘)
해석 셀 536,708 -> 640,872 (+104,164)
적색 424,193 -> 457,404
화살표=경로 일치 100%, 파랑인데 적색 가리킴 0개 (유지)
구조 변경
- Watershed_Expand.py 신설: analyze_domain(1회 분석) + expand_by_red_boundary(루프)
- StagePreview 에 spec/domain/expand_* 추가. 확장하면 격자가 1차 영역보다
커지므로 화면/저장은 region.spec 이 아니라 이쪽을 봐야 한다.
- 라우터 응답에 expansion(회차/닫힘/추가 셀 수) 추가
- config: DRAINAGE_RED_EXPAND_BAND_M=50, DRAINAGE_RED_EXPAND_MAX_ROUNDS=20
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- DRAINAGE_INITIAL_RADIUS_M 50m -> 100m
- 1차 영역 = 상류 세류망 buffer(100m) UNION 계획 노선 buffer(100m).
세류망 선정이 정확해져 노선 버퍼를 다시 넣어도 영역이 폭발하지 않는다.
- 저장을 B05_wf2_Route_Engine_Watershed_Export.py 로 분리.
STAGES 딕셔너리에 단계 이름을 추가하고 write_stage 를 부르면
drainage/{번호}_{단계}.geojson + manifest.json 이 함께 갱신된다.
앞으로 기능을 붙일 때마다 이 자리에 단계가 하나씩 쌓인다.
- primary_region 단계에 route 레이어 추가(도로 대조용).
- 라우터의 임시 GeoJSON 작성 코드 제거.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1차 영역이 도로 전체 버퍼까지 포함해 도로 아래(하류)로 퍼지고, 세류선을 피처
단위로만 갈라 상류망이 통째로 누락되던 문제를 고친다.
- 1차 영역 = 도로 교차점 상류로 이어진 세류망만 반경 버퍼. 노선은 버퍼하지 않음.
노선이 영역 밖으로 나간 길이(road_outside_m)를 재서 반경 판단 근거로 노출.
- 상류 판정을 연결망 기준으로 교체: unary_union 노딩 -> 도로에서 절단 ->
끝점 그래프 -> 도로 교차 노드를 통과하지 않는 확산. T자로 붙은 지류와
2단계 이상 이어진 지류까지 상류망으로 따라간다.
- 상하류 판정 표고를 최근접 등고선 정점에서 TIN 선형보간으로 교체
(ElevationSampler). 최근접 정점은 오차가 등고선 간격만큼 나서 계곡 교차점이
한 등고선 위로 잡히고 상류 조각이 전부 하류로 오판됐다.
- 격자 크기 자동 강등 삭제 - config 값을 그대로 쓴다. 셀 수가 많으면 경고만.
자동 강등이 도로 굽기 두께 전제를 조용히 깨뜨렸다.
- TIN 삼각망을 격자 범위 + 여유로 클리핑. 결과 동일, 속도만 개선.
- 기본 반경 300m -> 50m (단계 검증용).
단계 검증 수단
- GET /drainage/primary-region : TIN/흐름 계산 없이 상류망/하류망/1차영역/격자만 반환
- 프론트 1차영역 버튼 : 상류망(굵은 파랑), 하류망(회색 파선), 1차 영역(초록 채움),
해석 격자를 실제 셀 눈금으로 렌더. 켤 때마다 재요청한다.
- 클릭 시 storage/{project}/B05_wf2_Route/drainage/primary_region.geojson 저장
합성 검증(T자 지류 + 2단계 지류 + 하류 지류 + 고아 세류):
상류망 4조각 채택 / 하류망 3조각 / 미연결 1개 제외 - 전부 기대대로.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
등고선 아크 추적 + 능선 행진 방식이 능선/계곡을 안정적으로 분리하지 못해
D8 물 방향 + 도로 기준 상류 추적 방식으로 교체한다.
- 능선을 따로 탐지하지 않는다. 물길을 따라가 도로에 닿는 셀만 유역이고,
그 경계가 곧 능선이다. 유역 내부 봉우리는 자동으로 포함된다.
- 등고선 TIN 보간 후 웅덩이 채움(형태학적 재구성) + 평탄면 미세경사로
가짜 웅덩이/평탄 삼각형에서 흐름이 끊기는 문제를 없앤다.
- 상류 추적은 포인터 더블링으로 전 셀을 한 번에 푼다. 셀의 흐름 종착
도로 셀(root)이 유역 판정·흐름 강도·세부유역 라벨의 공통 근거가 되어,
관을 옮겨도 격자 해석 없이 측구 라우팅만 다시 돌면 된다(.npz 캐시).
- 활성 셀이 격자 최외곽에 닿은 방향으로만 확장하고, 경계 링이 전부
비활성이 되면(띠 폐합) 멈춘다.
변경 사항
- 신규 엔진 3종: Engine_Watershed_Grid / _Flow / _Basin
- 폐기 엔진 4종은 _legacy_watershed/ 로 원본 보관(ruff 제외)
- config_system.py §5-3-1 에 DRAINAGE_* 파라미터 18개 (격자 1m, 반경 300m)
- 표고점 데이터 사용 중단(유효 데이터 부족), 프론트 능선 토글 제거
(전체 유역 외곽선과 같은 선이므로 중복)
- 응답에 main_polygon_lonlat / strength_profile 추가, 계획선 위 흐름 강도 표기
합성 지형 검증: 유역 179,919㎡ vs 이론 180,000㎡ (오차 0.04%),
능선 자동 검출, 확장 3회 후 자동 정지, 캐시 재사용 1.2s -> 0.1s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>