import { useMemo } from 'react' import { Marker } from '@vis.gl/react-maplibre' import { ScatterplotLayer } from '@deck.gl/layers' import type { Layer } from '@deck.gl/core' import { hexToRgba } from '@common/components/map/mapUtils' interface WeatherStation { id: string name: string location: { lat: number; lon: number } wind: { speed: number direction: number speed_1k: number speed_3k: number } wave: { height: number period: number } temperature: { current: number feelsLike: number } pressure: number visibility: number } interface WeatherMapOverlayProps { stations: WeatherStation[] enabledLayers: Set onStationClick: (station: WeatherStation) => void selectedStationId: string | null } // 풍속에 따른 hex 색상 반환 function getWindHexColor(speed: number, isSelected: boolean): string { if (isSelected) return '#06b6d4' if (speed > 10) return '#ef4444' if (speed > 7) return '#f59e0b' return '#3b82f6' } // 파고에 따른 hex 색상 반환 function getWaveHexColor(height: number): string { if (height > 2.5) return '#ef4444' if (height > 1.5) return '#f59e0b' return '#3b82f6' } // 수온에 따른 hex 색상 반환 function getTempHexColor(temp: number): string { if (temp > 8) return '#ef4444' if (temp > 6) return '#f59e0b' return '#3b82f6' } /** * WeatherMapOverlay * * - deck.gl 레이어(ScatterplotLayer)를 layers prop으로 외부에 반환 * - 라벨(HTML)은 MapLibre Marker로 직접 렌더링 * - 풍향 화살표는 CSS rotate가 가능한 MapLibre Marker로 렌더링 */ export function WeatherMapOverlay({ stations, enabledLayers, onStationClick, selectedStationId, }: WeatherMapOverlayProps) { // deck.gl 레이어는 useWeatherDeckLayers 훅을 통해 외부로 전달되므로 // 이 컴포넌트는 HTML 오버레이(Marker) 부분만 담당 return ( <> {/* 풍향 화살표 — MapLibre Marker + CSS rotate */} {enabledLayers.has('wind') && stations.map((station) => { const isSelected = selectedStationId === station.id const color = getWindHexColor(station.wind.speed, isSelected) return ( onStationClick(station)} >
{/* 위쪽이 바람 방향을 나타내는 삼각형 */}
{station.wind.speed.toFixed(1)}
) })} {/* 기상 데이터 라벨 — 임시 비활성화 {enabledLayers.has('labels') && stations.map((station) => { const isSelected = selectedStationId === station.id const boxBg = isSelected ? 'rgba(6, 182, 212, 0.85)' : 'rgba(255, 255, 255, 0.1)' const boxBorder = isSelected ? '#06b6d4' : 'rgba(255, 255, 255, 0.3)' const textColor = isSelected ? '#000' : '#fff' return ( onStationClick(station)} >
{station.name}
🌡️
{station.temperature.current.toFixed(1)} °C
🌊
{station.wave.height.toFixed(1)} m
💨
{station.wind.speed.toFixed(1)} m/s
) })} */} ) } /** * WeatherMapOverlay에 대응하는 deck.gl 레이어 생성 훅 * WeatherView의 DeckGLOverlay layers 배열에 spread하여 사용 */ // eslint-disable-next-line react-refresh/only-export-components export function useWeatherDeckLayers( stations: WeatherStation[], enabledLayers: Set, selectedStationId: string | null, onStationClick: (station: WeatherStation) => void ): Layer[] { return useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: Layer[] = [] // 파고 분포 ScatterplotLayer (Circle 대체, 반경 = 파고 * 15km) if (enabledLayers.has('waves')) { const waveData = stations.map((s) => ({ position: [s.location.lon, s.location.lat] as [number, number], radius: s.wave.height * 15000, fillColor: hexToRgba(getWaveHexColor(s.wave.height), 38), // fillOpacity 0.15 lineColor: hexToRgba(getWaveHexColor(s.wave.height), 153), // opacity 0.6 station: s, })) result.push( new ScatterplotLayer({ id: 'weather-wave-circles', data: waveData, getPosition: (d) => d.position, getRadius: (d) => d.radius, getFillColor: (d) => d.fillColor, getLineColor: (d) => d.lineColor, getLineWidth: 2, stroked: true, radiusUnits: 'meters', pickable: true, onClick: (info) => { if (info.object) onStationClick(info.object.station) }, }) as unknown as Layer ) } // 수온 분포 ScatterplotLayer (Circle 대체, 고정 반경 10km) if (enabledLayers.has('temperature')) { const tempData = stations.map((s) => ({ position: [s.location.lon, s.location.lat] as [number, number], fillColor: hexToRgba(getTempHexColor(s.temperature.current), 51), // fillOpacity 0.2 lineColor: hexToRgba(getTempHexColor(s.temperature.current), 128), // opacity 0.5 station: s, })) result.push( new ScatterplotLayer({ id: 'weather-temp-circles', data: tempData, getPosition: (d) => d.position, getRadius: 10000, getFillColor: (d) => d.fillColor, getLineColor: (d) => d.lineColor, getLineWidth: 1, stroked: true, radiusUnits: 'meters', pickable: true, onClick: (info) => { if (info.object) onStationClick(info.object.station) }, updateTriggers: { getFillColor: [selectedStationId], getLineColor: [selectedStationId], }, }) as unknown as Layer ) } return result }, [stations, enabledLayers, selectedStationId, onStationClick]) }