wing-ops/frontend/src/components/weather/hooks/useWeatherData.ts

152 lines
4.7 KiB
TypeScript

import { useState, useEffect } from 'react';
import { getRecentObservation, OBS_STATION_CODES, API_KEY } from '../services/khoaApi';
interface WeatherStation {
id: string;
name: string;
location: { lat: number; lon: number };
}
export interface EnrichedWeatherStation extends WeatherStation {
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;
salinity?: number;
}
/**
* 기상 데이터 가져오기 훅 (KHOA 조위관측소 API 사용)
*/
export function useWeatherData(stations: WeatherStation[]) {
const [weatherStations, setWeatherStations] = useState<EnrichedWeatherStation[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
useEffect(() => {
let isMounted = true;
// 관측소별 fallback 데이터 생성
function generateFallbackStation(station: WeatherStation): EnrichedWeatherStation {
const seed = station.location.lat * 100 + station.location.lon;
const r = (n: number) => Math.round(n * 10) / 10; // 소수 첫째자리
const windSpeed = r(6 + (seed % 7));
const waveHeight = r(0.8 + (seed % 20) / 10);
const temp = r(5 + (seed % 8));
const windDir = [0, 45, 90, 135, 180, 225, 270, 315][Math.floor(seed) % 8];
return {
...station,
wind: {
speed: windSpeed,
direction: windDir,
speed_1k: r(windSpeed * 0.8),
speed_3k: r(windSpeed * 1.2),
},
wave: { height: waveHeight, period: 4 + (Math.floor(seed) % 3) },
temperature: { current: temp, feelsLike: r(temp - windSpeed * 0.3) },
pressure: 1010 + (Math.floor(seed) % 12),
visibility: 12 + (Math.floor(seed) % 10),
};
}
async function fetchWeatherData() {
try {
setLoading(true);
setError(null);
if (!API_KEY) {
console.warn('KHOA API 키 미설정 — fallback 데이터를 사용합니다.');
if (isMounted) {
setWeatherStations(stations.map(generateFallbackStation));
setLastUpdate(new Date());
setLoading(false);
}
return;
}
const enrichedStations = await Promise.all(
stations.map(async (station): Promise<EnrichedWeatherStation> => {
try {
const obsCode = OBS_STATION_CODES[station.id];
if (!obsCode) return generateFallbackStation(station);
const obs = await getRecentObservation(obsCode);
if (obs) {
const r = (n: number) => Math.round(n * 10) / 10;
const windSpeed = r(obs.wind_speed ?? 8.5);
const windDir = obs.wind_dir ?? 315;
const waterTemp = r(obs.water_temp ?? 8.0);
const airPres = Math.round(obs.air_pres ?? 1016);
return {
...station,
wind: {
speed: windSpeed,
direction: windDir,
speed_1k: r(windSpeed * 0.8),
speed_3k: r(windSpeed * 1.2),
},
wave: {
height: r(1.0 + windSpeed * 0.1),
period: Math.floor(4 + windSpeed * 0.3),
},
temperature: {
current: waterTemp,
feelsLike: r((obs.air_temp ?? waterTemp) - windSpeed * 0.3),
},
pressure: airPres,
visibility: airPres > 1010 ? 15 + Math.floor(Math.random() * 5) : 10,
};
}
return generateFallbackStation(station);
} catch (stationError) {
console.warn(`관측소 ${station.id} fallback 처리:`, stationError);
return generateFallbackStation(station);
}
})
);
if (isMounted) {
setWeatherStations(enrichedStations);
setLastUpdate(new Date());
setLoading(false);
}
} catch (err) {
console.error('기상 데이터 가져오기 오류:', err);
if (isMounted) {
setError(err instanceof Error ? err.message : '알 수 없는 오류');
setLoading(false);
}
}
}
fetchWeatherData();
// 10분마다 데이터 갱신
const interval = setInterval(fetchWeatherData, 10 * 60 * 1000);
return () => {
isMounted = false;
clearInterval(interval);
};
}, [stations]);
return { weatherStations, loading, error, lastUpdate };
}