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 { 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, }; }