import { ScatterplotLayer, PathLayer, TextLayer, PolygonLayer } from '@deck.gl/layers'; import type { Layer as DeckLayer } from '@deck.gl/core'; import type { MeasurePoint, MeasureResult } from '../../store/mapStore'; import { formatDistance, formatArea } from '../../utils/geo'; const CYAN = [6, 182, 212, 220] as const; const CYAN_FILL = [6, 182, 212, 60] as const; const WHITE = [255, 255, 255, 255] as const; function midpoint(a: MeasurePoint, b: MeasurePoint): [number, number] { return [(a.lon + b.lon) / 2, (a.lat + b.lat) / 2]; } export function centroid(pts: MeasurePoint[]): [number, number] { const n = pts.length; return [pts.reduce((s, p) => s + p.lon, 0) / n, pts.reduce((s, p) => s + p.lat, 0) / n]; } function toPos(pt: MeasurePoint): [number, number] { return [pt.lon, pt.lat]; } export function midpointOf(a: MeasurePoint, b: MeasurePoint): [number, number] { return midpoint(a, b); } export function buildMeasureLayers( measureInProgress: MeasurePoint[], measureMode: 'distance' | 'area' | null, measurements: MeasureResult[], ): DeckLayer[] { const layers: DeckLayer[] = []; // 진행 중인 점들 if (measureInProgress.length > 0) { layers.push( new ScatterplotLayer({ id: 'measure-in-progress-points', data: measureInProgress, getPosition: (d: MeasurePoint) => toPos(d), getRadius: 6, getFillColor: [...CYAN], getLineColor: [...WHITE], getLineWidth: 2, radiusUnits: 'pixels', lineWidthUnits: 'pixels', stroked: true, }), ); if (measureInProgress.length >= 2) { layers.push( new PathLayer({ id: 'measure-in-progress-path', data: [{ path: measureInProgress.map(toPos) }], getPath: (d: { path: [number, number][] }) => d.path, getColor: [...CYAN], getWidth: 2, widthUnits: 'pixels', }), ); } // 면적 모드: 첫 점으로 돌아가는 점선 미리보기 if (measureMode === 'area' && measureInProgress.length >= 3) { const first = measureInProgress[0]; const last = measureInProgress[measureInProgress.length - 1]; layers.push( new PathLayer({ id: 'measure-area-close-preview', data: [{ path: [toPos(last), toPos(first)] }], getPath: (d: { path: [number, number][] }) => d.path, getColor: [6, 182, 212, 100], getWidth: 2, widthUnits: 'pixels', }), ); } } // 완료된 측정 결과들 for (const m of measurements) { if (m.mode === 'distance') { layers.push( new PathLayer({ id: `${m.id}-line`, data: [{ path: m.points.map(toPos) }], getPath: (d: { path: [number, number][] }) => d.path, getColor: [...CYAN], getWidth: 3, widthUnits: 'pixels', }), new ScatterplotLayer({ id: `${m.id}-points`, data: m.points, getPosition: (d: MeasurePoint) => toPos(d), getRadius: 6, getFillColor: [...CYAN], getLineColor: [...WHITE], getLineWidth: 2, radiusUnits: 'pixels', lineWidthUnits: 'pixels', stroked: true, }), new TextLayer({ id: `${m.id}-label`, data: [{ position: midpoint(m.points[0], m.points[1]), text: formatDistance(m.value) }], getPosition: (d: { position: [number, number] }) => d.position, getText: (d: { text: string }) => d.text, getSize: 14, getColor: [255, 255, 255, 255], getTextAnchor: 'middle', getAlignmentBaseline: 'center', fontFamily: 'Pretendard, sans-serif', fontWeight: 700, outlineWidth: 3, outlineColor: [0, 0, 0, 200], billboard: true, }), ); } else { layers.push( new PolygonLayer({ id: `${m.id}-polygon`, data: [{ polygon: m.points.map(toPos) }], getPolygon: (d: { polygon: [number, number][] }) => d.polygon, getFillColor: [...CYAN_FILL], getLineColor: [...CYAN], getLineWidth: 2, lineWidthUnits: 'pixels', stroked: true, filled: true, }), new TextLayer({ id: `${m.id}-label`, data: [{ position: centroid(m.points), text: formatArea(m.value) }], getPosition: (d: { position: [number, number] }) => d.position, getText: (d: { text: string }) => d.text, getSize: 14, getColor: [255, 255, 255, 255], getTextAnchor: 'middle', getAlignmentBaseline: 'center', fontFamily: 'Pretendard, sans-serif', fontWeight: 700, outlineWidth: 3, outlineColor: [0, 0, 0, 200], billboard: true, }), ); } } return layers; }