chore(요구): 요구 389 요구서 · 분석 셋 — 예정노선 생성 칸 걷기 · 기준 지정 두 점 · 위성 · 노선분석 수동 직선 그리기 · 연도 구역

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 18:56:15 +09:00
co-authored by Claude Opus 5.5
parent d6551bb582
commit 7f9919bd59
4 changed files with 584 additions and 0 deletions
@@ -0,0 +1,157 @@
# 분석 389-A — 예정노선 생성 칸 · 기준 지정 · 자동 이름 · 지도 · 이동 저장 (항목 1 · 2 · 3 · 4 · 15)
읽기 전용 분석. 재현(브라우저) 안 함 — 15번 「빈 화면」은 코드 추정.
---
## 1. D01 생성 칸 빼기 (사업연도 끝 · 종류 · 임도 종류 · 설계속도 · 시작 · 종료 측점)
「임도 종료」 = 화면 칸 「임도 종류」(`B02_Proj_Field_RoadType`)로 봄.
### 지금
| 칸 | 화면(`D01_PlanRegister_UI_Page.ts`) | 서버(`D01_PlanRegister_Router.py`) · DB | 읽는 곳 | 빼면 |
| --- | --- | --- | --- | --- |
| 사업연도 끝 | 45-51 · 검사 121 · 이름 연도 「시작~끝」 96-99 | 요청 22 · 없으면 = 시작 41-43 · `projects.project_year_end`(`db_management/020_project_groups.sql:12-13`) | D02 연도 범위 `D02_PlanBase_Router.py:97 · 162 · 199` · D03 지점 저장 `D03_PlanAnalysis_Router.py:339-340` · 대시보드 상세 범위 표시 `B01_Dashboard_UI_ProjectDetail.ts:107` · 목록 `B01_Dashboard_Router_Overview.py:30` | 서버가 이미 끝 = 시작으로 채움 → D02 가 한 해만 보임(2번과 맞음) · 깨짐 없음 |
| 종류 | 57-58 읽기 전용 글 「노선 계획」 | 서버로 안 감 · `project_kind='PLAN'` 은 서버가 박음(라우터 54 `kind="PLAN"`) | 없음 | 영향 없음 |
| 임도 종류 | 59-66 · 속도 목록 86-92 · 이름 조각 104 | 요청 24 **필수**(`^(main\|fire\|work)$`) · 49 · `projects.road_type` NULL 허용(`001_create_schema.sql:183`) | D03 추천 기준 `D03_PlanAnalysis_Router.py:212` → `D03_PlanAnalysis_Analyze.py:73-81`(없으면 `work`) · D04 연결 새 임도 종류 `D04_PlanConfirm_Link.py:258`(없으면 `work`) · 상세 표시 `B01_Dashboard_UI_ProjectDetail.ts:40 · 117`(null 안전) · E 그룹은 ROAD 만 원본(`E01_StabRegister_Router.py:30-36`) → 무관 | 화면만 지우면 서버 422 → 서버 요청 칸 선택으로 |
| 설계속도 | 67 · 86-92 | 요청 25 · `resolve_design_speed(grade_class_of(...))` 49 → `projects.design_speed_kph`(`020:14-15`) | D03 추천 기준(최소 R · 기울기 상한) · D04 새 임도 B05 단계 2 씨앗 `D04_PlanConfirm_Link.py:266-279 · 308` · `record_link` COALESCE 134 · 318 · 상세 116 · 202 | 임도 종류 없으면 `grade_class_of(None)` = `work` → 20 km/h(`B05_Profile_PageSettings.py:54-61` · `B05_Profile_Engine_Grade.py:110-121`) |
| 노선 시작 측점 | 69-83(옆 측점 표기 포함) · 검사 124 | 요청 26 · 44-48 · `projects.route_start_m`(`015_route_range.sql:13`) | D03 이은 누가 시작 `D03_PlanAnalysis_Router.py:258 · 322` · D04 내보내기 측점 시작 `D04_PlanConfirm_Router.py:205 · 312` · `D04_PlanConfirm_Repository.py:19` · 연결 SELECT `D04_PlanConfirm_Link.py:108`(값 안 씀) | 모두 `or 0.0` → No.0 부터 · 깨짐 없음 |
| 노선 종료 측점 | 70 · 검사 125 | 요청 27 · `projects.route_end_m`(`015:15-16`) | D03 `end_chainage_target_m` `D03_PlanAnalysis_Router.py:324-326` → 화면 「끝 · 목표 · 차이」 줄 `D03_PlanAnalysis_UI_Page.ts:398-409` | null 이면 그 줄만 안 보임 |
### 바꿀 곳
- `D01_PlanRegister/D01_PlanRegister_UI_Page.ts` — 칸 여섯 · 속도 목록 · 측점 표기 · 검사 줄 · 보내는 값 · `SPEED_CHOICES` · `GRADE_CLASS` · `stationLabel` import 지움 · 머리 주석.
- `D01_PlanRegister/D01_PlanRegister_Router.py` — `road_type` 선택(`str | None = None`) · 끝 연도 · 측점 검사는 그대로 두어도 무해(안 오면 건너뜀).
- DB 칸은 **그대로 둠**(기본 NULL · 마이그레이션 없음). 끝 연도는 D03 연도 나누기 [저장] 때 씀(2번).
- 시험 — `resources/tester/test_528_project_groups.py:100-128`(`road_type` 필수 전제) · `test_553_4_de_parts.py:19-23`(측점 표기 소스 검사 → 칸 지우면 실패 · 시험도 지움) · `test_557_2_part_fixes.py:112-121`(폼 격자 — 그대로 통과 예상).
### 위험 · 물음
- 임도 종류를 빼면 D04 연결로 만든 새 임도 프로젝트가 늘 「작업임도」(`D04_PlanConfirm_Link.py:258`) · 설계속도 20 씨앗 — 업체가 B 에서 고쳐야 함. 씨앗을 아예 안 넣으려면 서버가 속도를 NULL 로 저장(→ `seed_design_speed` 건너뜀 271).
- 항목 11(추천 엔진 빼고 손 직선)이면 D03 `plan_criteria` 자체가 안 쓰임 → 임도 종류 · 설계속도는 D 안에서 쓸 곳 없음.
---
## 2. D02 기준 지정 = 전체 시점 · 종점 하나
### 지금
- 정본 = 표 `plan_route_years`(PK project_id + year) 연도마다 `start_x/y · end_x/y · epsg`(TM m) + 분석 `route_json · fingerprint · confirmed_at`(`020_project_groups.sql:21-36`).
- API = `GET/PUT /api/projects/{id}/plan/points`(`D02_PlanBase_Router.py:183-206`) — 연도 범위 = `project_year ~ project_year_end`(`_view` 160-180) · 잇기 규칙 `chain_years` 45-68(직전 종점 = 다음 시점 · 빈 연도 거절) · TM 띠 = 평균 경도 `with_tm` 71-80 · 바뀐 연도만 쓰고 그 연도 분석 비움 `_store` 115-150 · 범위 밖 연도 행 지움 202.
- 화면 = 경계점 P0..Pn(`D02_PlanBase_UI_Page.ts:61-65` · 이름 92-97 · 연도 색 43 · 연도 선 125-142) · 캐시 = 날 `sessionStorage` 열쇠 `d02_points_<id>`(47 · 102-103 · 226 · 256-259 — `b_page_state` 등록표 밖).
- D03 가 쓰는 길
- 서버 `_plan_inputs`(`D03_PlanAnalysis_Router.py:98-140`) — 연도 행 = 구간(segments) · 빈 연도 · 좌표계 섞임 거절.
- 끈 지점 덮기 `_override` 76-95 · [저장] 때 D02 규칙으로 정본에 씀 `_store_points` 330-353.
- 지형 = 연도마다 DEM 폴더 `models_{year}`(`D03_PlanAnalysis_Terrain.py:54-56 · 195-210`) · 화면은 경로 정규식 `models_(\d+)/` 로 연도 읽음(`D03_PlanAnalysis_Api.ts:111-121`).
- 분석 저장본 열쇠 = 연도 + 두 점(`D03_PlanAnalysis_Analyze.py:93-98`).
- 화면 `loadPoints`(`D03_PlanAnalysis_UI_Page.ts:630-649`) → `nodesOf` · `pointsOf` · `nodesInReach`(`logic/D03_PlanAnalysis_Plan.ts:191-230`).
- D04 이후는 연도 행 단위(확정 · 업체 지정 `plan_route_assignees.year` · 내보내기 `by_year` · 대시보드 「받은 예정노선」 · 연결).
### 바꿀 곳
- 정본 자리(갈래 — 끝 목록) · `D02_PlanBase/D02_PlanBase_Router.py` — 연도 잇기 · 범위 빼고 두 점 하나 GET/PUT · TM 셈은 그대로.
- `D02_PlanBase/D02_PlanBase_UI_Page.ts` — 점 둘(시점 · 종점) · 선 하나 · 연도 색 · 이름 정리 · 캐시는 등록표(`b_page_state` `STATE_REGISTRY`)로 옮김.
- `D03_PlanAnalysis/D03_PlanAnalysis_Router.py` — `_plan_inputs` · `_override` · `_store_points` 를 「전체 두 점 + (나눈 뒤) 연도 조각」으로.
- `D03_PlanAnalysis/D03_PlanAnalysis_Terrain.py` · `D03_PlanAnalysis_Api.ts` — 연도별 DEM → 전체 한 벌(폴더 이름 · 정규식).
- `D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts` · `logic/D03_PlanAnalysis_Plan.ts` — 연도 노드 대신 두 점 + 손 직선(항목 11) + 연도 표시(항목 14).
- `D03_PlanAnalysis_Analyze.py` — 항목 11 로 추천 엔진이 빠지면 연도 구간 열쇠 자체가 없어짐.
- `projects.project_year_end` = D03 연도 나누기 [저장] 때 마지막 연도로 씀 → 대시보드 범위 표시 그대로 삶.
- D04(`D04_PlanConfirm_*`)는 연도 행이 D03 에서 만들어지면 손 안 댐.
- 시험 — `test_529_3_plan_points.py`(연도 잇기 규칙 전부) · `test_530_*`(D03 연도 구간) · `test_553_4_de_parts.py:26-34`(D02 부품 소스 검사).
### 위험 · 물음
- 이미 만든 PLAN 프로젝트의 연도 행 — 옮김 규칙(첫 연도 시점 + 마지막 연도 종점 = 전체 두 점?) 물음.
- 두 점이 바뀌면 D03 손 직선 · 연도 나눔을 어떻게 할지(지금 규칙 = 그 연도 분석 비움) 물음.
- `plan_route_assignees.year` 는 연도 int(FK 없음) — 연도 나눔이 바뀌면 지정 업체 연도가 남음 → 처리 규칙 물음.
---
## 3. 자동 이름 = 사업연도 + 사업지역 + 「노선 계획」
### 지금
- `D01_PlanRegister_UI_Page.ts:95-111` — B02 조합기(`B02_ProjRegister/B02_ProjRegister_UI_Name.ts:18-55` · 조각 year · region · type · 손대기 전 자동 · 고친 뒤 그 조각만 갈아 끼움) · 조각 = 연도 「시작~끝」 · 사업 지역 · 임도 종류 글.
- 사업 지역 칸 = `region`(라벨 `B02_Proj_Field_Region` 「사업 지역」 `ui_template_locale_b1.ts:210`) → `projects.region` VARCHAR(100)(`001_create_schema.sql:182`).
- 서버는 이름을 받기만(조립 안 함).
### 바꿀 곳
- `D01_PlanRegister_UI_Page.ts:96-109` 만 — year = 시작 연도 · type 조각 = `t("DE_Reg_KindPlan")`(「노선 계획」 `ui_template_locale_de.ts:91`) 고정 · 듣개에서 끝 연도 · 임도 종류 뺌. 조합기 · 서버 손 안 댐. 결과 꼴 「2026 홍천군 ○○면 노선 계획」.
### 위험 · 물음
- D04 연결 새 임도 이름 = `{예정노선 이름} {연도}`(`D04_PlanConfirm_Link.py:256`) → 「2026 홍천 노선 계획 2027」처럼 연도 두 번.
- 「자동 입력정보」 = 이름 칸만으로 봄 — 다른 칸까지인지 물음.
---
## 4. D02 지도 · 위성
### 지금
- D02 지도 = **카카오맵 JS SDK** 일반 지도(ROADMAP) — `D02_PlanBase_UI_Page.ts:22 · 105-115` → 공용 `ui_template/ui_template_kakao_map.ts:18`(SDK 주소) · 60-92(싣기) · 149 `new maps.Map(box, { center, level: 7 })`(지도 종류 지정 없음 = 일반). 형 정의 39-55 에 `MapTypeId` · `MapTypeControl` 없음.
- D02 코드에 국토지리정보원 · VWorld 타일 없음. 「국토지리정보원」 인상 = D03 평면(국토지리정보원 수치지형도 도엽 등고선 + VWorld 위성) 또는 카카오 지도 저작권 표기로 추정.
- 키 자리(값 안 적음) — 카카오 JS 키 = `.env` `KAKAO_JS_KEY` → `common_util/common_util_map_router.py:15-21`(`GET /api/map/kakao-key`) · VWorld 키 = `config_system.py` 에 없음 → 소스 하드코딩 폴백 `B04_PreProcess/B04_PreProcess_Engine_VWorld.py:22-29` · `B04_PreProcess_Engine_GisVector.py:17-22`.
- 이미 있는 위성 코드
- D03 · B04 · B05 — 프로젝트별로 **미리 받아 둔** VWorld 위성 한 장(`D03_PlanAnalysis_UI_Page.ts:456-491` · `B04_PreProcess_UI_MapLayers.ts:14 · 35` · B05 배수유역 위성 겹) — 지점 범위가 정해진 뒤에야 생김 → D02(범위 정하기 전) 에 못 씀.
- B01 대시보드 VWorld **Base** 타일 대리 `GET /api/dashboard/map/tile/{z}/{x}/{y}`(`B01_Dashboard_Router.py:218-230` · `B01_Dashboard_Map.py:17 · 56-58`) — 정지 그림 격자 · 끌기 · 확대 지도 아님.
- `maplibre-gl` 은 받아 두었으나 페이지에서 안 씀(`config/package.json:9` · `vite.config.ts:30`).
- 이동 쉬운 길 = 카카오 SDK 기본 기능 — `map.setMapTypeId(kakao.maps.MapTypeId.HYBRID)`(위성 + 지명) / `SKYVIEW` · 지형 음영 겹 `addOverlayMapTypeId(MapTypeId.TERRAIN)`(항목 5 참고감).
### 바꿀 곳
- `ui_template/ui_template_kakao_map.ts` — 형 정의에 `MapTypeId` · `setMapTypeId` 더함 · `editMap` 에 지도 종류 바꾸기 손잡이(선택 인자 · 기본 꺼짐 → B08 거리이정표 네 화면 그대로).
- `D02_PlanBase/D02_PlanBase_UI_Page.ts` — 「일반 · 위성」 바꿈 단추(부품 상자 버튼 · 고른 값 = 취향 `aislo:pref:` 로 기억 여부 물음).
### 위험 · 물음
- 카카오 내장 단추(`MapTypeControl`)는 우리 부품 상자 밖 꼴 — CLAUDE.md 「화면 요소는 부품 상자만」 → 우리 단추로 `setMapTypeId` 부르는 쪽 권함.
- 카카오 위성은 D03 VWorld 위성과 출처 · 촬영 시점이 다름(같은 자리라도 그림 다름).
---
## 15. D03 → D02 돌아가면 빈 화면 · 바뀐 지점 안 보임
### 지금
- D02 → D03: [>] 만 저장 뒤 이동(`D02_PlanBase_UI_Page.ts:239-245` · `ui-step-nav` detail 1 가로챔). 단계 아이콘(🧭) 누름은 `A00_Common/group_workflow.ts:72` `navigateTo` 직행 → 저장 없음 · D02 캐시 `d02_points_<id>` 남음.
- D03 → D02: D03 에 떠남 처리 없음 → [<] = `ui_template_workflow_layout.ts:253-260` 이 단계 단추 click → `group_workflow.ts:72` 직행. 저장 없음.
- D03 지점 끌기 = 캐시 `d03-plan` 의 `nodes`(TM m · `D03_PlanAnalysis_UI_Page.ts:197-202` · 등록 `A00_Common/b_page_state.ts:202`). 정본 반영은 D03 [저장](`PUT plan/analysis` → `_store_points`)뿐이고, [저장]은 모든 연도 분석이 있어야 켜짐(`D03_PlanAnalysis_UI_Page.ts:311-314`) → 분석 전 지점 고침은 정본에 갈 길 없음.
- D02 열 때 = 서버 GET + 자기 캐시만(`D02_PlanBase_UI_Page.ts:255-259`) · D03 캐시(TM) 안 읽음(D02 는 경위도 · 브라우저 좌표 변환 없음 — `_분석_363` A).
- → 「바뀐 정보 안 보임」은 확정 경로. 거기에 D02 옛 캐시가 길이만 맞으면 서버 값을 덮음(259) → D03 에서 저장한 새 지점 대신 옛 D02 값이 보일 수 있음.
- 「빈 화면」 후보(재현 전 추정 · 확정 못함)
1. D02 첫 읽기(세션 · `plan/points`) 실패 → catch 가 표 자리에 글 한 줄만 · `paint()` 안 불려 카카오 지도가 아예 안 만들어짐(`D02_PlanBase_UI_Page.ts:247-264`) → 지도 칸 통째로 빔.
2. 카카오 SDK 못 받음(키 · 도메인) → 지도 칸 숨김 + 안내 한 줄(`ui_template_kakao_map.ts:216-219`).
3. 라우터 · 바깥 틀 문제는 없음 — 이동마다 아울렛을 새로 만들고(`app_shell.ts:332-345`) D03 고리는 `root.isConnected` 로 멈춤(`D03_PlanAnalysis_UI_Page.ts:538 · 574 · 675`) · 잠금판은 세는 방식 · 짝 맞음.
- 확정 방법 = ORCA 로 D03 지점 끌기 → [<] → D02 에서 `orca eval` 로 `.ui-kakao-map__box` 크기 · hidden · 표 자리 글 · 콘솔 오류 확인.
- 쓸 만한 선례
- D02 [>] 자체(위) · B09 같은 꼴(`B09_Estimation/B09_Estimation_UI_Shell.ts:356-367`).
- B05 · B06 — 단계 이동 = `moveStage(projectId, target, steps)`(`A00_Common/b_workflow_nav.ts:133-190` · 잠금판 + 단계 차례 + 실패면 머묾 · target 에 함수를 주면 B 전용 길을 안 탐) · 부르는 자리 `A00_Common/design/design_page.ts:205-210` · 머리 메뉴 · 뒤로 가기 = `guardDesignLeave`(`A00_Common/design/design_leave.ts:102-129` · `setLeaveGuard` + `leaveOrStay` [취소][폐기][저장] `ui_template/ui_template_leave_dialog.ts:68`) · 라우터 쪽 `A00_Common/main.ts:89-99`.
- `design_pipeline.ts` 는 조작 중 뒤 계산 한 방향 — 페이지 이동 저장과 무관 · 재사용 대상 아님.
### 바꿀 곳
- `A00_Common/group_workflow.ts` — `mountGroupStep` 에 `beforeLeave?: () => Promise<boolean>` 하나 · 72 줄 `onStepClick` 에서 기다린 뒤 이동. [<] [>] 와 단계 아이콘이 모두 이 한 길(ui-step-nav 도 결국 단계 단추 click). 머리 메뉴 · 브라우저 뒤로는 같은 함수를 `setLeaveGuard` 로.
- `D02_PlanBase_UI_Page.ts` — 239-245 따로 듣개 지우고 `beforeLeave = store`(산림청 · 바뀐 것 있을 때만).
- `D03_PlanAnalysis_UI_Page.ts` — `beforeLeave` = 지점(두 점 · 손 직선) 저장 · 분석 없이도 되는 저장 길 필요 → `D03_PlanAnalysis_Router.py` 에 지점만 저장(또는 D02 PUT 이 TM 도 받음). 항목 11 재설계와 한 묶음.
- 저장 뒤 상대 캐시 비움(D02 `d02_points` · D03 `d03-plan` nodes) → 돌아간 쪽이 서버 새 값을 봄.
### 위험 · 물음
- 저장 실패 시 머묾 · 알림으로 갈지, B05 처럼 [취소][폐기][저장] 물음으로 갈지.
- 보기 전용(지정 회사)은 저장 없이 이동 — 지금 규칙 그대로.
---
## 갈래 갈리는 결정
1. **전체 시점 · 종점 정본 자리** — ⓐ `projects` 에 JSON 칸 하나(`plan_base_json` · 마이그레이션 한 줄) ⓑ 새 표 ⓒ `plan_route_years` 첫 연도 행을 임시로 씀(뜻이 섞임) ⓓ storage `D02_PlanBase/` 정본 파일. 권함 = ⓐ.
2. **연도 행(`plan_route_years`)을 누가 만드나** — D03 연도 표시(항목 14) [저장] 때 만들고 `project_year_end` 도 그때 씀 · 맞는지.
3. **옛 PLAN 프로젝트 옮김** — 첫 연도 시점 + 마지막 연도 종점 = 전체 두 점으로 접을지 · 버릴지.
4. **임도 종류 · 설계속도** — D 에서 아예 안 받음(D04 연결 새 임도 = 작업임도 · 속도 씨앗 없음) vs 숨은 기본값 유지.
5. **노선 시작 측점** — 늘 No.0 으로 충분한지(연차 사업 이어 셈이 필요했던 요구서 345 3-1 근거가 사라짐).
6. **자동 이름 범위** — 이름 칸만인지 · D04 연결 새 임도 이름의 연도 두 번 꼴 정리 여부.
7. **위성 바꿈 단추** — 우리 부품 단추 vs 카카오 내장 단추 · 위성(SKYVIEW) vs 위성 + 지명(HYBRID) · 고른 값 기억 여부.
8. **이동 저장 꼴** — 말없이 저장 후 이동(D02 [>] 꼴) vs 묻기(B05 [취소][폐기][저장] 꼴).
@@ -0,0 +1,276 @@
# 분석 389-B — 노선분석(D03) 요구 6~14
근거 = 코드 file:line · 실패 프로젝트 재현(읽기만) · 지식DB. 고친 파일 없음.
요약
- D03 = B05 노선 추천 엔진 · 갈래 넷 · 꺾임점 R 고침 · 예상 횡단을 들여온 화면 → 11번대로 걷어 내고 「직선 꺾은선 손 긋기」 화면으로 다시 세움.
- 8번 토스트 뿌리 = B04 도엽 파서가 `.cpg` 없는 도엽 dbf 를 latin-1 로 읽어 `등고수치` 칸이 깨짐 → 등고선 높이 0 → 지형면 실패. 7번 「더 받기」가 늘 켜진 것도 이 실패의 뒤끝.
- 아래 단계(D04 · B03 가져오기 · D01 · B01)는 모두 「연도 한 행 = 꺾은선 한 줄 · 연도끼리 이어짐」을 전제 → 갈래(6) · 조각(14)과 부딪힘 = 가장 큰 결정거리.
---
## A. 지금 구조 · 버릴 것 · 남길 것 · 아래 단계가 읽는 꼴
지금 — B05 노선 추천에서 온 덩이
| 덩이 | 자리 | B05 에서 빌린 것 |
| --- | --- | --- |
| 분석 엔진 | `D03_PlanAnalysis_Analyze.py:34-49` · 갈래 넷 `:53` · 구간 분석 `:179-220` · 비용면 캐시 `_derived/cost_<연도>` `:251` · 나란히 프로세스 `:277-330` | `generate_initial_route` · `load_terrain_and_streams` · `request_fingerprint` · 법정 기울기 · R 함수 |
| 파생 파일(543-4) | `route_<연도>_<지점 열쇠>.json` `Analyze.py:93-98` · 거르기 `:127-139` | — |
| 분석 API | `Router.py:208-222` POST analyze · `:294-327` GET analysis · `:356-415` PUT analysis(갈래 이름 + 꺾임점 고침 → 서버 다시 셈) | — |
| 갈래 고름 · 이은 누가 | 카드 `UI_Page.ts:85-90 · 320-371` · `logic/Plan.ts:71-147`(짝 `Analyze.py:333-400`) | — |
| 꺾임점 · R 고침(543) | `D03_PlanAnalysis_Edit.py` 전부 · `logic/D03_PlanAnalysis_Edit.ts` · `UI_Edit.ts` · `UI_Page.ts:223-237 · 258-279 · 373-395` | `node_polyline` · `route_metrics` · `hard_limits` · `RouteEdit_Edits` · `_Curve` · 집기 함수 |
| 연도 경계 지점 끌기 | `UI_Map.ts:316-357` · `UI_Page.ts:411-438` · `Router.py:76-95 _override` · `:330-353 _store_points` | — |
| 하 횡단 · 3D 코리도(544) | `UI_Cross.ts` · `logic/Expect.ts` · `UI_3D.ts:105-117` | `buildCrossSvg` · `sectionsGround` · `buildCorridor` · `createRouteViewer` |
| 설정 | `config_system_terrain.PLAN_ANALYZE_WORKERS` | — |
버릴 것
- `Analyze.py` · `Edit.py` · `logic/Edit.ts` · `UI_Edit.ts` · `UI_Cross.ts` · `logic/Expect.ts` · 갈래 카드 · [분석] 단추 · POST analyze · PUT 의 `choices`/`edits` · 연도 경계 지점 끌기 · `PLAN_ANALYZE_WORKERS`.
- storage `_derived/route_*` · `cost_*` · `models_<연도>` — 새로 안 씀 · 안 읽음(손으로 지우지 않음).
- 시험: `test_530_2_plan_analyze` · `test_530_3_plan_logic` · `test_543_plan_edit` · `test_544_1_plan_expect` 지움 · `test_530_2_plan_router` 대부분 다시 · `test_553_4_de_parts.py:36-50` D03 단언 고침(갈래 카드 · 3D 체크 칸 단언).
남길 것
- 지형 받기 골격 — `Terrain.py`(도엽 · 위성 · DEM · `plan.prj`) · 일 진행 `Router.py:143-166 · 225-231` · 모델 행 `:169-191`.
- 평면 바탕 — `UI_Map.ts` 위성 · 등고선 · 측점 눈금 · 좌표 변환 · 확대 이동(`bindRouteEditNavigation`).
- 측점 — `stationChainages` · `leadInLine`(`Plan.ts:150-176`) · `stationLabel`.
- 3D 모달(지형 + 선 + 측점 막대).
아래 단계가 읽는 꼴(`plan_route_years.route_json`)
| 읽는 곳 | 자리 | 읽는 칸 |
| --- | --- | --- |
| D04 내보내기 · 확정 | `D04_PlanConfirm_Router.py:159-175` | `planned`([x,y] 꺾은선) · `start_chainage_m` · `end_chainage_m` · 행 `epsg` |
| D04 요약 표 | `D04_PlanConfirm_Router.py:246-263` | `length_m` · `mode` · `planned[0]` · `planned[-1]` |
| D04 shp · xlsx | `D04_PlanConfirm_Export.py:204-270` | 연도 = LineString 한 줄 · 「전체」 = 연도 차례로 이어 붙임(`:255-260` 직전 종점 = 다음 시점 전제) |
| D04 연결 → 임도 | `D04_PlanConfirm_Link.py:74-81` 지문(`planned` 만) · `:153-158` shp 한 벌 · `:250-263` `length_m` | 같음 |
| B03 [예정노선에서 가져오기] | `B03_FileInput_UI_PlanImport.ts:54` → POST link(위 D04 Link 길) · B01 `UI_PlanLink.ts:32` 같음 | shp 한 벌 = 노선 하나 |
| D01 받은 예정노선 | `D01_PlanRegister_Router.py:67-99` | 지문 · `confirmed_at` |
| B01 산림청 홈 | `B01_Dashboard_Router_Home.py:140-157` | `route_json IS NOT NULL` · `confirmed_at` |
| E01 안정성평가 | `E01_StabRegister_Router.py:35` — ROAD 프로젝트만 복사 | D03 직접 소비 없음(D → 임도 연결 → E 간접) |
- 전제 = 연도 한 행 · 꺾은선 한 줄 · 연도끼리 이어짐 · 업체 지정도 연도 단위(`020_project_groups.sql:38-56`).
위험 · 물음
- D03 지점 읽기 = 연도별 `plan/points`(`Api.ts:71`) — 2번(D02 = 전체 시점 · 종점 하나)과 묶여 바뀜 · 389-A 몫과 꼴 맞춤.
- 안내 화면 `A05_EduDetail/guide/D03/` 02 · 04 · 07 통째 낡음.
---
## B. 지형 받기(7)
지금
- 열면 자동 받기 — `UI_Page.ts:693-694`(받을 범위를 먼저 안 보임).
- 범위 = 서버가 정함 — 도엽 = 지점 + 1 km 표본마다 3×3(`Terrain.py:43 · 181-187`) · DEM = 연도 구간 상자 + 350 m(`dem_outline :96-102` + B 여유 300 m) · 연도마다 `models_<연도>/`(`:195-210`).
- 「범위 밖 — 더 받기」 보임 = `UI_Page.ts:302-304` `!editable || !outside || busy` · outside = 마디가 연도 DEM 상자(300 m 안쪽) 밖 · **DEM 없으면 밖**(`Plan.ts:212-227`).
- 늘 켜진 까닭 — 지형 받기가 2026년 지형면에서 실패(C) → 연도 지형면 0 → 모든 마디 「밖」 → 단추 늘 보임 · 열 때마다 자동 다시 받기 → 같은 실패 반복(`tmp/server.log` 1330524~1331398 · 9번). 단추 글 「범위 밖」은 틀린 안내(실은 받기 실패).
바꿀 곳
- 두 걸음 — 범위 보기 → [이 범위 받기].
- 서버 ③ 새 `GET …/plan/terrain/extent` — 시점 · 종점(그은 뒤면 선 bbox) + 여유 → DEM 상자 네 귀(경위도) · 받을 도엽 번호 · 도곽 · 칸 수 어림 · 이미 받은 상자. 셈 = `Terrain.py` 함수 그대로(`neighbors_for_points` · `sheet5k_to_bounds` · `dem_outline`).
- 그림 자리 — 받기 전엔 위성 · 등고선이 없어 D03 캔버스는 빈 바탕(`fakeMeta UI_Map.ts:99-121`) → 카카오 지도 부품 `editMap.set(pins, lines)`(`ui_template_kakao_map.ts:105-114`) 에 닫힌 선으로 상자 · 도곽.
- 여유(m) 칸 하나 — 기본값 협의.
- DEM = 사업 전체 한 벌(`models_plan/` 류) — 연도는 D03 나눔 표(14)로 나중에 정해짐. `fetchYearSurfaces`(`Api.ts:111-127` `models_(\d+)` 열쇠) → 한 벌.
- 「더 받기」 = 그은 꺾임점 · 커서가 받은 DEM 밖 · 일 안 돎 · 직전 실패가 아님. 실패면 「지형 받기 실패 — 까닭」 + [다시 받기] 로 따로.
- 자동 받기(`:693-694`) 지움.
위험 · 물음
- 한 벌 DEM 크기 — 1 m 격자(`SHEET_SURFACE_GRID_M`) · 실패 프로젝트 넓이(약 4.7 × 2.5 km)면 약 1,200만 칸 · 라플라스 400번 → 시간 · 메모리 실측 필요 · 넓으면 D 만 2 m 격자 갈래.
- 카카오 지도 · 위성(4 · 5번)과 같은 부품 — 389-A 와 겹침 조율.
---
## C. 토스트 「구간 지형면을 만들지 못했습니다」(8)
지금
- 글 자리 — `D03_PlanAnalysis_Terrain.py:209-210` `raise ValueError(f"{year}년 구간 지형면을 만들지 못했습니다 — 도엽 등고선 확인")` → `Router.py:157-159` 진행 `error` → `UI_Page.ts:543` 토스트.
- 나는 조건 = `build_sheet_surface_model` 빈 목록(`B04_PreProcess_Engine_SheetSurface.py:475-554`):
1. 병합 등고선 파일 없음 · 못 읽음(`:64-80 · 502-504`)
2. 상자 안 등고 높이 2단 미만(`:513-517`)
3. 보간 방식 모두 예외(`:534-536`)
4. 유효 칸 0(`:353-356`)
- 실제 까닭(재현 · 읽기만)
- 실패 프로젝트 `4250c7bc…`(storage/1/3) 로그 「등고 라인 94단을 격자에 굽어 0셀」 → 2번.
- 2026년 상자(x 284818~286552 · y 409459~410472) 안을 지나는 등고선 96 개 모두 `등고수치 = null`.
- 병합 도엽 20매 중 15매 등고선 칸 이름 깨짐(`µî°í¼öÄ¡` = cp949 「등고수치」를 latin-1 로 읽은 글자).
- 깨진 도엽 zip 은 `.cpg` 없음(`36712088.zip`) · 성한 도엽은 `.cpg` 있음(`36716009.zip`).
- 뿌리 = `B04_PreProcess_Engine_SheetParser.py:60` `gpd.read_file(...)` 인코딩 지정 없음 → `.cpg` 없는 dbf = latin-1 → 높이 칸 못 찾아 `_rasterize_contour_levels`(`SheetSurface.py:246-251`)가 그 선을 버림.
- 번짐 — B 도엽 지형면 · 배수유역 등고선 · D03 등고선 라벨(`prepareLayer … ["등고수치"]` `UI_Page.ts:485`)도 같은 도엽이면 같은 흠(잠재). storage 병합 파일 16 개 앞머리 표본에서 깨진 것은 이 D 프로젝트만.
바꿀 곳
- `SheetParser.py:60` — `.cpg` 없으면 `encoding="cp949"`(공용 한 자리 · 한 줄).
- 병합 열쇠에 파서 판 번호 — `B04_PreProcess_Engine.py:531` · `D03 Terrain.py:116-117`(`{"sheets": …}` 그대로면 옛 깨진 병합본을 계속 씀).
- 시험 — `.cpg` 없는 cp949 dbf 한 장 → 칸 이름 `등고수치`.
- (덤) 토스트 글에 까닭 — 「상자 안 등고선 높이 못 읽음(도엽 N 매)」.
위험 · 물음
- B04 = 남의 페이지 공용(`B04_PreProcess/CLAUDE.md`) → B 쪽 시험도 같이.
- 고친 뒤 이 D 프로젝트는 받기 한 번 더(열쇠 바뀜 → 다시 병합 → 지형면 열쇠도 따라 바뀜).
---
## D. 하 횡단 판 지움(9)
지금 — 닿는 자리
- `UI_Page.ts:58`(import) · `:119` · `:122` · `:172` · `:218-221` · `:228` · `:389` · `:433-435` · `:440-454`(onStation) · `:658`(검증 창구).
- `UI_Cross.ts` 전부 · `UI_Style.css:28-66` · `UI_3D.ts:22`(`loadExpectStandard` 를 Cross 에서 가져옴) · `:63-73 · 105-117`(예상 코리도).
- `logic/Expect.ts` = Cross · 3D 만 씀 · 시험 `test_544_1` · `test_553_4:45`(3D `createCheckbox` 단언) · 안내 D03 04 · 06 · `_scenes.json`(`d03-plan__cross*` 11곳).
바꿀 곳
- 횡단 판 · `UI_Cross.ts` 지움 → 평면이 본문 전체.
- 3D 남김(G) · 예상 코리도는 같은 「예상 횡단」 셈 → `Expect.ts` · 코리도 체크 칸 · `test_544_1` 같이 지움.
- 측점 누름 = 할 일 사라짐 → 지움(측점 눈금 · 이름은 남김).
위험 · 물음
- 3D 코리도도 「원했던 방식 아님」에 드는지 — 추정 = 듦(G).
---
## E. B05 평면 상단 단추 · 마우스 · 단축키(10)
| 단추 | B05 자리 | 묶임 | D03 쓰는 법 |
| --- | --- | --- | --- |
| 되돌리기 · 다시하기 | `RouteEdit_Chrome.ts:86-89` · 스택 `RouteEdit_History.ts:98-141` · 단추 `:148-183` | 단추 · 단축키가 B05 페이지 사진첩(`onPageHistory :174`)에 묶임 | 안 씀 · D03 자체 스택(`UI_Page.ts:177 · 205-216 · 674-682`)에 다시하기만 더함 |
| 3D | `Chrome.ts:91` 부른 쪽 단추 자리 | B05 3D 보기 | D03 자체 `openPlan3d` 그대로 |
| 좌 · 우 회전 | `RouteEdit_Rotate.ts:52-97` · 단추 찾기 `:72-79`(`[data-act="rotate-ccw|cw"]`) · 15° · Ctrl+휠 5°(`RouteEdit_Input.ts:58-59` `rotateWheel`) | B05 의존 없음(단추 둘 + 캔버스 크기) | import 그대로 · `UI_Map` 그리기에 캔버스 회전 · 포인터 `at()`에 `unrotate` · 탐색에 `unrotateDelta` · `rotateWheel` |
| 거리 재기 | `RouteEdit_Measure.ts:1-17 · 184-` | 노선 위 두 점 · 지반고 = B05 서버 `/route/elevations`(`Api_Replan.ts:206`) | 안 씀 · F 의 순수 셈 + `heightAt` 로 두 점 길이 · 기울기 |
- 단추 모양 — Chrome 의 날 `<button>` HTML(`:82-122`) 복사 금지(CLAUDE.md 4장) → `ui_template_toolbar.createToolbar({glass: true, size: 32})` + `createIconButton({icon: "undo" | "redo" | "cube" | "ruler"})`.
- 회전 아이콘 — 부품 상자에 없음 · B05 `HALF_TURN_ICON`(`Chrome.ts:29-36`)은 B05 Chrome 모듈째 끌고 옴 → `ui_template_icons` 에 회전 둘 더함.
마우스 · 단축키 · 설정 연동
- 설정 값 = `aislo:pref:input-prefs` `{wheelZoom: "pull"|"push", cadAliases}`(`A00_Common/input_prefs.ts:11-23`) — 고칠 수 있는 마우스 값은 휠 방향 하나 · 나머지는 규칙 안내(`app_settings.ts:37-42` · `ui_template_locale_common.ts:121-131` 「가운데 버튼 끌기 = 화면 옮김 · 왼쪽 = 고르기 · 오른쪽 = 메뉴」) · 단축키 탭 = 읽기 전용 목록(`app_settings.ts:45-53` · locale `:146-174`).
- 휠 방향 — `bindRouteEditNavigation`(`RouteEdit_Input.ts:47-128`) → `wheelZoomsIn`(`touch_bridge.ts:37`) → `wheelEventIn`(`input_prefs.ts:54-64`) → D03 은 이미 따름(`UI_Map.ts:358-365`).
- 어긋남 — D03 `leftPan: true`(`UI_Map.ts:363`) = 왼쪽 끌기 팬 → 설정 규칙(가운데 = 팬)과 다름 · 그리기에서 왼쪽 = 점 찍기라 꼭 끔.
- 단축키 — 설정 ③ 목록에 「D03 노선분석」 줄 더함(`KEY_ROWS` + locale) · 키 버릇 = B05 종단 자유지정과 같게(locale `:152-155` Enter 끝 · Esc 취소 · Backspace 마지막 점 빼기) + Ctrl+Z · Ctrl+Y/Ctrl+Shift+Z.
위험 · 물음
- 회전 시 글자 세움(`Rotate.ts:24 UPRIGHT_LABELS`) — D03 글자(측점 · 등고 라벨 · 미리보기 값)에 `uprightRad` 적용 필요.
- `B05_Profile/CLAUDE.md` 「남의 페이지가 부르는 파일」에 `UI_RouteEdit_Input` · `_View` · `_Rotate` 빠짐(지금도 D03 가 부름).
---
## F. 수동 그리기 설계(11~14)
있는 것 그대로
- 지도 누름 · 좌표 — `UI_Map.ts:301-304 at()` · `screenToMetric`(B05 `_View`) · `toScreen`.
- 지반고 — `common_util_surface_sampler.ts:65-82 heightAt`(짝 `DtmGridSampler.sample_xy` · 거울 `test_344_1`) · 격자 = 받은 DEM 평활본.
- 등고선 — `UI_Page.ts:456-491`(도엽 등고선 bbox 자름) · `UI_Map.ts:200-216`.
- 집기 · 붙음 — B05 `RouteEdit_Input.ts` `nodeAtScreen :166` · `segmentAtScreen :251` · `routePointAtScreen :290`(순수 · D03 이미 씀).
- 되돌리기 — `UI_Page.ts:177 · 205-216`(50 걸음).
- 측점 표기 — `stationLabel(…, 20, 1)`(`61+14.5`) · `drawStationTicks` · `stationChainages`.
- 영선 근거 — `resources/knowledge/original/산림과임업기술(임도)/2. 임도/3. 임도계획.md:287-297` 양각기 계획법 D = 100·h/G · `:336-337` 한 폭씩 진행 = 사용자가 말한 「원지반 기울기만큼 긋기」.
데이터 꼴(안)
```
plan_network — D03 [저장] 정본 · 프로젝트 하나에 한 벌
{ epsg,
lines: [ { id, vertices: [[x, y], …], branch: null | { line, vertex } } ], // 직선만 · R 없음
pieces: [ { line, from_m, to_m, year } ] } // 나눔 표 = 조각 경계
```
- z 는 안 저장 — 화면 · 서버가 같은 DEM 에서 늘 다시 읽음.
- 첫 선 = D02 시점에서 · 끝 = D02 종점에 붙으면 끝남.
- 갈래(6) — 둘째 선부터 첫 점을 기존 선 꺾임점(선 위 점이면 꺾임점 끼움)에 붙임 · `branch` 칸.
- 나눔 표(14) — 다 그은 뒤 [구역 나누기] 모드 · 선 위 누름 = 표(누가 m) · 조각마다 연도 · 같은 연도 여러 조각(6) 허용.
미리보기 셈(순수 · 새 `logic/D03_PlanAnalysis_Draw.ts`)
- 자유 — p = 커서 · L = |p − 끝점| · S = (z(p) − z(끝점)) / L × 100 · NaN → 「지형 밖」.
- 길이 고정 L0 — p = 끝점 + L0·u(u = 커서 쪽 단위 방향) · S 보임.
- 기울기 고정 G — 커서 방위 반직선에서 f(t) = z(끝점 + t·u) − z(끝점) − (G/100)·t = 0 인 t 중 커서 거리에 가장 가까운 것 · 1 m 걸음 부호 바뀜 → 이분 10번 · 없으면 자유 점 + 「이 방향엔 G % 자리 없음」.
- 둘 다(양각기) — 반지름 L0(기본 100·h/G · h = 도엽 주곡선 5 m) 원 위 360 방위 표본 → z = z(끝점) ± G·L0/100 인 방위를 후보 점으로 · 커서는 가까운 후보로 붙음.
- 값 표시 — 커서 옆 「L m · S %」 · 그은 구간마다 같은 값 라벨.
서버 [저장](① 짝)
- 받는 것 = 선 · 조각만 → 서버가 길이 · 구간 기울기 · 조각 누가 다시 셈(새 `D03_PlanAnalysis_Draw.py` 짝 · 거울 시험) → `plan_network` + 연도 행 파생.
- 새 API — GET · PUT `…/plan/network` · GET `…/plan/terrain/extent` · 지움 — POST analyze · GET/PUT analysis · 남김 — POST terrain · GET progress.
- 캐시 `d03-plan`(`b_page_state.ts:201-202`) 꼴 바뀜 → `version` 올림.
옛 연도 파일 · 행과 잇기
- `plan_route_years` = 연도별 파생 행으로 남김(D04 · D01 · B01 이 읽음).
- `route_json` = `{mode: "manual", planned, pieces: [{line, planned, start_chainage_m, end_chainage_m, length_m, segments: [{length_m, slope_pct}]}], length_m, start_chainage_m, end_chainage_m}` · `planned` = 그 연도 조각이 하나일 때 그 꺾은선(옛 읽는 곳 그대로 돎).
- `start_x…end_y` = 첫 조각 끝점 · 조각이 사라진 연도 행 = 지움(지정 업체 있으면 막거나 물음).
- 정본 자리 — (가) 새 표 `plan_networks`(project_id PK · D02 전체 시점 · 종점 칸 + `network_json`) (나) storage `D03_PlanAnalysis/plan_network.json`. 추천 (가) — 2번 D02 전체 시점 · 종점과 한 행.
위험 · 물음
- 갈래 · 조각 → 「연도 = 꺾은선 한 줄」 전제 깨짐 → D04 내보내기(`Export.py:255-260` 이어 붙임) · 업체 지정(연도 단위) · B03 가져오기(shp 한 벌 = 노선 하나) 손봐야 함.
- 갈래 측점 — 본선 누가 이어감 vs 갈래마다 0 부터.
- D01 시작 측점 칸 뺌(1번) → 본선 시작 누가 = 0.
---
## G. 3D · `buildCorridor`
지금
- `UI_3D.ts:18` `buildCorridor` · `:105-117` 예상 단면 → 코리도 · 노면 = 지반고(PLAN 544 정함) · 연도 고름 `:56-62` · 지형 = 연도 모델.
- 선 = `viewer.markers.renderRoute`(`B05_Profile_UI_Markers.ts:251-`) — 지반에 붙여(drape) 한 줄만 그림(`:255 disposeGroup`).
판단
- R · 종단 없는 꺾은선 = 계획고가 지반 그대로 → 절 · 성토 0 · 꺾인 자리 단면 겹침 → 코리도 뜻 없음 → 지움.
- 3D 자체(지형 + 선 drape + 측점 막대) = 선이 능선 · 계곡을 어떻게 넘는지 보는 쓸모 → 남김.
바꿀 곳
- `UI_3D.ts` — 연도 고름 → 선(갈래) 고름 · 지형 = 한 벌 모델 · 코리도 · 체크 칸 지움.
위험 · 물음
- 갈래 여러 줄 동시 보기 = B05 `Markers` 고침 필요(지금 범위 밖) → 한 줄씩 고름으로 시작.
---
## 갈래 갈리는 결정
1. 갈래 · 조각을 아래 단계에 넘기는 단위 — (가) 연도 행 하나에 조각 여럿(`route_json.pieces`) · 업체는 연도 단위 · 가져오기 때 조각 고름 (나) 조각마다 행(새 표) · 업체 지정도 조각 단위.
2. 정본 자리 — 새 표 `plan_networks` vs storage 파일.
3. 지형 — 사업 전체 한 벌 vs 연도 · 조각별(추천 한 벌).
4. 받기 미리보기 지도 — 카카오 지도(받기 전에도 바탕 있음) vs D03 캔버스(받기 전 빈 바탕).
5. 기울기 고정 뜻 — 반직선에서 그 기울기 자리 vs 양각기 원 후보 · 둘 다 넣을지.
6. 기울기 부호 — 오르막 · 내리막 구분 vs 절댓값.
7. 갈래 측점 — 본선 이어 셈 vs 갈래마다 0 부터.
8. 3D 예상 코리도 지움 여부.
9. 옛 엔진 꼴 저장본 · 연도 행 — 그대로 둠(읽기 꼴만 맞춤) vs 비움.
10. D 지형 격자 — 1 m 그대로 vs D 만 2 m.
11. [초기화] — 빈 선 vs 마지막 저장본.
12. 미리보기 기본값(여유 m · 길이 걸음 · 기울기 %) — 협의(지식DB 는 후보만).
---
## 일 나누기(파일 안 겹침)
| 덩이 | 요구 | 파일 | 앞 조건 |
| --- | --- | --- | --- |
| W1 도엽 인코딩 | 8 | `B04_PreProcess_Engine_SheetParser.py` · `B04_PreProcess_Engine.py`(병합 열쇠) · 새 시험 | 없음 · 바로 |
| W2 서버 지형 | 7 | `D03_PlanAnalysis_Terrain.py` · `D03_PlanAnalysis_Router.py`(terrain · extent · progress 만 남김) · `test_530_1` · `test_530_2_plan_router` | W1(병합 열쇠 판 번호) |
| W3 셈 짝 | 12~14 | 새 `logic/D03_PlanAnalysis_Draw.ts` · 새 `D03_PlanAnalysis_Draw.py` · 새 거울 시험 | 없음 |
| W4 서버 정본 | 6 · 11 · 14 | 새 `D03_PlanAnalysis_Network.py`(GET · PUT network · 연도 행 파생) · `main.py` 한 줄 · 새 `db_management` SQL · `Analyze.py` · `Edit.py` · `test_530_2_plan_analyze` · `test_543` 지움 · `config_system_terrain.py` 한 줄 | W3 함수 꼴 · 결정 1 · 2 |
| W5 평면 · 그리기 | 10~13 | `UI_Map.ts` · 새 `UI_Draw.ts`(`UI_Edit.ts` 자리) · 새 `UI_Measure.ts`(작음) | W3 |
| W6 페이지 · 패널 · 3D | 6 · 9 · 14 | `UI_Page.ts` · `UI_3D.ts` · `Api.ts` · `UI_Style.css` · `UI_Cross.ts` · `logic/Expect.ts` · `logic/Edit.ts` · `logic/Plan.ts` 정리 · `b_page_state.ts` 한 줄 · `test_553_4` · `test_544_1` · `test_530_3` | W4 API 꼴 · W5 겹 꼴 |
| W7 부품 · 설정 | 10 | `ui_template_icons.ts`(회전 둘) · `ui_template_locale_common.ts`(D03 단축키 줄) · `A00_Common/app_settings.ts`(`KEY_ROWS`) | 없음 |
| W8 아래 단계 | 6 · 14 | `D04_PlanConfirm_Export.py` · `_Router.py` · `_Link.py` · `B03_FileInput_UI_PlanImport.ts` · `B01_Dashboard_UI_PlanLink.ts` · `D01_PlanRegister_Router.py` | 결정 1 |
| W9 안내 | — | `A05_EduDetail/guide/D03/` | 맨 끝 · 요청 때 |
- 겹치는 이음 — W5 ↔ W6(`UI_Map` 장면 · 사건 꼴) · W4 ↔ W6(API 꼴)은 PLAN 에 먼저 적고 시작.
- `main.py` 는 W4 만 · `UI_Page.ts`(698 줄) 는 W6 만 — 덜어 내면 줄어듦.
@@ -0,0 +1,103 @@
# 389-C 분석 — 기준 지정 위성 · 위성 + 등고선 (요구 389-4 · 389-5)
## 결론
- 기준 지정(D02) 바탕지도 = **카카오 일반지도**(국토지리정보원 지도 아님). 카카오 SDK가 이미 실려 있어 **스카이뷰(위성) 전환은 몇 줄 수준**.
- 노선분석(D03)은 이미 **브이월드 위성 + 수치지형도 등고선**을 겹쳐 그림. 다만 위성을 한 장으로 이어 붙인 그림(최대 zoom 18 · 넓으면 더 낮음)이라 확대하면 흐림.
- 가장 빠르고 상업적으로 안전한 길 = **D02 카카오 스카이뷰 · 하이브리드 전환 단추**(카카오가 영리 서비스의 스카이뷰 사용을 「활용 가능」으로 답함 · 하루 30만 건 무료).
- 카카오내비 수준(위성 + 등고선, 빠른 확대) = GPU 지도 엔진 + 타일 피라미드 방식. 우리 쪽 대응 수단 = 이미 설치만 되어 있고 안 쓰는 **MapLibre GL** + 브이월드 위성 타일 + 우리 등고선. 큰 작업이라 따로 협의할 일.
## 1. 지금 코드
| 화면 | 바탕 | 출처 · 방식 | 파일 |
| --- | --- | --- | --- |
| D02 기준 지정 | 카카오 일반지도(ROADMAP 기본값 · 지도 종류 지정 없음) | 카카오 지도 JS SDK · 키 = 서버 `/api/map/kakao-key`(.env `KAKAO_JS_KEY`) · 표지 · 연도 선만 | `ui_template/ui_template_kakao_map.ts`(공용 · B08 거리이정표도 씀) · `D02_PlanBase/D02_PlanBase_UI_Page.ts` |
| D03 노선분석 | 브이월드 **Satellite** 한 장 + **도엽 등고선**(`등고수치`) | 서버가 WMTS 타일을 받아 PNG 한 장으로 이어 붙임(zoom 18 → 한 변 16장 넘으면 한 단씩 낮춤 · 최저 14) · Canvas 2D로 그림 | `D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts`(`loadBackground`) · `D03_PlanAnalysis_UI_Map.ts` · `B04_PreProcess/B04_PreProcess_Engine_VWorld.py` · `config/config_system.py`(`SURFACE_MAP_MAX_ZOOM=18` · `MAX_TILES_PER_SIDE=16`) |
| B01 회사 주소 지도 | 브이월드 **Base** 타일 | 서버 대리 받기 `/dashboard/map/tile/{z}/{x}/{y}`(키 숨김 · 1시간 캐시) | `B01_Dashboard/B01_Dashboard_Map.py` · `B01_Dashboard_Router.py` |
- 지도 라이브러리: 카카오 SDK(D02 · B08) · 자체 Canvas 2D(B04 · B05 · D03). Leaflet · OpenLayers 없음.
- **MapLibre GL 5.24** = `config/package.json` · `vite.config.ts` 별칭까지 있으나 쓰는 TS 파일 0.
- 바로e맵(국토지리정보원 emap) 사용처 없음.
- 우리 등고선 능력:
- LAS 지표면 모델 → 등고선(marching squares · 간격 지정 · 캐시): `B04_PreProcess/B04_PreProcess_Engine_Contour.py` · `B04_PreProcess_Router_Contour.py`
- 수치지형도 도엽 등고선(브이월드 도엽 내려받기 → GeoJSON `도엽_등고선`): `B04_PreProcess_Engine_SheetStore.py` · `B04_PreProcess_Engine_SheetParser.py` · `A00_Common/b_asset_cache.ts`
- LAS 우선 · 없으면 도엽 고르기: `B05_Profile/B05_Profile_UI_RouteEdit_Contour.ts`
- 벡터 타일(MVT) 서버 길: `B04_PreProcess_Engine_MvtHelper.py` · `B04_PreProcess_Router_GIS.py`
- 덧붙임(위험): 브이월드 API 키가 `.env` · `config_system.py`에 없고 `B04_PreProcess_Engine_VWorld.py` · `B04_PreProcess_Engine_GisVector.py`의 **코드 안 기본값**으로 들어 있음 — 상용 전 `.env`로 옮길 일.
## 2. 출처별 사실
### 카카오 지도 JS API
- 바탕 종류 = ROADMAP · SKYVIEW · HYBRID. 겹침 종류 = OVERLAY · ROADVIEW · TRAFFIC · TERRAIN(지형도) · BICYCLE · BICYCLE_HYBRID · USE_DISTRICT(지적편집도) — `setMapTypeId` · `addOverlayMapTypeId`.
- 일반지도 · 지형도 타일은 글자가 박혀 있어 끌 수 없음 · 글자 없는 바탕은 **스카이뷰뿐**(카카오 답변). TERRAIN을 일반지도에 겹치면 글자 두 겹.
- TERRAIN = 음영기복(산 그림자) 겹침으로 보임 — **높이 숫자 달린 등고선은 아님**(화면 확인 필요 · 3절 미확인).
- 우리 등고선을 카카오 위에 올리는 길 = 선마다 `Polyline`(많으면 느림) 또는 커스텀 타일셋(`kakao.maps.Tileset` · 카카오 고유 EPSG:5181 격자에 맞춰 서버가 그림을 만들어야 함) — 둘 다 무거움.
- 이용 조건:
- 영리 부동산 서비스의 스카이뷰 · 하이브리드 사용 = 「활용 가능」 · 출처 표기 의무 없음 · 촬영 시점 정보 미제공(카카오 답변).
- 데이터 **서버 저장 불가 · 실시간 호출만**(로컬 API 답변 · 지도 그림 캡처를 도면 · 보고서에 넣는 일은 피할 것).
- 무료 = 웹 JS SDK **하루 300,000건**(지도 객체 `new kakao.maps.Map()` 한 번 = 1건 · 타일 조각은 안 셈) · 넘으면 0.1원/건(비즈월렛 연결 시) · 월 전체 3,000,000건.
- 무료 쿼터 = 개발자 계정의 **첫 운영 앱만**(2026-07-21 개편 · 그 전 활성 앱은 기존 쿼터 유지) · 테스트 앱은 소량(운영 불가).
- JS 키에 **도메인 등록 필수**(지금 코드에 미등록 안내 문구 있음).
### 브이월드(국토교통부 공간정보 오픈플랫폼) WMTS
- 주소 꼴 `api.vworld.kr/req/wmts/1.0.0/{key}/{layer}/{z}/{row}/{col}.{ext}` · EPSG:3857 · 레이어 Base · white · midnight · Hybrid(png) · **Satellite(jpeg)**.
- zoom: Base · Hybrid · **Satellite 6~19** · white · midnight 6~18. z19 ≈ 0.24 m/화소(위도 37°).
- 이용허락 = **공공누리 제1유형(출처표시 · 상업 이용 · 변경 가능)** · 무료 · 개발 · 운영 자동승인.
- 키 = 회원 가입 → 인증키 신청(서비스 URL · 목적 입력) · 하루 제한량 초과 오류(`OVER_REQUEST_LIMIT`)가 있으나 WMTS 한도 숫자는 공개 문서에 없음.
- 우리는 이미 서버 대리 받기 길이 있음(B01) — 키 숨김 · 브라우저 CORS 걱정 없음.
### 국토지리정보원 바로e맵(국토정보플랫폼 오픈API)
- 배경지도 묶음에 영상지도 · 위성지도 · 하이브리드 포함 · WMTS · 공공누리 제1유형 · 무료 · 회원 서비스.
- **EPSG:5179 · OpenLayers 6.4.3 전용 예제** — 카카오(EPSG:5181)와도 우리 3857 길과도 안 맞음 · OpenLayers 미설치 → 이번 일에 부적합.
## 3. 카카오내비가 빠른 까닭(판단)
- 카카오맵 · 내비 앱 = 2023-08 공개된 **벡터 방식 SDK**와 같은 엔진(GPU로 선 · 글자를 그림). 위성은 미리 잘라 둔 **사진 타일 피라미드**(줌마다 해상도가 다른 장) + CDN.
- 그래서 확대할 때 그 줌에 맞는 사진 조각만 새로 받고, 등고선은 벡터라 깨지지 않음.
- 우리 D03 = 사진 **한 장**(정해진 zoom) + Canvas 2D 다시 그리기 → 확대하면 흐리고, 범위가 넓을수록 더 흐림.
- 같은 꼴을 웹에서 만드는 표준 수단 = MapLibre GL(WebGL) — 사진 타일 소스 + 등고선 벡터(GeoJSON · MVT) 겹. 우리에게 셋 다 이미 있음(라이브러리 · 위성 타일 대리 길 · 등고선 GeoJSON · MVT 서버).
## 4. 선택지 비교
| 선택지 | 출처 · 이용 조건 | 화질 · zoom | 우리 코드 손 | 속도 |
| --- | --- | --- | --- | --- |
| **A. D02 카카오 스카이뷰 · 하이브리드 전환** (+ 원하면 TERRAIN 음영 겹침) | 카카오 · 영리 활용 가능 답변 · 하루 30만 건 무료 · 도메인 등록 · 저장 금지 | 항공사진 · 레벨 1~14 · 등고선 숫자 없음 | `ui_template_kakao_map.ts`에 지도 종류 인자 + 전환 단추(부품 상자) — **수십 줄** · D02 한 줄 | 카카오 CDN · 빠름 |
| B. D03 위성을 줌별 타일로(한 장 이어 붙이기 대신) | 브이월드 · 공공누리 1유형(출처표시) · 무료 | Satellite z19까지 · 확대해도 선명 | B01 대리 길에 레이어 인자 + D03 Canvas가 보이는 타일만 받아 그림 — **중간**(수백 줄 · 좌표 3857↔TM 맞춤) | 처음 장면은 지금보다 빠름 · 서버 캐시 필요 |
| C. MapLibre GL 지도로 바꿈(위성 타일 + 우리 등고선 벡터) | 브이월드(위성) + 우리 데이터(등고선) · 카카오 타일은 못 씀 | z19 · 등고선 벡터라 깨짐 없음 · 내비와 같은 꼴 | D02만이면 중간 · D03은 끌기 · 측점 · 꺾임점 고침이 Canvas B 부품 위라 **큼** | GPU · 가장 빠름 |
## 5. 권고
1. **지금**: A — D02에 [일반 · 위성(하이브리드)] 전환. 기본값 = 일반지도 유지(요구 그대로). 공용 부품이라 B08도 원하면 같이 씀.
2. **D02에서 등고선까지**: 카카오 TERRAIN은 음영만이라 요구 5의 「등고선」과 다름 — 등고선이 필요한 일은 이미 D03이 함. D02에 꼭 필요하면 사용자 협의.
3. **내비 수준 속도 · 선명도**: 다음 단계로 B(D03 줌별 위성 타일) 먼저 · C(MapLibre 전환)는 D03 조작 부품 이전 비용이 커서 따로 계획.
4. 위성 출처가 화면마다 다름(D02 카카오 · D03 브이월드) — 촬영 시점 차이로 숲길 · 개간지 모양이 조금 다를 수 있음. 같은 사진을 원하면 D02도 브이월드(B)로 맞추는 길.
## 6. 확인 못 한 것
- 카카오 TERRAIN이 스카이뷰 위에서 어떻게 보이는지 · 등고선 선 유무 — ORCA 화면 5분 확인 거리.
- 카카오 스카이뷰 · 브이월드 영상의 원 해상도 · 촬영 연도(두 곳 모두 공식 미공개 · 카카오 2009년 발표 = 전국 50 cm급).
- 브이월드 WMTS 하루 한도 숫자 · 상용 서비스 URL 등록 범위(지금 키가 어느 URL로 등록됐는지).
- 카카오 상업 이용 = 개별 답변 근거 — 출시 전 우리 서비스 설명으로 데브톡 공식 확인 권장.
## 출처
- 카카오 지도 API 문서 · 샘플: https://apis.map.kakao.com/web/documentation/
- 카카오맵 API 공통 · 쿼터 개편(2026-07-21): https://developers.kakao.com/docs/ko/kakaomap/common
- 카카오 쿼터 표(웹 JS SDK 300,000건 · 0.1원): https://developers.kakao.com/docs/ko/getting-started/quota
- 카카오 무료 쿼터 개편 공지: https://devtalk.kakao.com/t/api-notice-on-new-kakao-map-api-features-and-free-quota-policy/150222
- 지형도 글자 겹침 · 스카이뷰만 글자 없음: https://devtalk.kakao.com/t/topic/136971
- 스카이뷰 영리 활용 가능 답변: https://devtalk.kakao.com/t/skyview/151446
- 영리 서비스 · 서버 저장 불가 답변: https://devtalk.kakao.com/t/api/150986
- 쿼터 집계(타일 로드 미차감): https://devtalk.kakao.com/t/web-sdk/150932
- 커스텀 타일셋: https://devtalk.kakao.com/t/x-y/119619
- 카카오 MapTypeId 목록(비공식 래퍼): https://lustre-kakaomap.hexdocs.pm/lustre_kakaomap/types.html
- 카카오 벡터 SDK 공개: https://devtalk.kakao.com/t/android-ios-sdk-8-23/130645
- 스카이뷰 50 cm급 발표: https://www.kakaocorp.com/page/detail/6256
- 브이월드 WMTS 가이드: https://www.vworld.kr/dev/v4dv_wmtsguide_s001.do
- 브이월드 오류 코드(OVER_REQUEST_LIMIT): https://www.vworld.kr/dev/v4dv_wmsguide2_s001.do
- 국토교통부 WMTS/TMS API(공공누리 1유형 · 무료 · 3857): https://www.data.go.kr/data/3046388/openapi.do
- 국토지리정보원 영상지도 · 배경지도 API: https://www.data.go.kr/data/15059358/openapi.do
- 국토정보플랫폼 ngii맵(EPSG:5179 · OpenLayers 6.4.3): https://www.ngii.go.kr/nlsc/service/api1
@@ -0,0 +1,48 @@
# 요구서 389 — 예정노선 생성 · 기준 지정 · 노선분석(수동 직선 노선)
원문 = `사용자요구_1025.md` 요구 389(15 항목) · 근거 = `_분석_389-A_생성_기준지정.md`(1~5 · 15) · `_분석_389-B_노선분석.md`(6~14) · `_분석_389-C_위성_등고선.md`(4 · 5) · 계획 = `PLAN.md` 560~563.
## 1. 지금
- D01 생성 칸 여섯(사업연도 끝 · 종류 · 임도 종류 · 설계속도 · 시작 · 종료 측점)은 DB 칸을 그대로 두고 화면에서 빼도 아래 단계가 안 깨짐 — 서버 필수 조건은 임도 종류 하나.
- D02 기준 지정 = 연도마다 행(직전 종점 = 다음 시점) · D03 의 지형 · 분석 · 저장이 모두 이 연도 구간 단위.
- D02 지도 = 카카오 일반 지도(국토지리정보원 아님) · 카카오 위성(HYBRID)은 바꿈 단추 하나로 됨 · 상용 사용 가능 · 하루 30만 회 무료.
- D03 위성 = VWorld 영상 + 도엽 등고선을 한 장으로 붙인 그림(줌 18 까지 · 확대하면 흐림) · 카카오네비 꼴(줌마다 조각 타일)은 뒤로 미룸.
- D03 노선 = B05 노선추천 엔진(비용면 · 갈래 넷 · 꺾임점 R) 그대로 — 요구 11 로 걷음 · 지형 받기 · 지도 바탕 · 측점 표기는 남김.
- 토스트 「구간 지형면을 만들지 못하였습니다」 원인 = 도엽 20 장 중 15 장에 `.cpg` 없음 → B04 도엽 읽기가 글자 인코딩을 잘못 읽어 등고 높이가 다 빔(B 프로젝트도 같은 숨은 흠) → 지형면 0 → 모든 점이 「범위 밖」이라 [범위 밖 더 받기]가 안 꺼짐 · 열 때마다 다시 받음.
- D03 → D02 빈 화면 = D02 첫 불러오기 실패 때 지도를 안 만듦(추정) · 바뀐 지점 안 보임 = D03 떠날 때 저장 없음(확정).
## 2. 바뀌는 흐름
```
D01 생성 이름 · 사업연도(시작) · 사업 지역 · 메모만 → 이름 자동 「<시작 연도> <사업 지역> 노선 계획」
D02 기준 전체 시점 · 종점 두 점만 · 지도 [일반][위성]
D03 분석 지형 범위 미리 봄 → [이 범위 받기] → 시점에서 직선을 이어 그어 종점까지(미리보기 길이 · 기울기)
→ 같은 연도 갈래 선 더함 → 선 위에 구역 마크 → 구역마다 연도 → [저장] = 연도 행 · 끝 연도 자동
D04 확정 연도 행 그대로 읽음(구역 · 갈래 단위로)
```
## 3. 정함(브레인) — 바꿀 수 있음
1. 전체 시점 · 종점 = 프로젝트 행에 칸 하나(`plan_base_json`) · 옛 프로젝트 = 첫 연도 시점 + 마지막 연도 종점으로 접음.
2. 연도 행(`plan_route_years`) · 사업연도 끝 = D03 [저장] 때 구역 연도로 만듦(아래 단계는 지금처럼 연도 행을 읽음).
3. 임도 종류 · 설계속도는 D 에서 안 받음 · D04 로 만드는 임도 프로젝트는 B 기본값(작업임도 · 20 km/h) → B 프로젝트에서 바꿈 · 측점은 늘 No.0 부터.
4. 위성 = 카카오 위성 + 지명(HYBRID) · 부품 상자 단추 [일반][위성] · 고른 값 기억.
5. 페이지 이동(D02 ↔ D03 · 단계 아이콘 포함) = 묻지 않고 저장 뒤 이동 · 저장 실패면 머묾.
6. 노선 정본 = 새 표 하나(프로젝트당 한 행 · 선 · 갈래 · 구역 JSON) · 높이는 저장 안 함(지형에서 읽음).
7. 지형 = 사업 전체 한 벌(연도를 나누기 전에 그리므로) · 받기 전 범위는 카카오 지도에 미리 보임 · 자동 다시 받기 없음.
8. 미리보기 = 마지막 점 → 마우스까지 길이(m) · 원지반 기울기(%, 오르막 + / 내리막 −) · 기본은 자유 · 제약 셋 — 길이 고정 / 기울기 고정(마우스 방향에서 그 기울기가 되는 첫 자리) / 둘 다(양각기 꼴 · 원 위 후보) · 제약 칸 처음 값 = 길이 20 m(측점 간격 · 지식DB 설계제원_총괄) · 기울기 9 %(간선 20 km/h 일반지형 상한 · 지식DB 종단선형).
9. 갈래 = 본선의 점에서 갈라지는 선 · 갈래 측점은 갈래마다 No.0 부터.
10. 구역 마크 = 선 위를 눌러 놓는 점 · 마크 사이 구간마다 연도 고름 · 갈래도 같은 방법.
11. 아래 횡단 패널 · 예상 횡단 · 3D 예상 코리도 걷음 · 3D = 지형 + 그은 선.
12. 도구 = 되돌리기 · 다시하기 · 3D · 좌회전 · 우회전 · 거리재기(B05 회전 코드 그대로 · 되돌리기 · 거리재기는 D03 자체) · 마우스 = 설정 창 값(가운데 단추 이동 · 휠 방향) · 지금 D03 왼쪽 끌기 이동은 끔(그리기와 겹침) · D03 단축키(Enter 끝 · Esc 취소 · Backspace 마지막 점 · Ctrl+Z/Y)를 설정 창 단축키 표에 올림.
13. [초기화] = 시점 · 종점만 남은 빈 선 · 옛 엔진 저장본은 지우지 않음(옛 연도 노선은 읽을 때 선으로 바꿔 보임).
14. 도엽 `.cpg` 없음 = cp949 로 읽음 + 병합 캐시 판 번호 올림(B 프로젝트도 같이 고쳐짐).
## 4. 사용자 확정 필요
1. **같은 연도 갈래 · 구역을 아래 단계(D04 업체 지정 · B03 가져오기)에 넘기는 단위** — (가) 연도 하나에 조각 여럿 · 업체는 연도 단위 · B03 가져올 때 조각 고름(권함 · 고침 적음) / (나) 조각마다 한 줄 · 업체도 조각 단위.
## 5. 뒤로 미룸
- D03 위성 확대 선명(줌별 조각 타일) · 카카오네비 꼴 지도 엔진(MapLibre) 옮김 — 별도 협의.