// KHOA (국립해양조사원) API 서비스 // API Key를 환경변수에서 로드 (소스코드 노출 방지) const API_KEY = import.meta.env.VITE_DATA_GO_KR_API_KEY || '' const BASE_URL = 'https://apis.data.go.kr/1192136/oceanCondition/GetOceanConditionApiService' const RECENT_OBS_URL = 'https://apis.data.go.kr/1192136/dtRecent/GetDTRecentApiService' // 지역 유형 (총 20개 지역) export const OCEAN_REGIONS = { 전국: 'KOREA', 인천: 'INCHEON', 군산: 'GUNSAN', 목포: 'MOKPO', 제주: 'JEJU', 여수: 'YEOSU', 통영: 'TONGYEONG', 부산: 'BUSAN', 울산: 'ULSAN', 포항: 'POHANG', 속초: 'SOKCHO', 동해: 'DONGHAE' } as const export interface OceanForecastData { imgFileNm: string // 이미지 파일명 imgFilePath: string // 이미지 파일경로 ofcBrnchId: string // 해황예보도 지점코드 ofcBrnchNm: string // 해황예보도 지점이름 ofcFrcstTm: string // 해황예보도 예보시각 ofcFrcstYmd: string // 해황예보도 예보일자 } interface OceanForecastApiResponse { header: { resultCode: string; resultMsg: string } body: { items: { item: OceanForecastData[] } pageNo: number numOfRows: number totalCount: number } } /** * 해황예보도 조회 * @param regionType 지역 유형 (KOREA, INCHEON, BUSAN 등) * @returns 해황예보 이미지 데이터 배열 */ export async function getOceanForecast( regionType: string = 'KOREA' ): Promise { try { const params = new URLSearchParams({ serviceKey: API_KEY, areaCode: regionType, type: 'json', }) const response = await fetch(`${BASE_URL}?${params}`) if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`) } const data = await response.json() as OceanForecastApiResponse return data?.body?.items?.item ?? [] } catch (error) { console.error('해황예보도 조회 오류:', error) throw error } } /** * 현재 시간에 가장 가까운 예보 데이터 찾기 * @param forecasts 예보 데이터 배열 * @returns 가장 최근 예보 데이터 */ export function getLatestForecast(forecasts: OceanForecastData[]): OceanForecastData | null { if (!forecasts || forecasts.length === 0) return null const sorted = [...forecasts].sort((a, b) => { const dateTimeA = `${a.ofcFrcstYmd}${a.ofcFrcstTm}` const dateTimeB = `${b.ofcFrcstYmd}${b.ofcFrcstTm}` return dateTimeB.localeCompare(dateTimeA) }) return sorted[0] } /** * 특정 시간대의 예보 데이터 필터링 * @param forecasts 예보 데이터 배열 * @param targetDay 목표 날짜 (YYYYMMDD) * @param targetHour 목표 시간 (HH) * @returns 필터링된 예보 데이터 */ export function getForecastByTime( forecasts: OceanForecastData[], targetDay: string, targetHour: string ): OceanForecastData | null { return ( forecasts.find((f) => f.ofcFrcstYmd === targetDay && f.ofcFrcstTm === targetHour) || null ) } // 조위관측소 코드 매핑 export const OBS_STATION_CODES: Record = { incheon: 'DT_0001', gunsan: 'DT_0005', mokpo: 'DT_0008', yeosu: 'DT_0010', tongyeong: 'DT_0012', ulsan: 'DT_0015', pohang: 'DT_0016', donghae: 'DT_0018', sokcho: 'DT_0019', jeju: 'DT_0020', } export interface RecentObservation { water_temp: number | null // 수온 (°C) air_temp: number | null // 기온 (°C) air_pres: number | null // 기압 (hPa) wind_dir: number | null // 풍향 (°) wind_speed: number | null // 풍속 (m/s) current_dir: number | null // 유향 (°) current_speed: number | null // 유속 (m/s) tide_level: number | null // 조위 (cm) } /** * 조위관측소 최신 관측데이터 조회 */ export async function getRecentObservation(obsCode: string): Promise { try { const now = new Date() const reqDate = formatDateForAPI(now) const params = new URLSearchParams({ serviceKey: API_KEY, obsCode, type: 'json', reqDate, }) const response = await fetch(`${RECENT_OBS_URL}?${params}`) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const data = await response.json() const item = data.body ? data.body.items.item[0] : null if (!item) return null return { water_temp: item.wtem != null ? parseFloat(item.wtem) : null, air_temp: item.artmp != null ? parseFloat(item.artmp) : null, air_pres: item.atmpr != null ? parseFloat(item.atmpr) : null, wind_dir: item.wndrct != null ? parseFloat(item.wndrct) : null, wind_speed: item.wspd != null ? parseFloat(item.wspd) : null, current_dir: item.crdir != null ? parseFloat(item.crdir) : null, current_speed: item.crsp != null ? parseFloat(item.crsp) : null, tide_level: item.bscTdlvHgt != null ? parseFloat(item.bscTdlvHgt) : null, } } catch (error) { console.error(`관측소 ${obsCode} 데이터 조회 오류:`, error) return null } } /** * 날짜 문자열을 YYYYMMDD 형식으로 변환 */ export function formatDateForAPI(date: Date): string { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}${month}${day}` } /** * 시간을 HH 형식으로 변환 (3시간 단위로 반올림) */ export function formatHourForAPI(date: Date): string { const hour = date.getHours() // 3시간 단위로 반올림 (00, 03, 06, 09, 12, 15, 18, 21) const roundedHour = Math.floor(hour / 3) * 3 return String(roundedHour).padStart(2, '0') }