@uniai-fe/uds-templates 0.5.28 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/cctv/styles/variables.scss +1 -1
- package/src/weather/_legacy/apis/index.ts +4 -0
- package/src/weather/_legacy/data/response.ts +36 -0
- package/src/weather/_legacy/hooks/index.ts +5 -0
- package/src/weather/{hooks → _legacy/hooks}/useOpenWeatherMap.ts +1 -1
- package/src/weather/{hooks → _legacy/hooks}/useWeatherKorea.ts +4 -7
- package/src/weather/{hooks → _legacy/hooks}/useWeatherKoreaAlert.ts +2 -4
- package/src/weather/_legacy/types/api.ts +221 -0
- package/src/weather/_legacy/types/base.ts +70 -0
- package/src/weather/_legacy/types/index.ts +4 -0
- package/src/weather/_legacy/utils/index.ts +5 -0
- package/src/weather/_legacy/utils/locale.ts +28 -0
- package/src/weather/_legacy/utils/location.ts +139 -0
- package/src/weather/_legacy/utils/weather.ts +460 -0
- package/src/weather/apis/client.ts +339 -0
- package/src/weather/apis/index.ts +2 -4
- package/src/weather/apis/server.ts +264 -0
- package/src/weather/components/icon/Address.tsx +7 -0
- package/src/weather/components/icon/Weather.tsx +7 -6
- package/src/weather/components/page-header/Address.tsx +14 -0
- package/src/weather/components/page-header/Alert.tsx +17 -13
- package/src/weather/components/page-header/Container.tsx +12 -19
- package/src/weather/components/page-header/Forecast.tsx +21 -28
- package/src/weather/components/page-header/NextDays.tsx +10 -0
- package/src/weather/components/page-header/Today.tsx +86 -158
- package/src/weather/components/page-header/index.ts +5 -0
- package/src/weather/data/response.ts +4 -23
- package/src/weather/hooks/index.ts +3 -3
- package/src/weather/hooks/useWeather.ts +52 -0
- package/src/weather/hooks/useWeatherAlert.ts +35 -0
- package/src/weather/index.tsx +2 -2
- package/src/weather/jotai/coordinate.ts +4 -0
- package/src/weather/jotai/farm-idx.ts +4 -0
- package/src/weather/types/api.ts +393 -114
- package/src/weather/types/base.ts +15 -32
- package/src/weather/types/index.ts +0 -3
- package/src/weather/types/page-header.ts +118 -68
- package/src/weather/utils/index.ts +6 -4
- package/src/weather/utils/locale.ts +7 -69
- package/src/weather/utils/location.ts +6 -141
- package/src/weather/utils/weather.ts +53 -456
- package/src/weather/data/alert-regions-meta.json +0 -1286
- package/src/weather/data/weather-regions-meta.json +0 -9833
- package/src/weather/types/provider.ts +0 -34
- package/src/weather/utils/alert.ts +0 -30
- /package/src/weather/{apis → _legacy/apis}/korea/client.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/korea/server.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/open-weather-map/client.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/open-weather-map/server.ts +0 -0
- /package/src/weather/{types → _legacy/types}/korea.ts +0 -0
- /package/src/weather/{types → _legacy/types}/open-weather-map.ts +0 -0
- /package/src/weather/{utils → _legacy/utils}/date-time.ts +0 -0
- /package/src/weather/{utils → _legacy/utils}/validate.ts +0 -0
|
@@ -1,460 +1,57 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
API_Res_WeatherKoreaNow,
|
|
6
|
-
KMA_Res_WeatherItem,
|
|
7
|
-
KMA_Res_WeatherForecast,
|
|
8
|
-
KMA_Res_WeatherNow,
|
|
9
|
-
OWM_Res_Weather_Forecast,
|
|
10
|
-
OWM_Res_Weather_Forecast_Item,
|
|
2
|
+
API_Res_WeatherForecast,
|
|
3
|
+
API_Res_WeatherNow,
|
|
4
|
+
API_Res_WeatherSummary,
|
|
11
5
|
} from "../types";
|
|
12
|
-
import { getMoments } from "./date-time";
|
|
13
6
|
|
|
14
7
|
/**
|
|
15
|
-
* 날씨 도구;
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* @
|
|
28
|
-
* @param {
|
|
29
|
-
* @
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* 강설량 코드
|
|
66
|
-
* @desc
|
|
67
|
-
* - 1(보통 눈); 1cm/h 미만
|
|
68
|
-
* - 2(많은 눈); 1cm/h 이상
|
|
69
|
-
* @param {string} props.pty - 강수형태 코드
|
|
70
|
-
* @param {string} props.snow - 1시간 신적설
|
|
71
|
-
* @return {string | null} 강설량 코드; 1(보통눈), 2(많은눈)
|
|
72
|
-
*/
|
|
73
|
-
const getSnowAmount = ({
|
|
74
|
-
pty,
|
|
75
|
-
snow,
|
|
76
|
-
}: {
|
|
77
|
-
pty: string | null;
|
|
78
|
-
snow: string | null;
|
|
79
|
-
}): string | null => {
|
|
80
|
-
if (!pty || !snow) return null;
|
|
81
|
-
|
|
82
|
-
if (Number(snow) < 1) return "1";
|
|
83
|
-
else return "2";
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 풍속 코드
|
|
88
|
-
* @desc
|
|
89
|
-
* - 1(약한 바람); 4m/s 미만
|
|
90
|
-
* - 2(약간 강한 바람); 4m/s 이상 9m/s 미만
|
|
91
|
-
* - 3(강한 바람); 9m/s 이상
|
|
92
|
-
* @param {string} props.wind - 풍속
|
|
93
|
-
* @return {string | null} 풍속 코드; 1(약한바람), 2(약간강한바람), 3(강한바람)
|
|
94
|
-
*/
|
|
95
|
-
const getWindSpeed = ({ wind }: { wind: string | null }): string | null => {
|
|
96
|
-
if (!wind) return null;
|
|
97
|
-
if (Number(wind) < 4) return "1";
|
|
98
|
-
else if (4 <= Number(wind) && Number(wind) < 9) return "2";
|
|
99
|
-
else return "3";
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 날씨 상태 코드
|
|
104
|
-
* @desc
|
|
105
|
-
* - sky-1(맑음)
|
|
106
|
-
* - sky-2(구름조금)
|
|
107
|
-
* - sky-3(구름많음)
|
|
108
|
-
* - sky-4(흐림)
|
|
109
|
-
* - drop-rain-shower(소나기)
|
|
110
|
-
* - drop-rain(비)
|
|
111
|
-
* - drop-rain-1(약한 비)
|
|
112
|
-
* - drop-rain-2(보통 비)
|
|
113
|
-
* - drop-rain-3(강한 비)
|
|
114
|
-
* - drop-rain-snow(비/눈)
|
|
115
|
-
* - drop-rain-snow-1(약한 비/눈)
|
|
116
|
-
* - drop-rain-snow-2(보통 비/눈)
|
|
117
|
-
* - drop-rain-snow-3(강한 비/눈)
|
|
118
|
-
* - drop-snow(눈)
|
|
119
|
-
* - drop-snow-1(보통 눈)
|
|
120
|
-
* - drop-snow-2(많은 눈)
|
|
121
|
-
* @param {string} props.sky - 하늘상태 코드; 1(맑음), 2(구름조금), 3(구름많음), 4(흐림)
|
|
122
|
-
* @param {string} props.pty - 강수형태 코드; 0(없음), 1(비), 2(비/눈), 3(눈), 4(소나기)
|
|
123
|
-
* @param {string} props.pcp - 1시간 강수량
|
|
124
|
-
* @param {string} props.snow - 1시간 신적설
|
|
125
|
-
*/
|
|
126
|
-
const getWeatherCondition = ({
|
|
127
|
-
sky,
|
|
128
|
-
pty,
|
|
129
|
-
pcp,
|
|
130
|
-
snow,
|
|
131
|
-
}: {
|
|
132
|
-
sky: string | null;
|
|
133
|
-
pty: string | null;
|
|
134
|
-
pcp: string | null;
|
|
135
|
-
snow: string | null;
|
|
136
|
-
}): { code: string | null; name: string | null } => {
|
|
137
|
-
const dropType = () => {
|
|
138
|
-
if (!pty || isNaN(Number(pty)) || pty === "0") return null;
|
|
139
|
-
switch (Number(pty)) {
|
|
140
|
-
case 1:
|
|
141
|
-
return "rain";
|
|
142
|
-
case 2:
|
|
143
|
-
return "rain-snow";
|
|
144
|
-
case 3:
|
|
145
|
-
return "snow";
|
|
146
|
-
case 4:
|
|
147
|
-
return "shower";
|
|
148
|
-
default:
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
if (!dropType()) {
|
|
154
|
-
const skyCode = sky === null ? null : sky;
|
|
155
|
-
const skyName = () => {
|
|
156
|
-
switch (Number(skyCode)) {
|
|
157
|
-
case 1:
|
|
158
|
-
return "맑음";
|
|
159
|
-
case 2:
|
|
160
|
-
return "구름조금";
|
|
161
|
-
case 3:
|
|
162
|
-
return "구름많음";
|
|
163
|
-
case 4:
|
|
164
|
-
return "흐림";
|
|
165
|
-
default:
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
code: `sky-${skyCode}`,
|
|
172
|
-
name: skyName(),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const rainAmount = getRainAmount({ pty, pcp });
|
|
177
|
-
const snowAmount = getSnowAmount({ pty, snow });
|
|
178
|
-
|
|
179
|
-
const dropStrength = (): string | null => {
|
|
180
|
-
const type = dropType();
|
|
181
|
-
if (type === "rain") return rainAmount;
|
|
182
|
-
else if (type === "snow") return snowAmount;
|
|
183
|
-
else if (type === "rain-snow") {
|
|
184
|
-
if (rainAmount === null) return snowAmount;
|
|
185
|
-
if (snowAmount === null) return rainAmount;
|
|
186
|
-
// rainAmount와 snowAmount 중 큰 값을 선택
|
|
187
|
-
return Math.max(Number(rainAmount), Number(snowAmount)).toString();
|
|
188
|
-
}
|
|
189
|
-
// 소나기
|
|
190
|
-
else if (type === "shower") return type;
|
|
191
|
-
return null;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const dropCode = `drop-${dropType()}${dropStrength() ? `-${dropStrength()}` : ""}`;
|
|
195
|
-
const dropName = () => {
|
|
196
|
-
const type = dropType();
|
|
197
|
-
if (type === "rain") {
|
|
198
|
-
if (dropStrength() === null) return "비";
|
|
199
|
-
if (rainAmount === "1") return "약한 비";
|
|
200
|
-
else if (rainAmount === "2") return "보통 비";
|
|
201
|
-
else if (rainAmount === "3") return "강한 비";
|
|
202
|
-
} else if (type === "snow") {
|
|
203
|
-
if (dropStrength() === null) return "눈";
|
|
204
|
-
if (snowAmount === "1") return "보통 눈";
|
|
205
|
-
else if (snowAmount === "2") return "많은 눈";
|
|
206
|
-
} else if (type === "rain-snow") {
|
|
207
|
-
if (dropStrength() === null) return "비/눈";
|
|
208
|
-
if (dropStrength() === "1") return "약한 비/눈";
|
|
209
|
-
else if (dropStrength() === "2") return "보통 비/눈";
|
|
210
|
-
else if (dropStrength() === "3") return "강한 비/눈";
|
|
211
|
-
} else if (type === "shower") {
|
|
212
|
-
return "소나기";
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
code: dropCode,
|
|
219
|
-
name: dropName(),
|
|
220
|
-
};
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* 날씨 도구; OpenWeatherMap 아이콘 코드를 weather icon 코드로 변환
|
|
225
|
-
* @param {string} [icon] OpenWeatherMap icon 코드
|
|
226
|
-
* @return {string} weather icon 코드
|
|
227
|
-
*/
|
|
228
|
-
export const getOpenWeatherMapConditionCode = (icon?: string): string => {
|
|
229
|
-
switch (icon) {
|
|
230
|
-
case "01d":
|
|
231
|
-
case "01n":
|
|
232
|
-
return "sky-1";
|
|
233
|
-
case "02d":
|
|
234
|
-
case "02n":
|
|
235
|
-
case "03d":
|
|
236
|
-
case "03n":
|
|
237
|
-
case "04d":
|
|
238
|
-
case "04n":
|
|
239
|
-
return "sky-2";
|
|
240
|
-
case "09d":
|
|
241
|
-
case "09n":
|
|
242
|
-
return "drop-rain-3";
|
|
243
|
-
case "10d":
|
|
244
|
-
case "10n":
|
|
245
|
-
return "drop-rain-1";
|
|
246
|
-
case "11d":
|
|
247
|
-
case "11n":
|
|
248
|
-
return "drop-rain-thunder";
|
|
249
|
-
case "13d":
|
|
250
|
-
case "13n":
|
|
251
|
-
return "drop-snow";
|
|
252
|
-
case "50d":
|
|
253
|
-
case "50n":
|
|
254
|
-
return "sky-4";
|
|
255
|
-
default:
|
|
256
|
-
return "sky-1";
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const getLocalDateKey = (date: Date): string => {
|
|
261
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
262
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
263
|
-
|
|
264
|
-
return `${date.getFullYear()}-${month}-${day}`;
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const getTimeZoneDateKey = (dateTimeSeconds: number, timezoneSeconds: number) =>
|
|
268
|
-
new Date((dateTimeSeconds + timezoneSeconds) * 1000)
|
|
269
|
-
.toISOString()
|
|
270
|
-
.slice(0, 10);
|
|
271
|
-
|
|
272
|
-
const getOpenWeatherMapDateKey = (
|
|
273
|
-
forecast?: OWM_Res_Weather_Forecast,
|
|
274
|
-
): string => {
|
|
275
|
-
const timezoneSeconds = forecast?.city?.timezone;
|
|
276
|
-
|
|
277
|
-
if (typeof timezoneSeconds === "number" && Number.isFinite(timezoneSeconds)) {
|
|
278
|
-
return getTimeZoneDateKey(Math.floor(Date.now() / 1000), timezoneSeconds);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return getLocalDateKey(new Date());
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const getForecastItemDateKey = (
|
|
285
|
-
item: OWM_Res_Weather_Forecast_Item,
|
|
286
|
-
timezoneSeconds = 0,
|
|
287
|
-
): string =>
|
|
288
|
-
Number.isFinite(item.dt)
|
|
289
|
-
? getTimeZoneDateKey(item.dt, timezoneSeconds)
|
|
290
|
-
: item.dt_txt?.slice(0, 10) || getLocalDateKey(new Date(item.dt * 1000));
|
|
291
|
-
|
|
292
|
-
const toOpenWeatherMapDayForecast = (
|
|
293
|
-
items: OWM_Res_Weather_Forecast_Item[],
|
|
294
|
-
): API_Res_WeatherKoreaNextDays | undefined => {
|
|
295
|
-
if (items.length === 0) return undefined;
|
|
296
|
-
|
|
297
|
-
const conditionItem =
|
|
298
|
-
items.find(item => item.dt_txt?.includes("12:00")) ||
|
|
299
|
-
items[Math.floor(items.length / 2)];
|
|
300
|
-
const temperatures = items
|
|
301
|
-
.flatMap(item => [item.main.temp_min, item.main.temp_max, item.main.temp])
|
|
302
|
-
.filter((temperature): temperature is number =>
|
|
303
|
-
Number.isFinite(temperature),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
sky: null,
|
|
308
|
-
drop: null,
|
|
309
|
-
rainAmount: null,
|
|
310
|
-
snowAmount: null,
|
|
311
|
-
windSpeed: null,
|
|
312
|
-
condition: getOpenWeatherMapConditionCode(
|
|
313
|
-
conditionItem?.weather?.[0]?.icon,
|
|
314
|
-
),
|
|
315
|
-
conditionName: conditionItem?.weather?.[0]?.description || null,
|
|
316
|
-
max_temperature:
|
|
317
|
-
temperatures.length > 0 ? Math.round(Math.max(...temperatures)) : null,
|
|
318
|
-
min_temperature:
|
|
319
|
-
temperatures.length > 0 ? Math.round(Math.min(...temperatures)) : null,
|
|
320
|
-
};
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* 날씨 도구; OpenWeatherMap 예보 응답을 page-header next days 형태로 변환
|
|
325
|
-
* @param {OWM_Res_Weather_Forecast} [forecast] OpenWeatherMap 예보 응답
|
|
326
|
-
* @return {{ day_1?: API_Res_WeatherKoreaNextDays; day_2?: API_Res_WeatherKoreaNextDays }} 내일/모레 예보
|
|
327
|
-
*/
|
|
328
|
-
export const getOpenWeatherMapNextDays = (
|
|
329
|
-
forecast?: OWM_Res_Weather_Forecast,
|
|
330
|
-
): {
|
|
331
|
-
day_1?: API_Res_WeatherKoreaNextDays;
|
|
332
|
-
day_2?: API_Res_WeatherKoreaNextDays;
|
|
333
|
-
} => {
|
|
334
|
-
if (!Array.isArray(forecast?.list)) return {};
|
|
335
|
-
|
|
336
|
-
const timezoneSeconds = forecast.city?.timezone ?? 0;
|
|
337
|
-
const todayKey = getOpenWeatherMapDateKey(forecast);
|
|
338
|
-
const grouped = forecast.list.reduce<
|
|
339
|
-
Record<string, OWM_Res_Weather_Forecast_Item[]>
|
|
340
|
-
>((days, item) => {
|
|
341
|
-
const dateKey = getForecastItemDateKey(item, timezoneSeconds);
|
|
342
|
-
if (dateKey === todayKey) return days;
|
|
343
|
-
|
|
344
|
-
days[dateKey] = [...(days[dateKey] || []), item];
|
|
345
|
-
return days;
|
|
346
|
-
}, {});
|
|
347
|
-
const [day1Key, day2Key] = Object.keys(grouped).sort();
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
day_1: day1Key ? toOpenWeatherMapDayForecast(grouped[day1Key]) : undefined,
|
|
351
|
-
day_2: day2Key ? toOpenWeatherMapDayForecast(grouped[day2Key]) : undefined,
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* 날씨 도구; OpenWeatherMap 예보 응답을 오늘 최고/최저 형태로 변환
|
|
357
|
-
* @param {OWM_Res_Weather_Forecast} [forecast] OpenWeatherMap 예보 응답
|
|
358
|
-
* @return {API_Res_WeatherKoreaNextDays | undefined} 오늘 최고/최저 예보
|
|
359
|
-
*/
|
|
360
|
-
export const getOpenWeatherMapTodayForecast = (
|
|
361
|
-
forecast?: OWM_Res_Weather_Forecast,
|
|
362
|
-
): API_Res_WeatherKoreaNextDays | undefined => {
|
|
363
|
-
if (!Array.isArray(forecast?.list)) return undefined;
|
|
364
|
-
|
|
365
|
-
const timezoneSeconds = forecast.city?.timezone ?? 0;
|
|
366
|
-
const todayKey = getOpenWeatherMapDateKey(forecast);
|
|
367
|
-
const todayItems = forecast.list.filter(
|
|
368
|
-
item => getForecastItemDateKey(item, timezoneSeconds) === todayKey,
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
return toOpenWeatherMapDayForecast(todayItems);
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
export function now(
|
|
375
|
-
outputResponse: API_Res_WeatherKoreaNow,
|
|
376
|
-
apiResponse: KMA_Res_WeatherNow,
|
|
377
|
-
): API_Res_WeatherKoreaNow {
|
|
378
|
-
const res: API_Res_WeatherKoreaNow = { ...outputResponse };
|
|
379
|
-
|
|
380
|
-
const items = apiResponse?.response?.body?.items?.item;
|
|
381
|
-
if (!items || !Array.isArray(items) || items.length === 0) return res;
|
|
382
|
-
|
|
383
|
-
const sky = getValueNow(items, "SKY"); // 없음
|
|
384
|
-
const pty = getValueNow(items, "PTY"); // 강수형태
|
|
385
|
-
const pcp = getValueNow(items, "RN1"); // 1시간 강수량 PCP없음
|
|
386
|
-
const snow = getValueNow(items, "SNO"); // 없음
|
|
387
|
-
const wind = getValueNow(items, "WSD");
|
|
388
|
-
|
|
389
|
-
const condition = getWeatherCondition({ sky, pty, pcp, snow });
|
|
390
|
-
|
|
391
|
-
const getDailyState: API_Res_WeatherKoreaBase = {
|
|
392
|
-
sky,
|
|
393
|
-
drop: pty,
|
|
394
|
-
rainAmount: getRainAmount({ pty, pcp }),
|
|
395
|
-
snowAmount: getSnowAmount({ pty, snow }),
|
|
396
|
-
windSpeed: getWindSpeed({ wind }),
|
|
397
|
-
condition: condition.code,
|
|
398
|
-
conditionName: condition.name,
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
res.today.temperature = getValueNow(items, "T1H");
|
|
402
|
-
res.today.humidity = getValueNow(items, "REH");
|
|
403
|
-
|
|
404
|
-
Object.assign(res.today, getDailyState);
|
|
405
|
-
|
|
406
|
-
return res;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export function forecast(
|
|
410
|
-
outputResponse: API_Res_WeatherKoreaForecast,
|
|
411
|
-
apiResponse: KMA_Res_WeatherForecast,
|
|
412
|
-
): API_Res_WeatherKoreaForecast {
|
|
413
|
-
const res: API_Res_WeatherKoreaForecast = { ...outputResponse };
|
|
414
|
-
|
|
415
|
-
const items = apiResponse?.response?.body?.items?.item;
|
|
416
|
-
if (!items || !Array.isArray(items) || items.length === 0) return res;
|
|
417
|
-
|
|
418
|
-
const { today, tomorrow, dayAfterTomorrow } = getMoments();
|
|
419
|
-
|
|
420
|
-
const getDailyState = (date: string): API_Res_WeatherKoreaBase => {
|
|
421
|
-
const sky = getValueForecast(items, date, "SKY");
|
|
422
|
-
const pty = getValueForecast(items, date, "PTY");
|
|
423
|
-
const pcp = getValueForecast(items, date, "PCP");
|
|
424
|
-
const snow = getValueForecast(items, date, "SNO");
|
|
425
|
-
const wind = getValueForecast(items, date, "WSD");
|
|
426
|
-
|
|
427
|
-
const condition = getWeatherCondition({ sky, pty, pcp, snow });
|
|
428
|
-
|
|
429
|
-
return {
|
|
430
|
-
sky,
|
|
431
|
-
drop: pty,
|
|
432
|
-
rainAmount: getRainAmount({ pty, pcp }),
|
|
433
|
-
snowAmount: getSnowAmount({ pty, snow }),
|
|
434
|
-
windSpeed: getWindSpeed({ wind }),
|
|
435
|
-
condition: condition.code,
|
|
436
|
-
conditionName: condition.name,
|
|
437
|
-
};
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
res.today.temperature = getValueForecast(items, today, "TMP");
|
|
441
|
-
res.today.max_temperature = getValueForecast(items, today, "TMX");
|
|
442
|
-
res.today.min_temperature = getValueForecast(items, today, "TMN");
|
|
443
|
-
res.today.humidity = getValueForecast(items, today, "REH");
|
|
444
|
-
Object.assign(res.today, getDailyState(today));
|
|
445
|
-
|
|
446
|
-
res.day_1.max_temperature = getValueForecast(items, tomorrow, "TMX");
|
|
447
|
-
res.day_1.min_temperature = getValueForecast(items, tomorrow, "TMN");
|
|
448
|
-
Object.assign(res.day_1, getDailyState(tomorrow));
|
|
449
|
-
|
|
450
|
-
res.day_2.max_temperature = getValueForecast(items, dayAfterTomorrow, "TMX");
|
|
451
|
-
res.day_2.min_temperature = getValueForecast(items, dayAfterTomorrow, "TMN");
|
|
452
|
-
Object.assign(res.day_2, getDailyState(dayAfterTomorrow));
|
|
453
|
-
|
|
454
|
-
return res;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export const extractWeatherSummary = {
|
|
458
|
-
forecast,
|
|
459
|
-
now,
|
|
460
|
-
};
|
|
8
|
+
* 날씨 도구; record shape 여부 확인.
|
|
9
|
+
* @util
|
|
10
|
+
* @desc unknown 값을 null이 아닌 object record로 좁힌다.
|
|
11
|
+
* @param {unknown} value 확인할 값
|
|
12
|
+
* @return {boolean} record shape 여부
|
|
13
|
+
*/
|
|
14
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
15
|
+
typeof value === "object" && value !== null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 날씨 도구; backend normalized 현재 날씨 응답 여부 확인.
|
|
19
|
+
* @util
|
|
20
|
+
* @desc unknown 응답이 weather now shape로 사용할 수 있는지 좁힌다.
|
|
21
|
+
* @param {unknown} response 날씨 응답
|
|
22
|
+
* @return {boolean} normalized 현재 날씨 응답 여부
|
|
23
|
+
*/
|
|
24
|
+
export const isWeatherNowResponse = (
|
|
25
|
+
response?: unknown,
|
|
26
|
+
): response is API_Res_WeatherNow =>
|
|
27
|
+
isRecord(response) && isRecord(response.today);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 날씨 도구; backend normalized 예보 날씨 응답 여부 확인.
|
|
31
|
+
* @util
|
|
32
|
+
* @desc unknown 응답이 weather forecast shape로 사용할 수 있는지 좁힌다.
|
|
33
|
+
* @param {unknown} response 날씨 응답
|
|
34
|
+
* @return {boolean} normalized 예보 날씨 응답 여부
|
|
35
|
+
*/
|
|
36
|
+
export const isWeatherForecastResponse = (
|
|
37
|
+
response?: unknown,
|
|
38
|
+
): response is API_Res_WeatherForecast =>
|
|
39
|
+
isRecord(response) &&
|
|
40
|
+
isRecord(response.today) &&
|
|
41
|
+
isRecord(response.day_1) &&
|
|
42
|
+
isRecord(response.day_2);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 날씨 도구; backend normalized summary 응답 여부 확인.
|
|
46
|
+
* @util
|
|
47
|
+
* @desc unknown 응답이 weather summary shape로 사용할 수 있는지 좁힌다.
|
|
48
|
+
* @param {unknown} response 날씨 응답
|
|
49
|
+
* @return {boolean} normalized summary 응답 여부
|
|
50
|
+
*/
|
|
51
|
+
export const isWeatherSummaryResponse = (
|
|
52
|
+
response?: unknown,
|
|
53
|
+
): response is API_Res_WeatherSummary =>
|
|
54
|
+
isRecord(response) &&
|
|
55
|
+
isWeatherNowResponse(response.now) &&
|
|
56
|
+
isWeatherForecastResponse(response.forecast) &&
|
|
57
|
+
isRecord(response.alert);
|