155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
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;
|
|
}
|