feat(D01·D04): 예정노선 측점 표기 No.+거리(61+14.5 꼴) — D01 입력 옆 표기 · D04 표 · xlsx 측점 칸
- D01 시작 · 종료 칸 이름 측점 말로 · 입력 옆 즉시 `= 61+14.5` (저장값 m 그대로) - D04 표 측점 열 · xlsx 시점 · 종점 측점 칸 덧(m 칸 · shp 숫자 칸 유지) - 파이썬 station_label ↔ stationLabel 거울 시험 · 도움말 D01 · D04 글 · 그림 열여섯 장 다시 찍음 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@@ -17,7 +17,7 @@
|
||||
| 2 | 사업 지역 | 사업이 있는 지역 이름을 적음. 꼭 넣어야 하는 칸 |
|
||||
| 3 | 임도 종류 · 설계속도 | 임도 종류를 고르면 옆 설계속도 목록이 따라 바뀜 |
|
||||
| 4 | 프로젝트명 | 위 칸을 채우면 이름이 저절로 만들어짐. 직접 고쳐도 됨 |
|
||||
| 5 | 노선 누가거리 | 노선 시작 · 종료 누가거리(m). 비워 두어도 됨 |
|
||||
| 5 | 노선 시작 · 종료 측점 | 거리(누가거리 m)로 넣으면 옆에 측점(`61+14.5`)이 보임. 비워 두어도 됨 |
|
||||
| 6 | 단추 줄 | [?] 도움말 · [취소] 대시보드로 돌아감 · [등록] 프로젝트를 만듦 |
|
||||
|
||||
## 작업 순서
|
||||
@@ -26,7 +26,7 @@
|
||||
2. 사업 연도 시작 · 끝과 사업 지역을 넣음. [사업 연도 · 지역](D01/02_사업연도_지역) 참고.
|
||||
3. 임도 종류와 설계속도를 고름. [임도 종류 · 설계속도](D01/03_임도종류_설계속도) 참고.
|
||||
4. 프로젝트명을 확인함. [프로젝트명](D01/04_프로젝트명) 참고.
|
||||
5. 필요하면 노선 시작 · 종료 누가거리를 넣음. [노선 누가거리](D01/05_누가거리) 참고.
|
||||
5. 필요하면 노선 시작 · 종료 측점을 넣음. [노선 시작 · 종료 측점](D01/05_누가거리) 참고.
|
||||
6. [등록]을 누름. 문제가 있는 칸은 아래에 빨간 안내가 나옴. [입력 오류와 등록](D01/06_입력오류와_등록) 참고.
|
||||
7. 등록되면 바로 다음 화면인 [기준 지정](D02/00_설명서)이 열림.
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
# 노선 누가거리
|
||||
# 노선 시작 · 종료 측점
|
||||
|
||||
노선 시작과 끝의 측점 값(누가거리, m)을 미리 정해 두는 칸. 비워 두어도 등록됨.
|
||||
노선 시작과 끝의 측점을 미리 정해 두는 칸. 거리(누가거리, m)로 넣으면 칸 옆에 측점(No.+거리, 예 `61+14.5`)이 바로 보임. 비워 두어도 등록됨.
|
||||
|
||||
## 여는 길
|
||||
|
||||
- 예정노선 등록 화면 맨 아래 「노선 시작 누가거리 (m)」 · 「노선 종료 누가거리 (m)」.
|
||||
- 예정노선 등록 화면 맨 아래 「노선 시작 측점 (누가거리 m 입력)」 · 「노선 종료 측점 (누가거리 m 입력)」.
|
||||
|
||||
## 단계
|
||||
|
||||
| 단계 | 할 일 |
|
||||
| --- | --- |
|
||||
| 1 | 노선 첫 지점의 측점 값을 「노선 시작 누가거리」에 넣음. 0 이상의 수. |
|
||||
| 2 | 노선 끝에서 목표로 하는 거리를 「노선 종료 누가거리」에 넣음. 시작 값보다 커야 함. |
|
||||
| 1 | 노선 첫 지점의 거리(m)를 「노선 시작 측점」 칸에 넣음. 0 이상의 수. 칸 옆에 `= 측점` 이 바로 나옴. |
|
||||
| 2 | 노선 끝에서 목표로 하는 거리(m)를 「노선 종료 측점」 칸에 넣음. 시작 값보다 커야 함. |
|
||||
| 3 | 모르면 두 칸 모두 비워 둠. |
|
||||
|
||||

|
||||
|
||||
그림 번호 — 1 노선 시작 누가거리 · 2 노선 종료 누가거리
|
||||
그림 번호 — 1 노선 시작 측점 · 2 노선 종료 측점
|
||||
|
||||
## 보이는 것
|
||||
|
||||
- 시작 값은 [노선 분석](D03/00_설명서) 화면의 측점 번호가 어디서부터 세는지를 정함. 비우면 0 부터.
|
||||
- 종료 값은 분석을 마친 뒤 실제 이은 끝 누가와 비교해 「목표 · 차이」로 보임.
|
||||
- 종료 값이 시작 값 이하이면 [등록] 때 「종료 누가거리는 시작보다 커야 합니다.」 안내가 나옴.
|
||||
- 종료 값이 시작 값 이하이면 [등록] 때 「종료 측점은 시작보다 커야 합니다.」 안내가 나옴.
|
||||
|
||||
> 참고: 누가거리 = 노선을 따라 잰 거리.
|
||||
> 참고: 누가거리 = 노선을 따라 잰 거리(m). 측점 = 20 m 마다 번호(No.)를 매기고 남는 거리를 더한 표기(1234.5 m = `61+14.5`). 저장되는 값은 m 그대로.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||

|
||||
|
||||
그림 번호 — 1 끝 연도가 시작 연도보다 앞 · 2 사업 지역 비어 있음 · 3 종료 누가거리가 시작 이하
|
||||
그림 번호 — 1 끝 연도가 시작 연도보다 앞 · 2 사업 지역 비어 있음 · 3 종료 측점이 시작 이하
|
||||
|
||||

|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
| 숫자 범위 안내 | 시작 연도가 2000 ~ 2100 밖 |
|
||||
| 「끝 연도는 시작 연도와 같거나 뒤여야 합니다.」 | 끝 연도가 시작 연도보다 앞 |
|
||||
| 「필수 항목을 입력하세요.」 | 사업 지역 또는 프로젝트명이 빔 |
|
||||
| 「종료 누가거리는 시작보다 커야 합니다.」 | 종료 값이 시작 값 이하 |
|
||||
| 「종료 측점은 시작보다 커야 합니다.」 | 종료 값이 시작 값 이하 |
|
||||
|
||||
- 산림청이 아닌 계정이거나 회사에 연결되지 않은 계정이면 서버가 「예정노선은 산림청만 만들 수 있습니다.」로 막고, 이 화면에 머묾.
|
||||
- 등록에 실패하면 서버가 알려 준 까닭이 알림으로 뜸.
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
| 임도 종류 | 간선임도 · 산불진화임도 · 작업임도 중 하나 | 간선임도(목록 맨 위) | 설계속도 선택지와 이름 조합에 쓰임 |
|
||||
| 설계속도 | 노선을 설계하는 기준 속도 | 20 km/h | 아래 표 참조 |
|
||||
| 프로젝트명 | 대시보드에 보이는 이름 | 자동 조합 | 필수 · 글자 255 자까지 |
|
||||
| 노선 시작 누가거리 (m) | 노선 첫 지점의 측점 값 | 비어 있음 | 0 이상 · 선택 |
|
||||
| 노선 종료 누가거리 (m) | 노선 끝 지점의 계획 목표 거리 | 비어 있음 | 시작 값보다 커야 함 · 선택 |
|
||||
| 노선 시작 측점 (누가거리 m 입력) | 노선 첫 지점의 거리(m) · 옆에 측점 표기 | 비어 있음 | 0 이상 · 선택 |
|
||||
| 노선 종료 측점 (누가거리 m 입력) | 노선 끝 지점의 계획 목표 거리(m) · 옆에 측점 표기 | 비어 있음 | 시작 값보다 커야 함 · 선택 |
|
||||
|
||||
## 설계속도 선택지
|
||||
| 임도 종류 | 고를 수 있는 값 |
|
||||
@@ -36,5 +36,5 @@
|
||||
- 시작 연도가 2000 ~ 2100 밖이면 숫자 범위 안내가 나옴.
|
||||
- 끝 연도가 시작보다 앞이면 「끝 연도는 시작 연도와 같거나 뒤여야 합니다.」 가 나옴.
|
||||
- 지역이나 이름이 비면 필수 입력 안내가 나옴.
|
||||
- 종료 누가거리가 시작 이하이면 「종료 누가거리는 시작보다 커야 합니다.」 가 나옴.
|
||||
- 종료 측점이 시작 이하이면 「종료 측점은 시작보다 커야 합니다.」 가 나옴.
|
||||
- 산림청이 아닌 계정으로 등록하면 서버가 「예정노선은 산림청만 만들 수 있습니다.」 로 막음. 회사에 연결되지 않은 계정도 막힘.
|
||||
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.0 KiB |
@@ -14,13 +14,13 @@
|
||||
|
||||
| 번호 | 구역 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 연도 표 | 연도마다 연장 · 시점 · 종점 · 누가 · 확정 시각 · 지정 업체를 한 줄로 보여 줌. 업체를 여기서 추가 · 삭제함 |
|
||||
| 1 | 연도 표 | 연도마다 연장 · 시점 · 종점 · 측점 · 확정 시각 · 지정 업체를 한 줄로 보여 줌. 업체를 여기서 추가 · 삭제함 |
|
||||
| 2 | 「확정할 연도」 카드 | 확정할 연도를 고름. 분석 [저장]이 된 연도만 나옴 |
|
||||
| 3 | 단추 줄 | [?] · [<] · [>] · [내보내기] · [확정] · [저장] |
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 연도 표에서 연도마다 연장과 누가를 확인함. [연도 표](D04/01_연도_표) 참고.
|
||||
1. 연도 표에서 연도마다 연장과 측점을 확인함. [연도 표](D04/01_연도_표) 참고.
|
||||
2. 지정 업체 칸에서 업체를 추가함. 등록 회사는 이름을 찾아 고르고, 아니면 업체명과 메일을 직접 적음. [지정 업체](D04/02_지정_업체) · [업체 직접 추가](D04/03_업체_직접_추가) 참고.
|
||||
3. [저장]을 눌러 지정 업체를 서버에 저장함. [단추와 확정할 연도](D04/04_단추와_확정할_연도) 참고.
|
||||
4. 「확정할 연도」에서 확정할 연도를 고르고 [확정]을 누름. 확인 창에서 진행을 누르면 확정과 메일 발송이 이뤄짐. [확정과 메일](D04/05_확정과_메일) 참고.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||

|
||||
|
||||
그림 번호 — 1 연장(m) · 2 시점(위도, 경도) · 3 누가(m) · 4 확정 시각 · 5 지정 업체
|
||||
그림 번호 — 1 연장(m) · 2 시점(위도, 경도) · 3 측점 · 4 확정 시각 · 5 지정 업체
|
||||
|
||||
## 단계
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
| --- | --- |
|
||||
| 1 | 연도 열과 연장(m)으로 연도별 노선 길이를 확인함. |
|
||||
| 2 | 시점 · 종점(위도, 경도)으로 노선 양 끝 위치를 확인함. |
|
||||
| 3 | 누가(m) 열로 그 연도 노선이 전체 노선의 어디부터 어디까지인지 확인함. |
|
||||
| 3 | 측점 열(`61+14.5` 꼴)로 그 연도 노선이 전체 노선의 어디부터 어디까지인지 확인함. |
|
||||
| 4 | 확정 열이 비어 있으면 아직 확정 전. 날짜와 시각이 있으면 확정이 끝난 연도. |
|
||||
| 5 | 지정 업체 열은 [지정 업체](D04/02_지정_업체) 참고. |
|
||||
|
||||
## 보이는 것
|
||||
|
||||
- 분석을 저장하지 않은 연도는 누가 칸에 「노선 분석 [저장] 전」이 나옴. 「확정할 연도」 목록에도 나타나지 않음.
|
||||
- 분석을 저장하지 않은 연도는 측점 칸에 「노선 분석 [저장] 전」이 나옴. 「확정할 연도」 목록에도 나타나지 않음.
|
||||
- 확정 열 = 확정한 날짜와 시각(한국 시간). 비어 있으면 아직 확정 전.
|
||||
- 시점 · 종점은 위도 · 경도 순서로 소수 6 자리까지 보임.
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
| 대상지 | 「사업지역.연장km」 글자(예: 지역.2.2). 20바이트(한글 10자 안팎)를 넘으면 뒤가 잘림 | 글자 20 |
|
||||
| 길이 | 노선 연장, 단위 km(소수 셋째 자리) | 숫자 11.3 |
|
||||
| 연도 | 사업 연도. 전체 노선은 비어 있음 | 정수 4 |
|
||||
| 시점누가 | 그 선이 시작하는 측점 값(m) | 숫자 11.2 |
|
||||
| 종점누가 | 그 선이 끝나는 측점 값(m) | 숫자 11.2 |
|
||||
| 시점누가 | 그 선이 시작하는 누가거리(m) · 기계용 숫자 칸 | 숫자 11.2 |
|
||||
| 종점누가 | 그 선이 끝나는 누가거리(m) · 기계용 숫자 칸 | 숫자 11.2 |
|
||||
|
||||
- 대상지 · 길이 두 칸은 용화 양식과 같고, 연도 · 시점누가 · 종점누가 는 이 프로그램이 덧붙인 칸임.
|
||||
- 누가 값은 2단계에서 이어 셈한 값 그대로임. 앞 연도 종점 누가 = 다음 연도 시점 누가.
|
||||
@@ -50,7 +50,8 @@
|
||||
| 종점 X · 종점 Y | 끝점 UTM-K 좌표(m) |
|
||||
| 시점 경도 · 시점 위도 | 시작점 위경도(도) |
|
||||
| 종점 경도 · 종점 위도 | 끝점 위경도(도) |
|
||||
| 시점누가(m) · 종점누가(m) | 이어 셈한 측점 값 |
|
||||
| 시점 측점 · 종점 측점 | 측점(No.+거리 · 예 `61+14.5` · 20 m 마다 번호) |
|
||||
| 시점누가(m) · 종점누가(m) | 이어 셈한 누가거리(m) |
|
||||
| 연장(m) | 그 연도 노선 길이 |
|
||||
|
||||
## 보는 사람에 따른 차이
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
@@ -18,6 +18,7 @@ import {
|
||||
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
||||
import { createHelpButton } from "@ui/ui_template_help";
|
||||
import { t } from "@ui/ui_template_locale";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { createGroupProject } from "../A00_Common/group_workflow";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { createNameComposer } from "../B02_ProjRegister/B02_ProjRegister_UI_Name";
|
||||
@@ -26,6 +27,9 @@ import { SPEED_CHOICES } from "../B05_Profile/logic/B05_Profile_Grade_Criteria";
|
||||
/** B02 임도 종류 → 설계속도 기준 등급(B05 grade_class_of 와 같음) */
|
||||
const GRADE_CLASS: Record<string, string> = { main: "trunk", fire: "fire", work: "work" };
|
||||
|
||||
/** 측점 간격 — D03 `PLAN_STATION_INTERVAL_M` 와 같음 */
|
||||
const STATION_INTERVAL_M = 20;
|
||||
|
||||
const numberOrNull = (text: string): number | null =>
|
||||
text.trim() === "" ? null : Number.parseFloat(text);
|
||||
|
||||
@@ -64,6 +68,20 @@ export function renderD01PlanRegister(root: HTMLElement): void {
|
||||
const name = createInputField({ label: t("DE_Reg_Name"), required: true });
|
||||
const startM = createInputField({ label: t("DE_Reg_StartM"), type: "number", min: 0 });
|
||||
const endM = createInputField({ label: t("DE_Reg_EndM"), type: "number", min: 0 });
|
||||
// 누가거리 칸 옆 즉시 측점 표기(저장값 · 서버 검사는 m 그대로)
|
||||
for (const field of [startM, endM]) {
|
||||
const shown = document.createElement("span");
|
||||
shown.style.cssText = "font-size:12px;color:var(--color-text-muted)";
|
||||
shown.dataset.station = "";
|
||||
field.root.insertBefore(shown, field.root.lastElementChild);
|
||||
field.input.addEventListener("input", () => {
|
||||
const value = numberOrNull(field.input.value);
|
||||
shown.textContent =
|
||||
value !== null && Number.isFinite(value) && value >= 0
|
||||
? `= ${stationLabel(value, STATION_INTERVAL_M, 1)}`
|
||||
: "";
|
||||
});
|
||||
}
|
||||
|
||||
// 설계속도 = 종류별 고를 수 있는 값 · 기본 = 가장 낮은 값(20)
|
||||
const syncSpeeds = (): void => {
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
계산 자리 ③ 서버 단독 — 화면이 만지지 않는다. 순수 함수(값 → 바이트)만 두고
|
||||
파일은 임시 폴더에서만 쓴다.
|
||||
|
||||
짝: `station_label` ↔ `common_util/common_util_station.ts` `stationLabel(…, 1)`
|
||||
(거울 시험 `test_545_1_station_label_mirror.py`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -49,6 +52,20 @@ _SCHEMA = {
|
||||
},
|
||||
}
|
||||
_BAD_FILENAME = re.compile(r'[\\/:*?"<>|\r\n\t]')
|
||||
STATION_INTERVAL_M = 20.0 # D03 `PLAN_STATION_INTERVAL_M` 와 같음
|
||||
|
||||
|
||||
def station_label(chainage_m: float, interval_m: float = STATION_INTERVAL_M) -> str:
|
||||
"""측점 표기 `61+14.5` — TS `stationLabel(chainage, interval, 1)` 짝(소수 한 자리 · 반올림 위로)."""
|
||||
safe = interval_m if interval_m > 0 else 1.0
|
||||
number = math.floor((chainage_m + 1e-6) / safe)
|
||||
rest = chainage_m - number * safe
|
||||
if abs(rest) < 0.05:
|
||||
rest = 0.0
|
||||
if rest >= safe - 0.05:
|
||||
number, rest = number + 1, 0.0
|
||||
tenth = math.floor(rest * 10 + 0.5) / 10
|
||||
return f"{number}+{int(tenth) if tenth == int(tenth) else tenth}"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -130,7 +147,7 @@ def _write_shapefile(
|
||||
|
||||
|
||||
def _coordinate_xlsx(project_name: str, rows: list[dict]) -> bytes:
|
||||
"""사람이 읽는 좌표표 — 연도별 시점 · 종점(UTM-K · 위경도) · 누가 · 연장."""
|
||||
"""사람이 읽는 좌표표 — 연도별 시점 · 종점(UTM-K · 위경도) · 측점 · 누가 · 연장."""
|
||||
to_ll = Transformer.from_crs(EXPORT_EPSG, 4326, always_xy=True)
|
||||
book = Workbook()
|
||||
sheet = book.active
|
||||
@@ -147,6 +164,8 @@ def _coordinate_xlsx(project_name: str, rows: list[dict]) -> bytes:
|
||||
"시점 위도",
|
||||
"종점 경도",
|
||||
"종점 위도",
|
||||
"시점 측점",
|
||||
"종점 측점",
|
||||
"시점누가(m)",
|
||||
"종점누가(m)",
|
||||
"연장(m)",
|
||||
@@ -166,6 +185,8 @@ def _coordinate_xlsx(project_name: str, rows: list[dict]) -> bytes:
|
||||
round(slat, 7),
|
||||
round(elon, 7),
|
||||
round(elat, 7),
|
||||
station_label(row["start_station"]),
|
||||
station_label(row["end_station"]),
|
||||
round(row["start_station"], 2),
|
||||
round(row["end_station"], 2),
|
||||
round(row["length_m"], 2),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* =============================================================================
|
||||
* D04_PlanConfirm_UI_Page.ts
|
||||
* D04 확정 · 업체 · 내보내기(요구서 345 3-4 · PLAN 531-3) — B05~B09 틀(group_workflow).
|
||||
* 본문 = 연도 표(연장 · 시점 · 종점 · 누가 · 확정 · 지정 업체) · 연도마다 업체 여럿
|
||||
* 본문 = 연도 표(연장 · 시점 · 종점 · 측점 · 확정 · 지정 업체) · 연도마다 업체 여럿
|
||||
* (등록 회사 찾기 = 가입 화면 회사 검색 · 아니면 업체명 + 메일 수기).
|
||||
* 좌측 = 확정할 연도 고르기 + [내보내기] [확정] [저장].
|
||||
* 조작 = 캐시 `d04-assignees` 즉시 · [저장] = PUT …/plan/assignees(연도 전부 맞춤) → 캐시 비움 ·
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import { createPanelCard } from "@ui/ui_template_collapsible";
|
||||
import { t } from "@ui/ui_template_locale";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { mountGroupStep } from "../A00_Common/group_workflow";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
@@ -77,6 +78,10 @@ async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
|
||||
const fmt = (value: number | undefined, digits = 1): string =>
|
||||
value === undefined || value === null ? "—" : value.toFixed(digits);
|
||||
/** 측점 간격 — D03 `PLAN_STATION_INTERVAL_M` 와 같음 */
|
||||
const STATION_INTERVAL_M = 20;
|
||||
const station = (m: number | undefined): string =>
|
||||
m === undefined || m === null ? "—" : stationLabel(m, STATION_INTERVAL_M, 1);
|
||||
const where = (p?: EndPoint): string => (p ? `${p.lat.toFixed(6)}, ${p.lon.toFixed(6)}` : "—");
|
||||
/** 같은 업체 — 등록 회사 = id · 수기 = 메일(서버 `_key` 와 같음) */
|
||||
const sameFirm = (a: Assignee, b: Assignee): boolean =>
|
||||
@@ -221,7 +226,7 @@ export async function renderD04PlanConfirm(root: HTMLElement): Promise<void> {
|
||||
const rows = summary.years.map((row) => {
|
||||
const firms = list.filter((item) => item.year === row.year).map(firmChip);
|
||||
const chain = row.saved
|
||||
? `${fmt(row.start_chainage_m)} ~ ${fmt(row.end_chainage_m)}`
|
||||
? `${station(row.start_chainage_m)} ~ ${station(row.end_chainage_m)}`
|
||||
: t("D04_NotSaved");
|
||||
return el("tr", {
|
||||
attrs: { "data-year": String(row.year) },
|
||||
|
||||
@@ -160,6 +160,13 @@ def test_zip_and_coordinate_table():
|
||||
sheet = load_workbook(io.BytesIO(bundle.all["좌표표_시험 프로젝트.xlsx"])).active
|
||||
rows = list(sheet.iter_rows(min_row=3, values_only=True))
|
||||
assert [r[0] for r in rows] == [2026, 2027]
|
||||
head = [c.value for c in next(sheet.iter_rows(min_row=2, max_row=2))]
|
||||
assert head[9:11] == ["시점 측점", "종점 측점"] and head[11:13] == [
|
||||
"시점누가(m)",
|
||||
"종점누가(m)",
|
||||
]
|
||||
# 100 m → 5+0 · m 칸은 그대로
|
||||
assert rows[0][9] == "5+0" and rows[0][11] == pytest.approx(100.0)
|
||||
# 업체용 좌표표는 그 연도만
|
||||
one = load_workbook(io.BytesIO(bundle.by_year[2026]["좌표표_시험 프로젝트.xlsx"])).active
|
||||
assert [r[0] for r in one.iter_rows(min_row=3, values_only=True)] == [2026]
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""545-1 측점 표기 거울 — D04 `station_label`(py) ↔ `common_util_station.ts` `stationLabel(…, 1)`."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from D04_PlanConfirm.D04_PlanConfirm_Export import station_label # noqa: E402
|
||||
|
||||
TS = PROJECT_ROOT / "common_util" / "common_util_station.ts"
|
||||
TYPESCRIPT = PROJECT_ROOT / "config" / "node_modules" / "typescript"
|
||||
VALUES = [
|
||||
0,
|
||||
19.96,
|
||||
20,
|
||||
39.99,
|
||||
100,
|
||||
1234.5,
|
||||
1234.56,
|
||||
1234.44,
|
||||
1234.05,
|
||||
1234.95,
|
||||
61 * 20 + 14.5,
|
||||
812.345,
|
||||
]
|
||||
|
||||
SCRIPT = r"""
|
||||
const ts = require(process.argv[1]);
|
||||
const src = require("fs").readFileSync(process.argv[2], "utf8");
|
||||
const js = ts.transpileModule(src, { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
process.stdout.write(JSON.stringify(JSON.parse(process.argv[3]).map((v) => box.exports.stationLabel(v, 20, 1))));
|
||||
"""
|
||||
|
||||
|
||||
def test_파이썬과_TS_측점_표기_같음():
|
||||
proc = subprocess.run(
|
||||
["node", "-e", SCRIPT, str(TYPESCRIPT), str(TS), json.dumps(VALUES)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
assert json.loads(proc.stdout) == [station_label(v) for v in VALUES]
|
||||
|
||||
|
||||
def test_표기_꼴():
|
||||
assert station_label(1234.5) == "61+14.5"
|
||||
assert station_label(0) == "0+0"
|
||||
assert station_label(39.99) == "2+0"
|
||||
@@ -26,7 +26,7 @@ export const ui_locales_de = {
|
||||
D04_Col_Length: ["연장 (m)", "Length (m)"],
|
||||
D04_Col_Start: ["시점 (위도, 경도)", "Start (lat, lon)"],
|
||||
D04_Col_End: ["종점 (위도, 경도)", "End (lat, lon)"],
|
||||
D04_Col_Chainage: ["누가 (m)", "Chainage (m)"],
|
||||
D04_Col_Chainage: ["측점", "Station"],
|
||||
D04_Col_Confirmed: ["확정", "Confirmed"],
|
||||
D04_Col_Assignees: ["지정 업체", "Assigned firms"],
|
||||
D04_NotSaved: ["노선 분석 [저장] 전", "Route not saved"],
|
||||
@@ -91,9 +91,9 @@ export const ui_locales_de = {
|
||||
DE_Reg_KindPlan: ["노선 계획", "Route plan"],
|
||||
DE_Reg_DesignSpeed: ["설계속도", "Design speed"],
|
||||
DE_Reg_Name: ["프로젝트명", "Project name"],
|
||||
DE_Reg_StartM: ["노선 시작 누가거리 (m)", "Route start chainage (m)"],
|
||||
DE_Reg_EndM: ["노선 종료 누가거리 (m)", "Route end chainage (m)"],
|
||||
DE_Reg_ChainOrder: ["종료 누가거리는 시작보다 커야 합니다.", "End chainage must exceed start."],
|
||||
DE_Reg_StartM: ["노선 시작 측점 (누가거리 m 입력)", "Route start station (enter chainage m)"],
|
||||
DE_Reg_EndM: ["노선 종료 측점 (누가거리 m 입력)", "Route end station (enter chainage m)"],
|
||||
DE_Reg_ChainOrder: ["종료 측점은 시작보다 커야 합니다.", "End station must exceed start."],
|
||||
DE_Reg_Source: ["대상 임도 프로젝트", "Source forest road project"],
|
||||
DE_Reg_SourceEmpty: [
|
||||
"횡단 설계(B06)까지 끝난 임도 프로젝트가 없습니다.",
|
||||
|
||||