feat(B02/B03/B04/B05): 계획노선 사용 범위 · 절단 여유 3m · 배수유역도 줌·측점 표기
- 계획노선 사용 범위: B02 등록에 시작·종료 누가거리 두 칸 추가, B01 수정 모달에서도 변경. projects.route_start_m·route_end_m 신설(015_route_range.sql). load_design_route 가 범위 절단 → 서피스 트림 순서로 적용. 시작 >= 종료는 화면·서버 양쪽에서 차단. 비우면 전 구간으로 종전과 같음. - 서피스 절단 여유 기본값 30m → 3m (SURFACE_ROUTE_EDGE_TRIM_M). - B04 지도·B05 배수유역도 줌 상한을 「화면 폭 20m」 기준으로 계산(고정 8배·16배 폐지). 4배를 넘으면 배경 그림 흐림 보간 해제. - 계획선 위 측점 눈금·번호 표기(측점번호+잔여거리). 관 마커와 겹치면 반대쪽으로 밀고, 되꺾임 구간에서 라벨이 겹치면 건너뜀. 그리기 코드는 두 화면 공용. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,10 @@ import { createFlowStrengthOverlay } from "./B04_PreProcess_UI_FlowStrength";
|
||||
import { createWatershedOverlay } from "./B04_PreProcess_UI_Watershed";
|
||||
import {
|
||||
computeMapRect,
|
||||
computeMaxScale,
|
||||
computeRouteView,
|
||||
createMetricProjector,
|
||||
MAP_STATION_INTERVAL_M,
|
||||
createNormalizer,
|
||||
drawPreparedLabels,
|
||||
drawPreparedLayer,
|
||||
@@ -41,6 +44,7 @@ import {
|
||||
type PreparedLayer,
|
||||
type ViewState,
|
||||
} from "./B04_PreProcess_UI_MapRender";
|
||||
import { drawStationTicks } from "./B04_PreProcess_UI_MapOverlays";
|
||||
import type { WatershedAnalysis } from "./B04_PreProcess_Api_Fetch";
|
||||
|
||||
export interface SurfaceMapViewer {
|
||||
@@ -160,8 +164,10 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
);
|
||||
const activeGisLayers = new Set<GisLayer>(GIS_LAYERS.filter((layer) => GIS_DEFAULT_ON[layer]));
|
||||
let showContourLabels = CONTOUR_LABEL_DEFAULT_ON;
|
||||
// 계획선(B03 업로드 계획노선) — 사업지 좌표계(m) 폴리라인을 배경 지도 위에 겹친다.
|
||||
// 계획선(B03 계획노선 정본) — 사업지 좌표계(m) 폴리라인을 배경 지도 위에 겹친다.
|
||||
let routeLayer: PreparedLayer | null = null;
|
||||
// 측점 눈금·번호를 찍기 위한 원본 점 목록 (2026-09-04 사용자 지시).
|
||||
let routePoints: ReadonlyArray<{ x: number; y: number }> = [];
|
||||
let showRoute = true;
|
||||
let scale = 1;
|
||||
let offsetX = 0;
|
||||
@@ -314,6 +320,9 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
function updateImageTransform(): void {
|
||||
backgroundImages.forEach((image) => {
|
||||
image.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
// 크게 당기면 배경 그림이 뭉개진다 — 흐림 보간을 끄고 픽셀을 그대로 보인다
|
||||
// (2026-09-04 사용자 지시). 실제 크기는 축척 막대로 읽는다.
|
||||
image.style.imageRendering = scale > 4 ? "pixelated" : "auto";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -416,6 +425,15 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
context.strokeStyle = routeLineColor();
|
||||
drawPreparedLayer(context, routeLayer, view, "dot");
|
||||
}
|
||||
// 측점 눈금·번호 — 계획선 위, 유역 오버레이 아래. B05 배수유역도와 같은 규칙이다.
|
||||
if (showRoute && meta && routePoints.length > 1) {
|
||||
const projector = createMetricProjector(meta, view);
|
||||
drawStationTicks(context, routePoints, {
|
||||
intervalM: MAP_STATION_INTERVAL_M,
|
||||
pxPerMeter: projector.pxPerMeter,
|
||||
toScreen: projector.toScreen,
|
||||
});
|
||||
}
|
||||
// 흐름 강도(도로 색·유입 집중점 마커)는 계획선 위에 얹는다.
|
||||
flowStrength.draw(context, normalizer, view);
|
||||
// 세부유역 채움과 관 마커는 그 위 — 편집 대상이라 다른 레이어에 가려지면 집을 수 없다.
|
||||
@@ -443,6 +461,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
meta = null;
|
||||
preparedLayers.clear();
|
||||
routeLayer = null;
|
||||
routePoints = [];
|
||||
resetView();
|
||||
status.textContent = L("B04_Surface_Map_Loading");
|
||||
showProgress(0, L("B04_Surface_Map_Loading"));
|
||||
@@ -480,6 +499,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
normalizer = createNormalizer(nextMeta);
|
||||
routeLayer =
|
||||
planned.points.length > 1 ? prepareMetricPolyline(planned.points, nextMeta) : null;
|
||||
routePoints = planned.points;
|
||||
// 흐름 강도는 계획선 위에 칠하므로 같은 점 목록·같은 메타를 쓴다(어긋나면 색이 밀린다).
|
||||
flowStrength.setRoute(planned.points, nextMeta);
|
||||
// 관 마커도 같은 계획선 위에 스냅한다 — 목록이 다르면 마커가 노선을 벗어난다.
|
||||
@@ -518,7 +538,16 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
event.preventDefault();
|
||||
const prevScale = scale;
|
||||
// 휠을 **당기면 확대**, 밀면 축소한다(2026-08-02 사용자 지시). 일반 스크롤과 반대 방향이다.
|
||||
scale = Math.min(8, Math.max(0.5, scale * (event.deltaY > 0 ? 1.15 : 0.87)));
|
||||
// 상한은 「화면 폭 20m」로 계산한다 — 도엽 크기가 달라도 체감이 같다(2026-09-04 사용자 지시).
|
||||
const wheelRect = viewport.getBoundingClientRect();
|
||||
const wheelWidth = Math.max(1, Math.floor(wheelRect.width));
|
||||
const maxScale = computeMaxScale(
|
||||
meta,
|
||||
computeMapRect(meta, wheelWidth, Math.max(1, Math.floor(wheelRect.height))).width,
|
||||
wheelWidth,
|
||||
8,
|
||||
);
|
||||
scale = Math.min(maxScale, Math.max(0.5, scale * (event.deltaY > 0 ? 1.15 : 0.87)));
|
||||
// 마우스 커서 아래 지점이 줌 전후로 같은 화면 위치에 머물도록 offset 보정.
|
||||
// screen = center + (base - center)·scale + offset 이므로,
|
||||
// 커서 고정 조건을 풀면 offset' = (cursor - center)·(1 - r) + offset·r (r = scale'/scale).
|
||||
|
||||
Reference in New Issue
Block a user