Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* 업체 자리 = 기준점(거침 · 없으면 공사지) 기준 지도 방위 — 업체 경위도(모달 주소 검색)가 있으면
|
||||
* 방위각 자동(244-4 · 기준점 좌표 없으면 시점 좌표로) · 없으면 옛 8방 · 그도 없으면 빈 방위 차례 ·
|
||||
* 같은 방위 여럿 = 옆으로 나란히
|
||||
* 기준점 자리 = 공사지 기준 같은 규칙(경위도 · 8방 · 기본 서)
|
||||
* 기준점 자리 = 기준점 주소 좌표 → 공사지(시점) 기준 방위각 · 좌표 없으면 서(왼 · 견본) — 옛 8방 무시(244-5)
|
||||
* 선 = 업체 → 기준점(또는 시점) · 기준점 → 시점 · 시점 → 중점 — 글 = 「도로 구분 km」 줄마다
|
||||
* 화면 = 북 위(SVG y 아래로 = 남)
|
||||
* 선 글 바로 고침(244-4 ⑥) = `legAt` · `setLeg` — 선(from) · 글 차례 → 그 구간(모달과 같은 값 한 벌)
|
||||
@@ -144,7 +144,7 @@ export function layoutMilestone(
|
||||
data.base.lon != null && data.base.lat != null ? [data.base.lon, data.base.lat] : null;
|
||||
let baseDeg = 270;
|
||||
if (useBase) {
|
||||
baseDeg = placeDeg(data.base, startLL) ?? 270;
|
||||
baseDeg = placeDeg({ dir: null, lon: data.base.lon, lat: data.base.lat }, startLL) ?? 270;
|
||||
const [ux, uy] = unit(baseDeg);
|
||||
const cx = ux * R_BASE;
|
||||
const cy = uy * R_BASE;
|
||||
|
||||
@@ -292,6 +292,10 @@ export function openBaseInfo(projectId: string): void {
|
||||
head("공통 구간 — 기준점 → 시점 (km · 도로 구분)"),
|
||||
legs.root,
|
||||
head("주소 · 지도"),
|
||||
el("p", {
|
||||
className: "b08-options__hint",
|
||||
text: "주소를 고르면 다이어그램에 실제 방위로 놓임 — 안 고르면 공사지 서쪽(왼쪽) 기본 자리",
|
||||
}),
|
||||
address.root,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
import { createInputField, createSelectField, el } from "@ui/ui_template_elements";
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { request } from "./B08_Quantity_UI_Hydro";
|
||||
import { DIRS, ROADS, type MilestoneData, type Road } from "./B08_Quantity_Milestone_Calc";
|
||||
import { ROADS, type MilestoneData, type Road } from "./B08_Quantity_Milestone_Calc";
|
||||
|
||||
export interface SiteInfo {
|
||||
length_m: number | null;
|
||||
@@ -27,18 +27,6 @@ interface Payload {
|
||||
images: Record<string, string>;
|
||||
}
|
||||
|
||||
/** 8방 이름 */
|
||||
export const DIR_TEXT: Record<string, string> = {
|
||||
N: "북",
|
||||
NE: "북동",
|
||||
E: "동",
|
||||
SE: "남동",
|
||||
S: "남",
|
||||
SW: "남서",
|
||||
W: "서",
|
||||
NW: "북서",
|
||||
};
|
||||
|
||||
export const EMPTY: MilestoneData = {
|
||||
site: { mid_station_m: null, mid_road: "비포장" },
|
||||
base: { enabled: false, name: "", address: "", dir: null, legs: [] },
|
||||
@@ -139,15 +127,11 @@ export async function imageBlobUrl(projectId: string, routeId: string): Promise<
|
||||
}
|
||||
|
||||
export const roadOptions = ROADS.map((r) => ({ value: r, text: r }));
|
||||
export const dirOptions = [
|
||||
{ value: "", text: "방위 —" },
|
||||
...DIRS.map((d) => ({ value: d, text: DIR_TEXT[d] })),
|
||||
];
|
||||
|
||||
const km = (m: number | null | undefined): string =>
|
||||
m == null ? "-" : (m / 1000).toLocaleString(undefined, { maximumFractionDigits: 3 });
|
||||
|
||||
/** 좌측 패널 「거리이정표」 카드 몸 — 공사지(시점 · 중점) · 기준점(있음 · 이름 · 주소 · 방위) */
|
||||
/** 좌측 패널 「거리이정표」 카드 몸 — 공사지(시점 · 중점) · 기준점(있음 · 이름 · 주소 · 방위 = 주소 좌표 자동) */
|
||||
export function milestoneCardBody(projectId: string | null): HTMLElement[] {
|
||||
const mid = createInputField({ label: "중점 측점 (m)", type: "number", compact: true, min: 0 });
|
||||
const midRoad = createSelectField({
|
||||
@@ -159,14 +143,9 @@ export function milestoneCardBody(projectId: string | null): HTMLElement[] {
|
||||
const has = createCheckbox("기준점 있음");
|
||||
const name = createInputField({ label: "기준점 이름", compact: true });
|
||||
const address = createInputField({ label: "기준점 주소", compact: true });
|
||||
const dir = createSelectField({
|
||||
label: "공사지에서 본 방위",
|
||||
options: dirOptions,
|
||||
compact: true,
|
||||
});
|
||||
mid.input.dataset.b08Milestone = "mid_station_m";
|
||||
has.input.dataset.b08Milestone = "base_enabled";
|
||||
const baseFields = el("div", { children: [name.root, address.root, dir.root] });
|
||||
const baseFields = el("div", { children: [name.root, address.root] });
|
||||
|
||||
const show = (): void => {
|
||||
if (!projectId) return;
|
||||
@@ -183,7 +162,6 @@ export function milestoneCardBody(projectId: string | null): HTMLElement[] {
|
||||
baseFields.hidden = !data.base.enabled;
|
||||
if (document.activeElement !== name.input) name.input.value = data.base.name;
|
||||
if (document.activeElement !== address.input) address.input.value = data.base.address;
|
||||
dir.select.value = data.base.dir ?? "";
|
||||
};
|
||||
const edit = (fn: (data: MilestoneData) => void): void => {
|
||||
if (!projectId) return;
|
||||
@@ -210,9 +188,6 @@ export function milestoneCardBody(projectId: string | null): HTMLElement[] {
|
||||
address.input.addEventListener("input", () =>
|
||||
edit((d) => (d.base.address = address.input.value)),
|
||||
);
|
||||
dir.select.addEventListener("change", () =>
|
||||
edit((d) => (d.base.dir = (dir.select.value || null) as MilestoneData["base"]["dir"])),
|
||||
);
|
||||
|
||||
const host = el("div", {
|
||||
children: [mid.root, midRoad.root, siteHint, has.root, baseFields],
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""244-5 B08 거리이정표 기준점 방위 칸 없앰 — 기준점 자리 = 주소 좌표 → 공사지 기준 방위각 ·
|
||||
좌표 없으면 공사지 서쪽(견본) · 옛 저장 값의 방위(dir)는 무시.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
||||
|
||||
|
||||
def _read(rel):
|
||||
return (ROOT / rel).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _layout(base, site):
|
||||
data = {"site": {"mid_station_m": None, "mid_road": "비포장"}, "base": base, "routes": []}
|
||||
src = IMPORTS.sub("", _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts"))
|
||||
expr = f"layoutMilestone({json.dumps(data)}, {json.dumps(site)}, 0)"
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", src + f"\nconsole.log(JSON.stringify({expr}));"],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
lay = json.loads(result.stdout)
|
||||
center = {b["id"]: (b["x"] + b["w"] / 2, b["y"] + b["h"] / 2) for b in lay["boxes"]}
|
||||
return center["site"], center["base"]
|
||||
|
||||
|
||||
BASE = {"enabled": True, "name": "기준", "address": "", "legs": []}
|
||||
SITE = {"start_lonlat": [127.8, 35.7], "mid_lonlat": None}
|
||||
|
||||
|
||||
def test_좌표_없음_서쪽_옛_방위_무시():
|
||||
for site in (SITE, None):
|
||||
(cx, cy), (bx, by) = _layout({**BASE, "dir": "E"}, site)
|
||||
assert bx < cx - 200 and abs(by - cy) < 1 # 옛 「동」 무시 · 서(왼)
|
||||
|
||||
|
||||
def test_좌표_있음_공사지_기준_방위각():
|
||||
(cx, cy), (bx, by) = _layout({**BASE, "dir": "W", "lon": 127.8, "lat": 35.8}, SITE)
|
||||
assert abs(bx - cx) < 1 and by < cy - 200 # 북 = 위(옛 「서」 무시)
|
||||
(cx, cy), (bx, by) = _layout({**BASE, "dir": None, "lon": 127.9, "lat": 35.7}, SITE)
|
||||
assert bx > cx + 200 and abs(by - cy) < 1 # 동 = 오른
|
||||
# 시점 좌표 모름 = 서쪽 기본
|
||||
(cx, cy), (bx, by) = _layout({**BASE, "lon": 127.9, "lat": 35.7}, None)
|
||||
assert bx < cx - 200
|
||||
|
||||
|
||||
def test_좌측_패널_방위_칸_없음_모달_안내():
|
||||
state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts")
|
||||
assert "공사지에서 본 방위" not in state and "dirOptions" not in state
|
||||
assert "d.base.dir" not in state and 'label: "기준점 주소"' in state
|
||||
modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts")
|
||||
base = modal[modal.index("export function openBaseInfo") : modal.index("interface SiteAddress")]
|
||||
assert "주소를 고르면 다이어그램에 실제 방위로 놓임" in base
|
||||
layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts")
|
||||
assert (
|
||||
"placeDeg({ dir: null, lon: data.base.lon, lat: data.base.lat }, startLL) ?? 270" in layout
|
||||
)
|
||||
Reference in New Issue
Block a user