feat(B05): 계획노선 편집 — 예상노선 거리 재기 · 측점 눈금 라벨 비켜 놓기 · 횡단 반폭 선
- 거리·기울기 재기가 예상노선(점선)에서도 됨 — 두 점은 한 노선 위 · 다른 노선을 짚으면 새로 시작 - 측점 눈금 라벨이 겹치면 빼지 않고 비켜 놓음(반대쪽 → 한 칸 더 멀리 · 가는 선으로 이음) — 공용 drawStationTicks - 오른쪽에 횡단이 뜬 측점에 좌·우 설계 반폭 선 — 노드를 끄는 동안 브라우저 셈으로 바로 따라감 - 측점 틀 파이썬·TS 짝(_interpolate_xy·_tangent_at ↔ stationFrameAt) + 거울 시험 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WeXB1zh5hbYLgoeGS5kQu
This commit is contained in:
@@ -194,6 +194,9 @@ export function drawRidgeRing(
|
||||
* 으로 밀어 마커를 가리지 않게 한다. B04 지도와 B05 배수유역도가 이 한 곳을 함께 쓴다.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/** 측점 라벨을 눈금에서 띄우는 거리(px) — 앞이 기본 자리, 뒤로 갈수록 비켜 놓는 자리. */
|
||||
const LABEL_REACH_PX = [16, 32, 48, 64, 80];
|
||||
|
||||
export interface StationTickOptions {
|
||||
/** 규칙 측점 간격(m). */
|
||||
intervalM: number;
|
||||
@@ -234,8 +237,8 @@ export function drawStationTicks(
|
||||
context.font = "11px system-ui, sans-serif";
|
||||
context.textAlign = "center";
|
||||
context.textBaseline = "middle";
|
||||
// 노선이 되꺾이면 멀쩡한 배율에서도 두 측점이 화면에서 붙는다 — 이미 그린 라벨과
|
||||
// 겹치는 자리는 건너뛴다(2026-09-04 실측에서 4px 간격까지 붙었다).
|
||||
// 노선이 되꺾이면 멀쩡한 배율에서도 두 측점이 화면에서 붙는다(2026-09-04 실측 4px) —
|
||||
// 이미 그린 라벨과 겹치는 자리는 비켜 놓는다.
|
||||
const drawn: Array<{ x: number; y: number; half: number }> = [];
|
||||
let cursor = 1;
|
||||
for (let chainage = 0; chainage <= total; chainage += interval) {
|
||||
@@ -268,14 +271,33 @@ export function drawStationTicks(
|
||||
context.stroke();
|
||||
|
||||
const label = stationLabel(chainage, interval);
|
||||
const lx = sx + ux * side * 16;
|
||||
const ly = sy + uy * side * 16;
|
||||
const width = context.measureText(label).width + 6;
|
||||
const half = width / 2;
|
||||
const collides = drawn.some(
|
||||
(item) => Math.abs(item.x - lx) < item.half + half && Math.abs(item.y - ly) < 16,
|
||||
);
|
||||
if (collides) continue;
|
||||
// 겹치면 빼지 않고 **비켜 놓는다** — 직각 방향으로 반대쪽 → 한 칸 더 멀리 순서로 빈자리를
|
||||
// 찾는다. 끝내 못 찾으면 첫 자리에 그대로 둔다(눈금마다 이름이 있어야 한다).
|
||||
const free = (x: number, y: number): boolean =>
|
||||
!drawn.some((item) => Math.abs(item.x - x) < item.half + half && Math.abs(item.y - y) < 16);
|
||||
let reach = LABEL_REACH_PX[0] * side;
|
||||
for (const distance of LABEL_REACH_PX) {
|
||||
const found = [side, -side].find((way) =>
|
||||
free(sx + ux * way * distance, sy + uy * way * distance),
|
||||
);
|
||||
if (found !== undefined) {
|
||||
reach = distance * found;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const lx = sx + ux * reach;
|
||||
const ly = sy + uy * reach;
|
||||
if (Math.abs(reach) > LABEL_REACH_PX[0]) {
|
||||
// 멀리 비켜 선 라벨은 어느 눈금 것인지 가는 선으로 잇는다.
|
||||
context.beginPath();
|
||||
context.moveTo(sx + ux * Math.sign(reach) * 6, sy + uy * Math.sign(reach) * 6);
|
||||
context.lineTo(lx - ux * Math.sign(reach) * 8, ly - uy * Math.sign(reach) * 8);
|
||||
context.lineWidth = 0.8;
|
||||
context.strokeStyle = "rgba(40, 40, 40, 0.55)";
|
||||
context.stroke();
|
||||
}
|
||||
drawn.push({ x: lx, y: ly, half });
|
||||
context.save();
|
||||
if (options.uprightRad) {
|
||||
|
||||
Reference in New Issue
Block a user