wing-ops/frontend/src/tabs/weather/services/weatherUtils.ts

122 lines
4.6 KiB
TypeScript

import { getRecentObservation, OBS_STATION_CODES } from './khoaApi';
import type { WeatherSnapshot } from '@common/store/weatherSnapshotStore';
const CARDINAL_LABELS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] as const;
export function degreesToCardinal(deg: number): string {
const idx = Math.round(((deg % 360) + 360) % 360 / 22.5) % 16;
return CARDINAL_LABELS[idx];
}
const BASE_STATIONS = [
{ id: 'incheon', name: '인천', location: { lat: 37.45, lon: 126.43 } },
{ id: 'ulsan', name: '울산', location: { lat: 35.52, lon: 129.38 } },
{ id: 'yeosu', name: '여수', location: { lat: 34.74, lon: 127.75 } },
{ id: 'jeju', name: '제주', location: { lat: 33.51, lon: 126.53 } },
{ id: 'pohang', name: '포항', location: { lat: 36.03, lon: 129.38 } },
{ id: 'mokpo', name: '목포', location: { lat: 34.78, lon: 126.38 } },
{ id: 'gunsan', name: '군산', location: { lat: 35.97, lon: 126.7 } },
{ id: 'sokcho', name: '속초', location: { lat: 38.21, lon: 128.59 } },
{ id: 'tongyeong', name: '통영', location: { lat: 34.83, lon: 128.43 } },
{ id: 'donghae', name: '동해', location: { lat: 37.52, lon: 129.14 } },
];
export async function fetchWeatherSnapshotForCoord(
lat: number,
lon: number,
): Promise<WeatherSnapshot> {
const nearest = BASE_STATIONS.reduce((best, s) => {
const d = (s.location.lat - lat) ** 2 + (s.location.lon - lon) ** 2;
const bd = (best.location.lat - lat) ** 2 + (best.location.lon - lon) ** 2;
return d < bd ? s : best;
});
const r = (n: number) => Math.round(n * 10) / 10;
const obsCode = OBS_STATION_CODES[nearest.id];
const obs = obsCode ? await getRecentObservation(obsCode) : null;
const windIcon = (spd: number) => spd > 12 ? '🌧️' : spd > 8 ? '🌦️' : spd > 5 ? '⛅' : '☀️';
const mockAstronomy = {
sunrise: '07:12', sunset: '17:58',
moonrise: '19:35', moonset: '01:50',
moonPhase: '상현달 14일', tidalRange: 6.7,
};
if (obs) {
const windSpeed = r(obs.wind_speed ?? 8.0);
const windDir = obs.wind_dir ?? 315;
const waterTemp = r(obs.water_temp ?? 8.0);
const airTemp = r(obs.air_temp ?? waterTemp);
const pressure = Math.round(obs.air_pres ?? 1013);
const waveHeight = r(1.0 + windSpeed * 0.1);
return {
stationName: nearest.name,
capturedAt: new Date().toISOString(),
wind: {
speed: windSpeed,
direction: windDir,
directionLabel: degreesToCardinal(windDir),
speed_1k: r(windSpeed * 0.8),
speed_3k: r(windSpeed * 1.2),
},
wave: {
height: waveHeight,
maxHeight: r(waveHeight * 1.6),
period: Math.floor(4 + windSpeed * 0.3),
direction: degreesToCardinal(windDir + 45),
},
temperature: { current: waterTemp, feelsLike: r(airTemp - windSpeed * 0.3) },
pressure,
visibility: pressure > 1010 ? 15 : 10,
salinity: 31.2,
forecast: [0, 3, 6, 9, 12].map((h, i) => ({
time: `${h}`,
icon: windIcon(windSpeed + (i * 0.3 - 0.3)),
temperature: r(airTemp - i * 0.3),
windSpeed: r(windSpeed + (i * 0.2 - 0.2)),
})),
astronomy: mockAstronomy,
alert: windSpeed > 14 ? '풍랑주의보 예상' : undefined,
};
}
// Fallback: 시드 기반 더미
const seed = nearest.location.lat * 100 + nearest.location.lon;
const windSpeed = r(6 + (seed % 7));
const windDir = [0, 45, 90, 135, 180, 225, 270, 315][Math.floor(seed) % 8];
const waveHeight = r(0.8 + (seed % 20) / 10);
const temp = r(5 + (seed % 8));
return {
stationName: nearest.name,
capturedAt: new Date().toISOString(),
wind: {
speed: windSpeed,
direction: windDir,
directionLabel: degreesToCardinal(windDir),
speed_1k: r(windSpeed * 0.8),
speed_3k: r(windSpeed * 1.2),
},
wave: {
height: waveHeight,
maxHeight: r(waveHeight * 1.6),
period: 4 + (Math.floor(seed) % 3),
direction: degreesToCardinal(windDir + 45),
},
temperature: { current: temp, feelsLike: r(temp - windSpeed * 0.3) },
pressure: 1010 + (Math.floor(seed) % 12),
visibility: 12 + (Math.floor(seed) % 10),
salinity: 31.2,
forecast: [0, 3, 6, 9, 12].map((h, i) => ({
time: `${h}`,
icon: windIcon(windSpeed + (i * 0.3 - 0.3)),
temperature: r(temp - i * 0.3),
windSpeed: r(windSpeed + (i * 0.2 - 0.2)),
})),
astronomy: mockAstronomy,
alert: windSpeed > 14 ? '풍랑주의보 예상' : undefined,
};
}