wing-ops/frontend/src/tabs/weather/components/OceanForecastOverlay.tsx
htlee 85749c2f68 feat(map): Leaflet → MapLibre GL JS + deck.gl 전환 (Phase 6)
지도 엔진을 Leaflet 1.9에서 MapLibre GL JS 5.x + deck.gl 9.x로 전환.
15개 파일 수정, Leaflet 완전 제거. WebGL 단일 canvas로 z-index 충돌 해결,
유류 입자 ScatterplotLayer GPU 렌더링으로 10~100배 성능 향상.

- MapView.tsx: MapLibre Map + DeckGLOverlay(MapboxOverlay interleaved)
- 유류 입자/오일펜스/HNS: deck.gl ScatterplotLayer/PathLayer
- 역추적 리플레이: createBacktrackLayers() 함수 패턴
- 기상 오버레이: WeatherMapOverlay/OceanCurrent/WindParticle deck.gl 전환
- 수온 히트맵: WaterTemperatureLayer deck.gl ScatterplotLayer
- 해황예보도: MapLibre image source + raster layer
- SCAT/Assets/Incidents: MapLibre Map + deck.gl 레이어
- WMS 밝기: raster-brightness-min/max 네이티브 속성

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 02:48:54 +09:00

70 lines
1.9 KiB
TypeScript
Executable File

import { useEffect, useState } from 'react'
import { Source, Layer } from '@vis.gl/react-maplibre'
import type { OceanForecastData } from '../services/khoaApi'
interface OceanForecastOverlayProps {
forecast: OceanForecastData | null
opacity?: number
visible?: boolean
}
// 한국 해역 범위 (MapLibre image source용 좌표 배열)
// [left, bottom, right, top] → MapLibre coordinates 순서: [sw, nw, ne, se]
// [lon, lat] 순서
const KOREA_IMAGE_COORDINATES: [[number, number], [number, number], [number, number], [number, number]] = [
[124.5, 33.0], // 남서 (제주 남쪽)
[124.5, 38.5], // 북서
[132.0, 38.5], // 북동 (동해 북쪽)
[132.0, 33.0], // 남동
]
/**
* OceanForecastOverlay
*
* 기존: react-leaflet ImageOverlay + LatLngBounds
* 전환: @vis.gl/react-maplibre Source(type=image) + Layer(type=raster)
*
* MapLibre image source는 Map 컴포넌트 자식으로 직접 렌더링 가능
*/
export function OceanForecastOverlay({
forecast,
opacity = 0.6,
visible = true,
}: OceanForecastOverlayProps) {
const [loadedUrl, setLoadedUrl] = useState<string | null>(null)
useEffect(() => {
if (!forecast?.filePath) return
let cancelled = false
const img = new Image()
img.onload = () => { if (!cancelled) setLoadedUrl(forecast.filePath) }
img.onerror = () => { if (!cancelled) setLoadedUrl(null) }
img.src = forecast.filePath
return () => { cancelled = true }
}, [forecast?.filePath])
const imageLoaded = !!loadedUrl && loadedUrl === forecast?.filePath
if (!visible || !forecast || !imageLoaded) {
return null
}
return (
<Source
id="ocean-forecast-image"
type="image"
url={forecast.filePath}
coordinates={KOREA_IMAGE_COORDINATES}
>
<Layer
id="ocean-forecast-raster"
type="raster"
paint={{
'raster-opacity': opacity,
'raster-resampling': 'linear',
}}
/>
</Source>
)
}