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>
This commit is contained in:
2026-10-26 12:48:33 +09:00
co-authored by Claude Sonnet 5.5
parent c02d4ade6e
commit 852af20206
26 changed files with 139 additions and 30 deletions
+2 -2
View File
@@ -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_설명서)이 열림.
+8 -8
View File
@@ -1,27 +1,27 @@
# 노선 누가거리
# 노선 시작 · 종료 측점
노선 시작과 끝의 측점 값(누가거리, m)을 미리 정해 두는 칸. 비워 두어도 등록됨.
노선 시작과 끝의 측점을 미리 정해 두는 칸. 거리(누가거리, m)로 넣으면 칸 옆에 측점(No.+거리, 예 `61+14.5`)이 바로 보임. 비워 두어도 등록됨.
## 여는 길
- 예정노선 등록 화면 맨 아래 「노선 시작 누가거리 (m)」 · 「노선 종료 누가거리 (m)」.
- 예정노선 등록 화면 맨 아래 「노선 시작 측점 (누가거리 m 입력)」 · 「노선 종료 측점 (누가거리 m 입력)」.
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | 노선 첫 지점의 측점 값을 「노선 시작 누가거리」에 넣음. 0 이상의 수. |
| 2 | 노선 끝에서 목표로 하는 거리를 「노선 종료 누가거리」에 넣음. 시작 값보다 커야 함. |
| 1 | 노선 첫 지점의 거리(m)를 「노선 시작 측점」 칸에 넣음. 0 이상의 수. 칸 옆에 `= 측점` 이 바로 나옴. |
| 2 | 노선 끝에서 목표로 하는 거리(m)를 「노선 종료 측점」 칸에 넣음. 시작 값보다 커야 함. |
| 3 | 모르면 두 칸 모두 비워 둠. |
![누가거리 입력](D01_05_chainage.webp)
그림 번호 — 1 노선 시작 누가거리 · 2 노선 종료 누가거리
그림 번호 — 1 노선 시작 측점 · 2 노선 종료 측점
## 보이는 것
- 시작 값은 [노선 분석](D03/00_설명서) 화면의 측점 번호가 어디서부터 세는지를 정함. 비우면 0 부터.
- 종료 값은 분석을 마친 뒤 실제 이은 끝 누가와 비교해 「목표 · 차이」로 보임.
- 종료 값이 시작 값 이하이면 [등록] 때 「종료 누가거리는 시작보다 커야 합니다.」 안내가 나옴.
- 종료 값이 시작 값 이하이면 [등록] 때 「종료 측점은 시작보다 커야 합니다.」 안내가 나옴.
> 참고: 누가거리 = 노선을 따라 잰 거리.
> 참고: 누가거리 = 노선을 따라 잰 거리(m). 측점 = 20 m 마다 번호(No.)를 매기고 남는 거리를 더한 표기(1234.5 m = `61+14.5`). 저장되는 값은 m 그대로.
@@ -17,7 +17,7 @@
![입력 오류 안내](D01_06_errors.webp)
그림 번호 — 1 끝 연도가 시작 연도보다 앞 · 2 사업 지역 비어 있음 · 3 종료 누가거리가 시작 이하
그림 번호 — 1 끝 연도가 시작 연도보다 앞 · 2 사업 지역 비어 있음 · 3 종료 측점이 시작 이하
![단추 줄](D01_07_buttons.webp)
@@ -30,7 +30,7 @@
| 숫자 범위 안내 | 시작 연도가 2000 ~ 2100 밖 |
| 「끝 연도는 시작 연도와 같거나 뒤여야 합니다.」 | 끝 연도가 시작 연도보다 앞 |
| 「필수 항목을 입력하세요.」 | 사업 지역 또는 프로젝트명이 빔 |
| 「종료 누가거리는 시작보다 커야 합니다.」 | 종료 값이 시작 값 이하 |
| 「종료 측점은 시작보다 커야 합니다.」 | 종료 값이 시작 값 이하 |
- 산림청이 아닌 계정이거나 회사에 연결되지 않은 계정이면 서버가 「예정노선은 산림청만 만들 수 있습니다.」로 막고, 이 화면에 머묾.
- 등록에 실패하면 서버가 알려 준 까닭이 알림으로 뜸.
+3 -3
View File
@@ -13,8 +13,8 @@
| 임도 종류 | 간선임도 · 산불진화임도 · 작업임도 중 하나 | 간선임도(목록 맨 위) | 설계속도 선택지와 이름 조합에 쓰임 |
| 설계속도 | 노선을 설계하는 기준 속도 | 20 km/h | 아래 표 참조 |
| 프로젝트명 | 대시보드에 보이는 이름 | 자동 조합 | 필수 · 글자 255 자까지 |
| 노선 시작 누가거리 (m) | 노선 첫 지점의 측점 값 | 비어 있음 | 0 이상 · 선택 |
| 노선 종료 누가거리 (m) | 노선 끝 지점의 계획 목표 거리 | 비어 있음 | 시작 값보다 커야 함 · 선택 |
| 노선 시작 측점 (누가거리 m 입력) | 노선 첫 지점의 거리(m) · 옆에 측점 표기 | 비어 있음 | 0 이상 · 선택 |
| 노선 종료 측점 (누가거리 m 입력) | 노선 끝 지점의 계획 목표 거리(m) · 옆에 측점 표기 | 비어 있음 | 시작 값보다 커야 함 · 선택 |
## 설계속도 선택지
| 임도 종류 | 고를 수 있는 값 |
@@ -36,5 +36,5 @@
- 시작 연도가 2000 ~ 2100 밖이면 숫자 범위 안내가 나옴.
- 끝 연도가 시작보다 앞이면 「끝 연도는 시작 연도와 같거나 뒤여야 합니다.」 가 나옴.
- 지역이나 이름이 비면 필수 입력 안내가 나옴.
- 종료 누가거리가 시작 이하이면 「종료 누가거리는 시작보다 커야 합니다.」 가 나옴.
- 종료 측점이 시작 이하이면 「종료 측점은 시작보다 커야 합니다.」 가 나옴.
- 산림청이 아닌 계정으로 등록하면 서버가 「예정노선은 산림청만 만들 수 있습니다.」 로 막음. 회사에 연결되지 않은 계정도 막힘.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

+2 -2
View File
@@ -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_확정과_메일) 참고.
+3 -3
View File
@@ -8,7 +8,7 @@
![연도 표](D04_01_year_table.webp)
그림 번호 — 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) | 그 연도 노선 길이 |
## 보는 사람에 따른 차이
Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

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 => {
+22 -1
View File
@@ -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),
+7 -2
View File
@@ -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"
+4 -4
View File
@@ -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)까지 끝난 임도 프로젝트가 없습니다.",