Merge pull request 'feat(weather): KHOA API ���� ��û �� ���� ��Ʈ�� ��ġ ����' (#193) from feature/weather-tab-development into develop
This commit is contained in:
커밋
d69e057d8b
@ -19,7 +19,7 @@
|
||||
|
||||
```bash
|
||||
cd frontend && npm install # Frontend
|
||||
npm run dev # Vite dev (localhost:5173)
|
||||
npm run dev # Vite dev (localhost:5174)
|
||||
npm run build # tsc -b && vite build
|
||||
npm run lint # ESLint
|
||||
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
|
||||
### 변경
|
||||
- InfoLayerSection을 공통 컴포넌트로 이동 (prediction → common/layer)
|
||||
- 기상 탭: 지도 오버레이 컨트롤 위치 우측 상단으로 조정
|
||||
|
||||
### 수정
|
||||
- 기상정보 탭 로딩 지연 개선: KHOA API 관측소 요청을 병렬 처리로 전환 및 API 키 미설정 시 즉시 fallback 처리
|
||||
|
||||
### 기타
|
||||
- DB migration 033: SPIL_QTY NUMERIC(22,10) 확장 (대용량 HNS 유출량 지원)
|
||||
|
||||
@ -139,8 +139,8 @@ function MapOverlayControls({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 좌측 컨트롤 컬럼 */}
|
||||
<div className="absolute top-[80px] left-[10px] z-10 flex flex-col gap-1">
|
||||
{/* 우측 컨트롤 컬럼 */}
|
||||
<div className="absolute top-[10px] right-[10px] z-10 flex flex-col gap-1">
|
||||
{/* 줌 */}
|
||||
<button title="줌 인" onClick={() => map?.zoomIn()} className={btn}>
|
||||
+
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getRecentObservation, OBS_STATION_CODES } from '../services/khoaApi';
|
||||
import { getRecentObservation, OBS_STATION_CODES, API_KEY } from '../services/khoaApi';
|
||||
|
||||
interface WeatherStation {
|
||||
id: string;
|
||||
@ -68,71 +68,64 @@ export function useWeatherData(stations: WeatherStation[]) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const enrichedStations: EnrichedWeatherStation[] = [];
|
||||
let apiFailed = false;
|
||||
|
||||
for (const station of stations) {
|
||||
if (apiFailed) {
|
||||
enrichedStations.push(generateFallbackStation(station));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const obsCode = OBS_STATION_CODES[station.id];
|
||||
if (!obsCode) {
|
||||
enrichedStations.push(generateFallbackStation(station));
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
enrichedStations.push({
|
||||
...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,
|
||||
});
|
||||
} else {
|
||||
enrichedStations.push(generateFallbackStation(station));
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
} catch (stationError) {
|
||||
if (!apiFailed) {
|
||||
console.warn('KHOA API 연결 실패, fallback 데이터를 사용합니다:', stationError);
|
||||
apiFailed = true;
|
||||
}
|
||||
enrichedStations.push(generateFallbackStation(station));
|
||||
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);
|
||||
if (apiFailed) {
|
||||
setError('KHOA API 연결 실패 — fallback 데이터 사용 중');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('기상 데이터 가져오기 오류:', err);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// KHOA (국립해양조사원) API 서비스
|
||||
|
||||
// API Key를 환경변수에서 로드 (소스코드 노출 방지)
|
||||
const API_KEY = import.meta.env.VITE_DATA_GO_KR_API_KEY || '';
|
||||
export 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';
|
||||
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user