import axios from 'axios' const API_KEY = import.meta.env.VITE_WEATHER_API_KEY const BASE_URL = 'https://apihub.kma.go.kr/api/typ01/url' // 위경도 → 격자 변환 (기상청 제공 알고리즘) export function latLngToGrid(lat: number, lng: number) { const RE = 6371.00877 // 지구 반경(km) const GRID = 5.0 // 격자 간격(km) const SLAT1 = 30.0 // 표준위도1 const SLAT2 = 60.0 // 표준위도2 const OLON = 126.0 // 기준점 경도 const OLAT = 38.0 // 기준점 위도 const XO = 43 // 기준점 X좌표 const YO = 136 // 기준점 Y좌표 const DEGRAD = Math.PI / 180.0 const re = RE / GRID const slat1 = SLAT1 * DEGRAD const slat2 = SLAT2 * DEGRAD const olon = OLON * DEGRAD const olat = OLAT * DEGRAD let sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5) sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn) let sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5) sf = (Math.pow(sf, sn) * Math.cos(slat1)) / sn let ro = Math.tan(Math.PI * 0.25 + olat * 0.5) ro = (re * sf) / Math.pow(ro, sn) let ra = Math.tan(Math.PI * 0.25 + lat * DEGRAD * 0.5) ra = (re * sf) / Math.pow(ra, sn) let theta = lng * DEGRAD - olon if (theta > Math.PI) theta -= 2.0 * Math.PI if (theta < -Math.PI) theta += 2.0 * Math.PI theta *= sn const x = Math.floor(ra * Math.sin(theta) + XO + 0.5) const y = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5) return { nx: x, ny: y } } // 현재 날짜/시간을 기상청 API 형식으로 변환 export function getCurrentBaseTime() { const now = new Date() const hours = now.getHours() const minutes = now.getMinutes() // 기상청 API는 매시간 40분에 업데이트되므로, 40분 이전이면 이전 시간 사용 let baseHour = hours if (minutes < 40) { baseHour = hours - 1 if (baseHour < 0) baseHour = 23 } const baseDate = now.toISOString().slice(0, 10).replace(/-/g, '') const baseTime = String(baseHour).padStart(2, '0') + '00' return { baseDate, baseTime } } // 초단기실황 조회 (현재 기상 상태) export async function getUltraShortNowcast(lat: number, lng: number) { const { nx, ny } = latLngToGrid(lat, lng) const { baseDate, baseTime } = getCurrentBaseTime() try { const response = await axios.get(`${BASE_URL}/getUltraSrtNcst`, { params: { serviceKey: API_KEY, numOfRows: 10, pageNo: 1, base_date: baseDate, base_time: baseTime, nx, ny, dataType: 'JSON', }, }) const items = response.data.response.body.items.item // 데이터 파싱 const weather: Record = {} items.forEach((item: Record) => { switch (item.category) { case 'T1H': // 기온 weather.temperature = parseFloat(item.obsrValue) break case 'RN1': // 강수량 weather.rainfall = parseFloat(item.obsrValue) break case 'WSD': // 풍속 weather.windSpeed = parseFloat(item.obsrValue) break case 'VEC': // 풍향 weather.windDirection = parseInt(item.obsrValue) break case 'REH': // 습도 weather.humidity = parseInt(item.obsrValue) break case 'PTY': // 강수형태 weather.precipType = parseInt(item.obsrValue) break } }) return weather } catch (error) { console.error('기상청 API 오류:', error) throw error } } // 단기예보 조회 (최대 72시간) export async function getShortForecast(lat: number, lng: number) { const { nx, ny } = latLngToGrid(lat, lng) const now = new Date() // 단기예보 base_time: 02:00, 05:00, 08:00, 11:00, 14:00, 17:00, 20:00, 23:00 const baseHours = [2, 5, 8, 11, 14, 17, 20, 23] const currentHour = now.getHours() const baseHour = baseHours.reduce((prev, curr) => curr <= currentHour ? curr : prev ) const baseDate = now.toISOString().slice(0, 10).replace(/-/g, '') const baseTime = String(baseHour).padStart(2, '0') + '00' try { const response = await axios.get(`${BASE_URL}/getVilageFcst`, { params: { serviceKey: API_KEY, numOfRows: 1000, pageNo: 1, base_date: baseDate, base_time: baseTime, nx, ny, dataType: 'JSON', }, }) const items = response.data.response.body.items.item // 시간별로 그룹화 const forecastByTime: Record> = {} items.forEach((item: Record) => { const key = `${item.fcstDate}_${item.fcstTime}` if (!forecastByTime[key]) { forecastByTime[key] = { date: item.fcstDate, time: item.fcstTime, } } switch (item.category) { case 'TMP': // 기온 forecastByTime[key].temperature = parseFloat(item.fcstValue) break case 'WSD': // 풍속 forecastByTime[key].windSpeed = parseFloat(item.fcstValue) break case 'VEC': // 풍향 forecastByTime[key].windDirection = parseInt(item.fcstValue) break case 'SKY': // 하늘상태 forecastByTime[key].skyCondition = parseInt(item.fcstValue) break case 'POP': // 강수확률 forecastByTime[key].precipProbability = parseInt(item.fcstValue) break case 'WAV': // 파고 forecastByTime[key].waveHeight = parseFloat(item.fcstValue) break } }) return Object.values(forecastByTime) } catch (error) { console.error('단기예보 API 오류:', error) throw error } } // 풍향을 16방위로 변환 export function windDirectionToText(degree: number): string { const directions = [ 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ] const index = Math.round((degree % 360) / 22.5) % 16 return directions[index] } // 해상 기상 정보 (Mock - 실제로는 해양기상청 API 사용) // eslint-disable-next-line @typescript-eslint/no-unused-vars export async function getMarineWeather(lat: number, lng: number) { // TODO: 해양기상청 API 연동 // 현재는 Mock 데이터 반환 return { waveHeight: 1.2, // 파고 (m) waveDirection: 135, // 파향 (도) wavePeriod: 5.5, // 주기 (초) seaTemperature: 12.5, // 수온 (°C) currentSpeed: 0.3, // 해류속도 (m/s) currentDirection: 180, // 해류방향 (도) visibility: 15, // 시정 (km) } }