feat(B05): C군 기본값·마커 민감도·3D 우클릭 배치·메뉴 단일화·3D 스냅·측점선 연장
- C군 기본값 확정(2026-08-19 사용자): 높이 2.5·길이 10·전길이 5 — 기본값이 생겨 required 해제. 기준 측점만 넣으면 범위가 바로 계산된다. - 드래그 민감도 완화: 종단 알약(누적→변위 14px, 임계 전 시각 이동 금지)·배수유역도 관 마커(이동 시작 8px 임계) — 고르려다 옮겨지는 문제. - 3D 노선 우클릭 → 구조물 배치 메뉴(신규 B05_Profile_UI_Viewer_Menu) — 지형 픽을 노선 폴리라인에 투영(15m 이내), 그래프·배수유역도와 같은 2단 메뉴·addAt 경로. - 우클릭 메뉴 전역 단일화(ui_template_context_menu): 새 메뉴가 열리면 다른 영역 메뉴는 닫히고, 좌클릭·중간버튼은 어디서든 닫는다(캡처 단계). - 3D 비정규 측점(배관 등) 좌표를 노선 폴리라인 chainage 투영으로 정확화 — 규칙 측점 직선보간의 곡선 모서리 잘림(노선 이탈) 해소. - 3D 측점선(노란 띠) 도로 반폭 1.5배 연장 — 측구 방향 램프는 끝단에 따라붙음. - 검증: pytest 112 통과(정책 테스트 기본값 반영), tsc, 헤드 브라우저 저장 왕복 (기준 3+0 → 55~65·anchor 60·before 5, 목록 '배수관 1/2' 번호 표기). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,15 +86,43 @@ export function circlePoint(point: PlacedRoutePoint): CirclePoint {
|
||||
return { ...routePoint(point), radius_m: point.radius_m ?? 25 };
|
||||
}
|
||||
|
||||
/** 노선 폴리라인 위 chainage 지점의 (x, y) — 세그먼트 누적 길이로 정확히 되짚는다.
|
||||
* 규칙 측점(20m 간격) 사이 직선보간은 곡선 구간에서 모서리를 잘라 3D 마커가 노선을
|
||||
* 벗어난다(2026-08-19 사용자 보고 — 도엽 등고선 분석 좌표와 라이다 노선의 미세 차).
|
||||
* 폴리라인은 노선 정본이라 이 좌표가 3D 노선 선 위에 정확히 얹힌다. */
|
||||
export function chainageToPolylineXY(
|
||||
polyline: ReadonlyArray<{ x: number; y: number }>,
|
||||
chainage: number,
|
||||
): { x: number; y: number } | null {
|
||||
if (polyline.length < 2) return null;
|
||||
let travelled = 0;
|
||||
for (let index = 1; index < polyline.length; index += 1) {
|
||||
const from = polyline[index - 1];
|
||||
const to = polyline[index];
|
||||
const span = Math.hypot(to.x - from.x, to.y - from.y);
|
||||
if (span < 1e-9) continue;
|
||||
if (travelled + span >= chainage - 1e-6) {
|
||||
const t = Math.min(Math.max((chainage - travelled) / span, 0), 1);
|
||||
return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
|
||||
}
|
||||
travelled += span;
|
||||
}
|
||||
const last = polyline[polyline.length - 1];
|
||||
return { x: last.x, y: last.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* 비정규 측점을 규칙 측점 좌표 사이 chainage로 선형보간해 `SectionStation`(월드 좌표·프레임 포함)으로
|
||||
* 만든다. 백엔드가 아직 이 측점의 횡단을 생성하지 않으므로, 3D 표시에 필요한 위치만 근사한다.
|
||||
* 노선 범위를 벗어난 chainage는 제외한다.
|
||||
* 노선 범위를 벗어난 chainage는 제외한다. `routePolyline`을 주면 중심 (x, y)는 노선
|
||||
* 폴리라인에서 정확히 되짚는다(곡선 모서리 잘림 방지, 2026-08-19) — 표고·프레임은
|
||||
* 여전히 규칙 측점 보간값이다.
|
||||
*/
|
||||
export function interpolateIrregularStations(
|
||||
base: SectionStation[],
|
||||
list: IrregularStation[],
|
||||
maxChainage: number,
|
||||
routePolyline: ReadonlyArray<{ x: number; y: number }> = [],
|
||||
): SectionStation[] {
|
||||
const sorted = [...base].sort((a, b) => a.chainage_m - b.chainage_m);
|
||||
if (!sorted.length) return [];
|
||||
@@ -133,15 +161,22 @@ export function interpolateIrregularStations(
|
||||
};
|
||||
return list
|
||||
.filter((entry) => entry.chainage_m >= 0 && entry.chainage_m <= maxChainage + 1e-6)
|
||||
.map((entry) => ({
|
||||
...anchorAt(entry.chainage_m),
|
||||
station_id: irregularStationId(entry.id),
|
||||
chainage_m: entry.chainage_m,
|
||||
label: irregularLabel(entry),
|
||||
kind: "irregular" as const,
|
||||
// 3D 측점 라벨이 `측점번호 구조물명`으로 표기할 수 있게 구조물 이름을 실어 보낸다.
|
||||
structure: entry.structure,
|
||||
}));
|
||||
.map((entry) => {
|
||||
const anchor = anchorAt(entry.chainage_m);
|
||||
// 중심 좌표는 노선 폴리라인에서 정확히 — 곡선에서 규칙 측점 직선보간이 만드는
|
||||
// 모서리 잘림(3D 마커가 노선 옆으로 새는 현상)을 없앤다(2026-08-19).
|
||||
const onRoute = chainageToPolylineXY(routePolyline, entry.chainage_m);
|
||||
return {
|
||||
...anchor,
|
||||
...(onRoute ? { center_x: onRoute.x, center_y: onRoute.y } : {}),
|
||||
station_id: irregularStationId(entry.id),
|
||||
chainage_m: entry.chainage_m,
|
||||
label: irregularLabel(entry),
|
||||
kind: "irregular" as const,
|
||||
// 3D 측점 라벨이 `측점번호 구조물명`으로 표기할 수 있게 구조물 이름을 실어 보낸다.
|
||||
structure: entry.structure,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** 시설 종류별 표시 이름 — 그래프·3D 라벨용(배관은 관종·관경까지 따로 붙인다). */
|
||||
|
||||
Reference in New Issue
Block a user