152 lines
4.7 KiB
TypeScript
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 };
|
|
}
|