signal-batch/frontend/src/features/vessel-map/utils/viewport.ts
htlee ed0f3056b1 feat: Ship-GIS 기능 이관 — 최근위치/선박항적/뷰포트 리플레이
dark(ship-gis) 프로젝트의 맵 기반 3대 기능을 API 탐색기에 이관.
Feature 폴더 모듈화 구조로 타 프로젝트 재활용 가능하게 구성.

Phase 1: vessel-map 공유 모듈 (Deck.gl 9 + Zustand 5 + STOMP)
Phase 2: 최근 위치 (30초 폴링 + IconLayer + 선종 필터 + 팝업)
Phase 3: 선박 항적 (MMSI 조회 + PathLayer + 타임라인 보간)
Phase 4: 뷰포트 리플레이 (STOMP WebSocket 청크 + TripsLayer 애니메이션)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 15:19:21 +09:00

53 lines
1.6 KiB
TypeScript

import type maplibregl from 'maplibre-gl'
import type { ViewportBounds, VesselPosition } from '../types'
/** 맵 인스턴스에서 현재 뷰포트 바운드 추출 */
export function getViewportBounds(map: maplibregl.Map): ViewportBounds {
const bounds = map.getBounds()
return {
west: bounds.getWest(),
south: bounds.getSouth(),
east: bounds.getEast(),
north: bounds.getNorth(),
}
}
/** 바운드 내부에 있는 선박만 필터링 */
export function filterByViewport<T extends { lon: number; lat: number }>(
positions: T[],
bounds: ViewportBounds,
): T[] {
return positions.filter(
(p) =>
p.lon >= bounds.west &&
p.lon <= bounds.east &&
p.lat >= bounds.south &&
p.lat <= bounds.north,
)
}
/** 바운드를 마진(비율)만큼 확장 */
export function expandBounds(bounds: ViewportBounds, margin = 0.1): ViewportBounds {
const lonSpan = bounds.east - bounds.west
const latSpan = bounds.north - bounds.south
return {
west: bounds.west - lonSpan * margin,
south: bounds.south - latSpan * margin,
east: bounds.east + lonSpan * margin,
north: bounds.north + latSpan * margin,
}
}
/** VesselPosition 배열의 전체 바운드 계산 (fitBounds용) */
export function calcPositionBounds(positions: VesselPosition[]): ViewportBounds | null {
if (positions.length === 0) return null
let west = Infinity, south = Infinity, east = -Infinity, north = -Infinity
for (const p of positions) {
if (p.lon < west) west = p.lon
if (p.lon > east) east = p.lon
if (p.lat < south) south = p.lat
if (p.lat > north) north = p.lat
}
return { west, south, east, north }
}