refactor(weather): KHOA API 병렬 요청 및 지도 컨트롤 위치 개선
This commit is contained in:
부모
70fe23e40b
커밋
1825bcbb5f
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend && npm install # Frontend
|
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 build # tsc -b && vite build
|
||||||
npm run lint # ESLint
|
npm run lint # ESLint
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,9 @@
|
|||||||
### 변경
|
### 변경
|
||||||
- InfoLayerSection을 공통 컴포넌트로 이동 (prediction → common/layer)
|
- InfoLayerSection을 공통 컴포넌트로 이동 (prediction → common/layer)
|
||||||
|
|
||||||
|
### 수정
|
||||||
|
- 기상정보 탭 로딩 지연 개선: KHOA API 관측소 요청을 병렬 처리로 전환 및 API 키 미설정 시 즉시 fallback 처리
|
||||||
|
|
||||||
### 기타
|
### 기타
|
||||||
- DB migration 033: SPIL_QTY NUMERIC(22,10) 확장 (대용량 HNS 유출량 지원)
|
- DB migration 033: SPIL_QTY NUMERIC(22,10) 확장 (대용량 HNS 유출량 지원)
|
||||||
|
|
||||||
|
|||||||
@ -139,8 +139,8 @@ function MapOverlayControls({
|
|||||||
|
|
||||||
return (
|
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}>
|
<button title="줌 인" onClick={() => map?.zoomIn()} className={btn}>
|
||||||
+
|
+
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
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 {
|
interface WeatherStation {
|
||||||
id: string;
|
id: string;
|
||||||
@ -68,71 +68,64 @@ export function useWeatherData(stations: WeatherStation[]) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const enrichedStations: EnrichedWeatherStation[] = [];
|
if (!API_KEY) {
|
||||||
let apiFailed = false;
|
console.warn('KHOA API 키 미설정 — fallback 데이터를 사용합니다.');
|
||||||
|
if (isMounted) {
|
||||||
for (const station of stations) {
|
setWeatherStations(stations.map(generateFallbackStation));
|
||||||
if (apiFailed) {
|
setLastUpdate(new Date());
|
||||||
enrichedStations.push(generateFallbackStation(station));
|
setLoading(false);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
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) {
|
if (isMounted) {
|
||||||
setWeatherStations(enrichedStations);
|
setWeatherStations(enrichedStations);
|
||||||
setLastUpdate(new Date());
|
setLastUpdate(new Date());
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (apiFailed) {
|
|
||||||
setError('KHOA API 연결 실패 — fallback 데이터 사용 중');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('기상 데이터 가져오기 오류:', err);
|
console.error('기상 데이터 가져오기 오류:', err);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// KHOA (국립해양조사원) API 서비스
|
// KHOA (국립해양조사원) API 서비스
|
||||||
|
|
||||||
// API Key를 환경변수에서 로드 (소스코드 노출 방지)
|
// 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 BASE_URL = 'https://apis.data.go.kr/1192136/oceanCondition/GetOceanConditionApiService';
|
||||||
const RECENT_OBS_URL = 'https://apis.data.go.kr/1192136/dtRecent/GetDTRecentApiService';
|
const RECENT_OBS_URL = 'https://apis.data.go.kr/1192136/dtRecent/GetDTRecentApiService';
|
||||||
|
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user