@uniai-fe/uds-templates 0.1.15 → 0.1.17
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/dist/styles.css +139 -10
- package/package.json +18 -11
- package/src/auth/common/complete/Template.tsx +3 -0
- package/src/auth/common/complete/types.ts +4 -1
- package/src/auth/common/find/markup/CodeStep.tsx +2 -1
- package/src/auth/common/find/markup/InfoStep.tsx +2 -1
- package/src/auth/common/find/styles/result.scss +1 -1
- package/src/auth/find-password/markup/StepResetPassword.tsx +2 -1
- package/src/auth/index.tsx +8 -1
- package/src/auth/login/markup/FormField.tsx +2 -1
- package/src/auth/signup/markup/AccountForm.tsx +2 -1
- package/src/auth/signup/markup/Complete.tsx +2 -1
- package/src/auth/signup/markup/UserInfoForm.tsx +2 -1
- package/src/auth/signup/markup/VerificationForm.tsx +4 -2
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
- package/src/modal/core/components/FooterButtons.tsx +4 -1
- package/src/modal/templates/Dialog.tsx +4 -2
- package/src/modal/types/footer.ts +4 -2
- package/src/page-frame/container/index.scss +14 -8
- package/src/page-frame/desktop/components/header/Container.tsx +22 -0
- package/src/page-frame/desktop/components/header/Section.tsx +20 -0
- package/src/page-frame/desktop/components/header/index.tsx +19 -0
- package/src/page-frame/desktop/components/header/util/Button.tsx +23 -0
- package/src/page-frame/desktop/components/header/util/Container.tsx +18 -0
- package/src/page-frame/desktop/components/header/util/Item.tsx +13 -0
- package/src/page-frame/desktop/components/header/util/Logout.tsx +26 -0
- package/src/page-frame/desktop/components/header/util/setting/Button.tsx +39 -0
- package/src/page-frame/desktop/components/index.tsx +15 -0
- package/src/page-frame/desktop/components/nav/Button.tsx +24 -0
- package/src/page-frame/desktop/components/nav/Container.tsx +70 -0
- package/src/page-frame/desktop/components/nav/Logo.tsx +46 -0
- package/src/page-frame/desktop/components/nav/index.tsx +5 -0
- package/src/page-frame/desktop/components/page/Container.tsx +13 -0
- package/src/page-frame/desktop/components/page/ServiceFrame.tsx +15 -0
- package/src/page-frame/desktop/components/page/ServiceMain.tsx +13 -0
- package/src/page-frame/desktop/components/page/ServiceMainWrapper.tsx +24 -0
- package/src/page-frame/desktop/components/page/index.tsx +14 -0
- package/src/page-frame/desktop/components/popup/Container.tsx +18 -0
- package/src/page-frame/desktop/components/popup/frame/Body.tsx +14 -0
- package/src/page-frame/desktop/components/popup/frame/Container.tsx +16 -0
- package/src/page-frame/desktop/components/popup/frame/Header.tsx +26 -0
- package/src/page-frame/desktop/components/popup/frame/index.tsx +11 -0
- package/src/page-frame/desktop/components/popup/index.tsx +9 -0
- package/src/page-frame/desktop/index.scss +5 -0
- package/src/page-frame/desktop/index.tsx +5 -0
- package/src/page-frame/desktop/styles/page/common.scss +40 -0
- package/src/page-frame/desktop/styles/page/header.scss +49 -0
- package/src/page-frame/desktop/styles/page/nav.scss +128 -0
- package/src/page-frame/desktop/styles/popup/popup.scss +45 -0
- package/src/page-frame/desktop/styles/variables.scss +28 -0
- package/src/page-frame/desktop/types/nav.ts +77 -0
- package/src/page-frame/index.tsx +2 -0
- package/src/page-frame/mobile/index.scss +1 -1
- package/src/page-frame/types/index.ts +69 -0
- package/src/weather/apis/index.ts +4 -0
- package/src/weather/apis/korea/client.ts +93 -0
- package/src/weather/apis/korea/server.ts +253 -0
- package/src/weather/apis/open-weather-map/client.ts +69 -0
- package/src/weather/apis/open-weather-map/server.ts +134 -0
- package/src/weather/components/icon/Address.tsx +13 -0
- package/src/weather/components/icon/Weather.tsx +32 -0
- package/src/weather/components/index.tsx +7 -0
- package/src/weather/components/page-header/Address.tsx +21 -0
- package/src/weather/components/page-header/Alert.tsx +42 -0
- package/src/weather/components/page-header/Container.tsx +14 -0
- package/src/weather/components/page-header/Forecast.tsx +26 -0
- package/src/weather/components/page-header/NextDays.tsx +32 -0
- package/src/weather/components/page-header/Today.tsx +135 -0
- package/src/weather/context/mock.tsx +45 -0
- package/src/weather/data/alert-regions-meta.json +1286 -0
- package/src/weather/data/response.ts +36 -0
- package/src/weather/data/weather-regions-meta.json +9833 -0
- package/src/weather/hooks/index.ts +4 -0
- package/src/weather/hooks/useOpenWeatherMap.ts +42 -0
- package/src/weather/hooks/useWeatherKorea.ts +78 -0
- package/src/weather/hooks/useWeatherKoreaAlert.ts +36 -0
- package/src/weather/index.tsx +11 -0
- package/src/weather/jotai/coordinate.ts +16 -0
- package/src/weather/jotai/farm-idx.ts +5 -0
- package/src/weather/jotai/index.ts +2 -0
- package/src/weather/styles/weather.scss +151 -0
- package/src/weather/types/api.ts +215 -0
- package/src/weather/types/base.ts +50 -0
- package/src/weather/types/index.ts +4 -0
- package/src/weather/types/korea.ts +228 -0
- package/src/weather/types/open-weather-map.ts +164 -0
- package/src/weather/utils/alert.ts +30 -0
- package/src/weather/utils/date-time.ts +65 -0
- package/src/weather/utils/index.ts +4 -0
- package/src/weather/utils/location.ts +161 -0
- package/src/weather/utils/validate.ts +9 -0
- package/src/weather/utils/weather.ts +304 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 기상청 격자 좌표.
|
|
3
|
+
* @property {number} x 경도 격자값
|
|
4
|
+
* @property {number} y 위도 격자값
|
|
5
|
+
*/
|
|
6
|
+
export type KMA_GridCoordinate = {
|
|
7
|
+
/** 경도 격자값 */
|
|
8
|
+
x: number;
|
|
9
|
+
/** 위도 격자값 */
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 기상청 기본 요청 필수 파라미터.
|
|
15
|
+
* @property {string} pageNo 페이지 번호
|
|
16
|
+
* @property {string} numOfRows 페이지당 결과 수
|
|
17
|
+
* @property {string} authKey 인증키
|
|
18
|
+
* @property {string} dataType 응답 타입(JSON/XML)
|
|
19
|
+
*/
|
|
20
|
+
export type KMA_Req_Base = {
|
|
21
|
+
/** 페이지 번호 */
|
|
22
|
+
pageNo: string;
|
|
23
|
+
/** 페이지당 결과 수 */
|
|
24
|
+
numOfRows: string;
|
|
25
|
+
/** 인증 키 */
|
|
26
|
+
authKey: string;
|
|
27
|
+
/** 응답 포맷 */
|
|
28
|
+
dataType: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 기상청 요청/응답 공통 시각 정보.
|
|
33
|
+
* @property {string} base_date 기준 날짜(YYYYMMDD)
|
|
34
|
+
* @property {string} base_time 기준 시각(HH00)
|
|
35
|
+
*/
|
|
36
|
+
export type KMA_Req_BaseMoments = {
|
|
37
|
+
/** 기준 날짜(YYYYMMDD) */
|
|
38
|
+
base_date: string;
|
|
39
|
+
/** 기준 시각(HH00) */
|
|
40
|
+
base_time: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 기상청 격자 좌표 파라미터.
|
|
45
|
+
* @property {number} nx 격자 X
|
|
46
|
+
* @property {number} ny 격자 Y
|
|
47
|
+
*/
|
|
48
|
+
export type KMA_Req_BaseGridCoordinate = {
|
|
49
|
+
/** 격자 X */
|
|
50
|
+
nx: number;
|
|
51
|
+
/** 격자 Y */
|
|
52
|
+
ny: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 기상청 날씨 API 요청 조합.
|
|
57
|
+
*/
|
|
58
|
+
export type KMA_Req_Weather = KMA_Req_Base &
|
|
59
|
+
KMA_Req_BaseMoments &
|
|
60
|
+
KMA_Req_BaseGridCoordinate;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 기상청 API 응답; 예보변수
|
|
64
|
+
* @desc
|
|
65
|
+
* - SKY(하늘상태) 코드 - 1(맑음), 2(구름조금), 3(구름많음), 4(흐림)
|
|
66
|
+
* - PTY(강수형태) 코드 - 0(없음), 1(비), 2(비/눈), 3(눈), 4(소나기)
|
|
67
|
+
* - POP(강수확률) %
|
|
68
|
+
* - PCP(1시간 강수량) 1mm
|
|
69
|
+
* - SNO(1시간 신적설) 1cm
|
|
70
|
+
* - TMP(기온) ℃
|
|
71
|
+
* - TMX(최고기온) ℃
|
|
72
|
+
* - TMN(최저기온) ℃
|
|
73
|
+
* - REH(습도) %
|
|
74
|
+
* - UUU(풍속; 동서바람성분) m/s
|
|
75
|
+
* - VVV(풍속; 남북바람성분) m/s
|
|
76
|
+
* - VEC(풍향) deg
|
|
77
|
+
* - WSD(풍속) m/s
|
|
78
|
+
* - WAV(파고) m
|
|
79
|
+
*/
|
|
80
|
+
export type KMA_Res_WeatherVariablesCategory =
|
|
81
|
+
| "TMP"
|
|
82
|
+
| "TMX"
|
|
83
|
+
| "TMN"
|
|
84
|
+
| "UUU"
|
|
85
|
+
| "VVV"
|
|
86
|
+
| "VEC"
|
|
87
|
+
| "WSD"
|
|
88
|
+
| "SKY"
|
|
89
|
+
| "PTY"
|
|
90
|
+
| "POP"
|
|
91
|
+
| "PCP"
|
|
92
|
+
| "SNO"
|
|
93
|
+
| "REH"
|
|
94
|
+
| "WAV";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 기상청 예보 데이터 기본 구조.
|
|
98
|
+
* @property {string} baseDate 발표 날짜
|
|
99
|
+
* @property {string} baseTime 발표 시각
|
|
100
|
+
* @property {KMA_Res_WeatherVariablesCategory} category 변수 종류
|
|
101
|
+
* @property {number} nx 격자 X
|
|
102
|
+
* @property {number} ny 격자 Y
|
|
103
|
+
*/
|
|
104
|
+
export type KMA_Res_WeatherItemBase = {
|
|
105
|
+
/** 발표 날짜 */
|
|
106
|
+
baseDate: string;
|
|
107
|
+
/** 발표 시각 */
|
|
108
|
+
baseTime: string;
|
|
109
|
+
/** 변수 종류 */
|
|
110
|
+
category: KMA_Res_WeatherVariablesCategory;
|
|
111
|
+
} & KMA_Req_BaseGridCoordinate;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 기상청 예보 데이터.
|
|
115
|
+
* @property {string} fcstDate 예보 날짜
|
|
116
|
+
* @property {string} fcstTime 예보 시각
|
|
117
|
+
* @property {string} fcstValue 예보 값
|
|
118
|
+
*/
|
|
119
|
+
export type KMA_Res_WeatherItem = KMA_Res_WeatherItemBase & {
|
|
120
|
+
/** 예보 날짜 */
|
|
121
|
+
fcstDate: string;
|
|
122
|
+
/** 예보 시각 */
|
|
123
|
+
fcstTime: string;
|
|
124
|
+
/** 예보 값 */
|
|
125
|
+
fcstValue: string;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// /**
|
|
129
|
+
// * 기상청 API 응답; 현재 날씨 데이터
|
|
130
|
+
// */
|
|
131
|
+
// export type KMA_Res_WeatherItemNow = KMA_Res_WeatherItemBase & {
|
|
132
|
+
// obsrValue: string;
|
|
133
|
+
// };
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 기상청 응답 헤더.
|
|
137
|
+
* @property {string} resultCode 응답 코드
|
|
138
|
+
* @property {string} resultMsg 응답 메시지
|
|
139
|
+
*/
|
|
140
|
+
export type KMA_Res_Header = {
|
|
141
|
+
/** 응답 코드 */
|
|
142
|
+
resultCode: string;
|
|
143
|
+
/** 응답 메시지 */
|
|
144
|
+
resultMsg: string;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 기상청 응답 바디.
|
|
149
|
+
* @property {string} dataType 응답 타입
|
|
150
|
+
* @property {{ item: Item[] }} items 데이터 목록
|
|
151
|
+
* @property {number} pageNo 페이지 번호
|
|
152
|
+
* @property {number} numOfRows 페이지당 건수
|
|
153
|
+
* @property {number} totalCount 전체 건수
|
|
154
|
+
*/
|
|
155
|
+
export type KMA_Res_Body<Item> = {
|
|
156
|
+
/** 응답 포맷 */
|
|
157
|
+
dataType: string;
|
|
158
|
+
/** 데이터 목록 */
|
|
159
|
+
items: { item: Item[] };
|
|
160
|
+
/** 페이지 번호 */
|
|
161
|
+
pageNo: number;
|
|
162
|
+
/** 페이지당 건수 */
|
|
163
|
+
numOfRows: number;
|
|
164
|
+
/** 전체 건수 */
|
|
165
|
+
totalCount: number;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 기상청 응답 공통 구조.
|
|
170
|
+
* @property {KMA_Res_Header} header 헤더
|
|
171
|
+
* @property {KMA_Res_Body<Item>} body 바디
|
|
172
|
+
*/
|
|
173
|
+
export type KMA_Res_Base<Item> = {
|
|
174
|
+
response: {
|
|
175
|
+
/** 헤더 */
|
|
176
|
+
header: KMA_Res_Header;
|
|
177
|
+
/** 바디 */
|
|
178
|
+
body: KMA_Res_Body<Item>;
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** * * 기상청 API 응답; 현재 날씨.
|
|
183
|
+
* */
|
|
184
|
+
export type KMA_Res_WeatherNow = KMA_Res_Base<KMA_Res_WeatherItem>;
|
|
185
|
+
|
|
186
|
+
/** * * 기상청 API 응답; 예보.
|
|
187
|
+
* */
|
|
188
|
+
export type KMA_Res_WeatherForecast = KMA_Res_Base<KMA_Res_WeatherItem>;
|
|
189
|
+
|
|
190
|
+
// export type KMA_Res_MetaRegionCodeItem = {
|
|
191
|
+
// warningAreaCode: string;
|
|
192
|
+
// korName: string;
|
|
193
|
+
// };
|
|
194
|
+
// export type KMA_Res_MetaRegionCodes = KMA_Res_Base<KMA_Res_MetaRegionCodeItem>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 기상청 API 응답; 특보 종류
|
|
198
|
+
* @see https://apihub.kma.go.kr/static/html/attach/wrn_table.html
|
|
199
|
+
*/
|
|
200
|
+
export type KMA_Res_AlertType =
|
|
201
|
+
| "강풍"
|
|
202
|
+
| "호우"
|
|
203
|
+
| "한파"
|
|
204
|
+
| "건조"
|
|
205
|
+
| "해일"
|
|
206
|
+
| "지진해일"
|
|
207
|
+
| "풍량"
|
|
208
|
+
| "태풍"
|
|
209
|
+
| "대설"
|
|
210
|
+
| "황사"
|
|
211
|
+
| "폭염";
|
|
212
|
+
/**
|
|
213
|
+
* 기상청 API 응답; 특보 수준
|
|
214
|
+
* @see https://apihub.kma.go.kr/static/html/attach/wrn_table.html
|
|
215
|
+
*/
|
|
216
|
+
export type KMA_Res_AlertLevel = "예비특보" | "주의보" | "경보";
|
|
217
|
+
/**
|
|
218
|
+
* 기상청 API 응답; 특보 명령
|
|
219
|
+
* @see https://apihub.kma.go.kr/static/html/attach/wrn_table.html
|
|
220
|
+
*/
|
|
221
|
+
export type KMA_Res_AlertCommand =
|
|
222
|
+
| "발표"
|
|
223
|
+
| "대치"
|
|
224
|
+
| "해제"
|
|
225
|
+
| "대치해제"
|
|
226
|
+
| "연장"
|
|
227
|
+
| "변경"
|
|
228
|
+
| "변경해제";
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenWeatherMap 위/경도 좌표.
|
|
3
|
+
* @property {number} lat 위도
|
|
4
|
+
* @property {number} lon 경도
|
|
5
|
+
*/
|
|
6
|
+
export type OWM_Res_Coord = {
|
|
7
|
+
lat: number;
|
|
8
|
+
lon: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @see https://openweathermap.org/weather-conditions
|
|
13
|
+
*/
|
|
14
|
+
export type OWM_Res_Condition = {
|
|
15
|
+
/**
|
|
16
|
+
* 날씨 코드
|
|
17
|
+
* @see https://openweathermap.org/weather-conditions
|
|
18
|
+
* @desc
|
|
19
|
+
* - 800: 맑음
|
|
20
|
+
* - 801: 구름조금
|
|
21
|
+
* - 802: 구름많음
|
|
22
|
+
* - 803: 흐림
|
|
23
|
+
* - 804: 흐림
|
|
24
|
+
* - 7XX: 안개, 연무, 황사 등
|
|
25
|
+
* - 6XX: 눈
|
|
26
|
+
* - 5XX: 비
|
|
27
|
+
* - 3XX: 이슬비, 빗방울 등
|
|
28
|
+
* - 2XX: 천둥번개
|
|
29
|
+
*/
|
|
30
|
+
id: number;
|
|
31
|
+
/** * 개황 (영문) */
|
|
32
|
+
main: string;
|
|
33
|
+
/** * 개황 (한글) */
|
|
34
|
+
description: string;
|
|
35
|
+
/**
|
|
36
|
+
* 아이콘 코드
|
|
37
|
+
* @see https://openweathermap.org/weather-conditions
|
|
38
|
+
* @desc
|
|
39
|
+
* - 01d: 맑음 (clear sky)
|
|
40
|
+
* - 02d: 구름조금 (few clouds)
|
|
41
|
+
* - 03d: 구름많음 (scattered clouds)
|
|
42
|
+
* - 04d: 흐림 (broken clouds)
|
|
43
|
+
* - 09d: 소나기 (shower rain)
|
|
44
|
+
* - 10d: 비 (rain)
|
|
45
|
+
* - 11d: 천둥번개 (thunderstorm)
|
|
46
|
+
* - 13d: 눈 (snow)
|
|
47
|
+
* - 50d: 안개 (mist)
|
|
48
|
+
*/
|
|
49
|
+
icon: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 측정값
|
|
54
|
+
* - 온도: 섭씨온도 (°C)
|
|
55
|
+
* - 습도: 백분율 (%)
|
|
56
|
+
* - 기압: hPa
|
|
57
|
+
*/
|
|
58
|
+
export type OWM_Res_Weather_Degrees = {
|
|
59
|
+
/** * 현재 온도 (°C) */
|
|
60
|
+
temp: number;
|
|
61
|
+
/** * 체감 온도 (°C) */
|
|
62
|
+
feels_like: number;
|
|
63
|
+
/** * 최저 온도 (°C) */
|
|
64
|
+
temp_min: number;
|
|
65
|
+
/** * 최고 온도 (°C) */
|
|
66
|
+
temp_max: number;
|
|
67
|
+
/** * 해면기압 (hPa) */
|
|
68
|
+
pressure: number;
|
|
69
|
+
/** * 습도 (%) */
|
|
70
|
+
humidity: number;
|
|
71
|
+
/** * 해면기압 (hPa) */
|
|
72
|
+
sea_level: number;
|
|
73
|
+
/** * 현지기압 (hPa) */
|
|
74
|
+
grnd_level: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 바람 관련 응답.
|
|
79
|
+
* @property {number} speed 풍속(m/s)
|
|
80
|
+
* @property {number} deg 풍향(deg)
|
|
81
|
+
* @property {number} gust 돌풍(m/s)
|
|
82
|
+
*/
|
|
83
|
+
export type OWM_Res_Wind = {
|
|
84
|
+
/** * 풍속 (m/s) */
|
|
85
|
+
speed: number;
|
|
86
|
+
/**
|
|
87
|
+
* 풍향 (deg)
|
|
88
|
+
* - 0: 북쪽
|
|
89
|
+
* - 90: 동쪽
|
|
90
|
+
* - 180: 남쪽
|
|
91
|
+
* - 270: 서쪽
|
|
92
|
+
*/
|
|
93
|
+
deg: number;
|
|
94
|
+
/** * 돌풍 (m/s) */
|
|
95
|
+
gust: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 구름량 정보.
|
|
100
|
+
* @property {number} all 구름량(%)
|
|
101
|
+
*/
|
|
102
|
+
export type OWM_Res_Clouds = {
|
|
103
|
+
/** * 구름량 (0-100, %) */
|
|
104
|
+
all: number;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 일출/일몰 정보.
|
|
109
|
+
* @property {string} country 국가 코드
|
|
110
|
+
* @property {number} sunrise 일출 시각 timestamp
|
|
111
|
+
* @property {number} sunset 일몰 시각 timestamp
|
|
112
|
+
*/
|
|
113
|
+
export type OWM_Res_Sun = {
|
|
114
|
+
country: string; // 국가 코드 "KR";
|
|
115
|
+
sunrise: number; // 일출 timestamp 1748290711;
|
|
116
|
+
sunset: number; // 일몰 timestamp 1748342383;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* OpenWeatherMap API 응답; 현재 날씨
|
|
121
|
+
* @see https://openweathermap.org/current
|
|
122
|
+
*/
|
|
123
|
+
export type OWM_Res_Weather_Now = {
|
|
124
|
+
/** * 위/경도 좌표 */
|
|
125
|
+
coord: OWM_Res_Coord;
|
|
126
|
+
/** * 개황 정보 */
|
|
127
|
+
weather: OWM_Res_Condition[];
|
|
128
|
+
/** 내부 지표값 */
|
|
129
|
+
base: string;
|
|
130
|
+
/** * 측정 정보 */
|
|
131
|
+
main: OWM_Res_Weather_Degrees;
|
|
132
|
+
/** * 시정거리 (m) */
|
|
133
|
+
visibility: number;
|
|
134
|
+
/** * 바람 정보 */
|
|
135
|
+
wind: OWM_Res_Wind;
|
|
136
|
+
/** * 구름량 정보 */
|
|
137
|
+
clouds: OWM_Res_Clouds;
|
|
138
|
+
/** * 데이터 연산 시간 timestamp (UTC, sec) */
|
|
139
|
+
dt: number;
|
|
140
|
+
/** * 일출/일몰 정보 */
|
|
141
|
+
sys: OWM_Res_Sun;
|
|
142
|
+
/**
|
|
143
|
+
* UTC로부터 현지 시간 보정 (sec)
|
|
144
|
+
* - 한국 : 9시간 (3600 * 9 = 32400)
|
|
145
|
+
*/
|
|
146
|
+
timezone: number;
|
|
147
|
+
/**
|
|
148
|
+
* 도시 ID
|
|
149
|
+
* @deprecated
|
|
150
|
+
*/
|
|
151
|
+
/** 도시 ID */
|
|
152
|
+
id: number;
|
|
153
|
+
/**
|
|
154
|
+
* 도시 이름
|
|
155
|
+
* @deprecated
|
|
156
|
+
*/
|
|
157
|
+
/** 도시 이름 */
|
|
158
|
+
name: number;
|
|
159
|
+
/**
|
|
160
|
+
* API 응답 정보
|
|
161
|
+
*/
|
|
162
|
+
/** API 응답 코드 */
|
|
163
|
+
cod: number;
|
|
164
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 특보 도구; 지역 ID 추출
|
|
3
|
+
* @param {Array} params
|
|
4
|
+
* @param {Array<{ lat:number; lon:number; regId:string; }>} params.regions - 지역 목록
|
|
5
|
+
* @param {[number, number]} params.coordinate - [위도, 경도]
|
|
6
|
+
*/
|
|
7
|
+
export const getRegId = ({
|
|
8
|
+
regions,
|
|
9
|
+
coordinate,
|
|
10
|
+
}: {
|
|
11
|
+
regions: Array<{
|
|
12
|
+
[dataKey: string]: unknown;
|
|
13
|
+
lat: number;
|
|
14
|
+
lon: number;
|
|
15
|
+
regId: string;
|
|
16
|
+
}>;
|
|
17
|
+
coordinate: [number, number];
|
|
18
|
+
}): string | null => {
|
|
19
|
+
const [lat, lng] = coordinate;
|
|
20
|
+
let bestId: string | null = null;
|
|
21
|
+
let min = Infinity;
|
|
22
|
+
for (const z of regions) {
|
|
23
|
+
const d = Math.hypot(lat - z.lat, lng - z.lon);
|
|
24
|
+
if (d < min) {
|
|
25
|
+
min = d;
|
|
26
|
+
bestId = z.regId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return bestId;
|
|
30
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { KMA_Req_BaseMoments, WeatherUtilDateTimeMoments } from "../types";
|
|
2
|
+
import { dateFormat } from "@uniai-fe/util-functions";
|
|
3
|
+
|
|
4
|
+
export const getWeatherBaseMoments = (
|
|
5
|
+
category: "now" | "forecast",
|
|
6
|
+
): KMA_Req_BaseMoments => {
|
|
7
|
+
const FORECAST_HOURS = [2, 5, 8, 11, 14, 17, 20, 23]; // 발표 시각
|
|
8
|
+
|
|
9
|
+
const now = new Date();
|
|
10
|
+
|
|
11
|
+
let base_date = "";
|
|
12
|
+
let base_time = "";
|
|
13
|
+
|
|
14
|
+
if (category === "now") {
|
|
15
|
+
const base = new Date(now.getTime() - 10 * 60 * 1000); // 10분 보정
|
|
16
|
+
base_date = dateFormat(base).replace(/-/g, "");
|
|
17
|
+
base_time = `${base.getHours().toString().padStart(2, "0")}00`;
|
|
18
|
+
} else {
|
|
19
|
+
const hour = now.getHours();
|
|
20
|
+
const base = new Date(now);
|
|
21
|
+
let base_hour: number = hour;
|
|
22
|
+
if (hour < 2) {
|
|
23
|
+
base.setDate(base.getDate() - 1);
|
|
24
|
+
base_hour = 23;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 현재 시각 이전 중 가장 가까운 발표 시각
|
|
28
|
+
const validHour = FORECAST_HOURS.reduce(
|
|
29
|
+
(prev, curr) => (curr <= base_hour ? curr : prev),
|
|
30
|
+
FORECAST_HOURS[0],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
base_date = dateFormat(base).replace(/-/g, "");
|
|
34
|
+
base_time = `${validHour.toString().padStart(2, "0")}00`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { base_date, base_time };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 날씨 도구; 현재 날씨/예보 시각
|
|
42
|
+
* @util
|
|
43
|
+
* @return {WeatherUtilDateTimeMoments}
|
|
44
|
+
* @desc
|
|
45
|
+
* - now: Date 객체
|
|
46
|
+
* - today: YYYYMMDD 형식의 문자열
|
|
47
|
+
* - tomorrow: YYYYMMDD 형식의 문자열
|
|
48
|
+
* - dayAfterTomorrow: YYYYMMDD 형식의 문자열
|
|
49
|
+
*/
|
|
50
|
+
export const getMoments = (): WeatherUtilDateTimeMoments => {
|
|
51
|
+
const now = new Date();
|
|
52
|
+
const today = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
53
|
+
|
|
54
|
+
const tomorrow = new Date(now.getTime() + 86400000)
|
|
55
|
+
.toISOString()
|
|
56
|
+
.slice(0, 10)
|
|
57
|
+
.replace(/-/g, "");
|
|
58
|
+
|
|
59
|
+
const dayAfterTomorrow = new Date(now.getTime() + 2 * 86400000)
|
|
60
|
+
.toISOString()
|
|
61
|
+
.slice(0, 10)
|
|
62
|
+
.replace(/-/g, "");
|
|
63
|
+
|
|
64
|
+
return { now, today, tomorrow, dayAfterTomorrow };
|
|
65
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GeoCoordinate,
|
|
3
|
+
KMA_GridCoordinate,
|
|
4
|
+
KMA_Req_BaseGridCoordinate,
|
|
5
|
+
WeatherCoordinate,
|
|
6
|
+
} from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 날씨 도구; 접속위치 추출
|
|
10
|
+
* @util
|
|
11
|
+
* @return {GeoCoordinate}}
|
|
12
|
+
*/
|
|
13
|
+
export const userLocation = async (): Promise<GeoCoordinate> => {
|
|
14
|
+
if (typeof window === "undefined" || !window.navigator.geolocation) {
|
|
15
|
+
// console.log("[userLocation] window 없음");
|
|
16
|
+
return { latitude: null, longitude: null };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const position = await new Promise<GeolocationPosition>(
|
|
21
|
+
(resolve, reject) => {
|
|
22
|
+
window.navigator.geolocation.getCurrentPosition(resolve, reject);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
// console.log("[userLocation] 위치 정보:", position.coords);
|
|
26
|
+
const res = {
|
|
27
|
+
latitude: position.coords.latitude,
|
|
28
|
+
longitude: position.coords.longitude,
|
|
29
|
+
};
|
|
30
|
+
return res;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("[userLocation] 위치 정보 에러", error);
|
|
33
|
+
return { latitude: null, longitude: null };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 날씨 도구; 위경도 -> 격자 좌표 변환
|
|
39
|
+
* @util
|
|
40
|
+
* @param {GeoCoordinate} coordinate - 위경도 좌표
|
|
41
|
+
* @param {number} coordinate.latitude - 위도
|
|
42
|
+
* @param {number} coordinate.longitude - 경도
|
|
43
|
+
* @return {KMA_GridCoordinate} 격자 좌표
|
|
44
|
+
* @desc
|
|
45
|
+
* - 위경도 좌표를 기상청 격자 좌표로 변환합니다.
|
|
46
|
+
* - 출처: 기상청
|
|
47
|
+
* @see https://apihub.kma.go.kr/
|
|
48
|
+
* @see https://apihub.kma.go.kr/getAttachFile.do?fileName=%EB%8B%A8%EA%B8%B0%EC%98%88%EB%B3%B4%20%EC%A1%B0%ED%9A%8C%EC%84%9C%EB%B9%84%EC%8A%A4_API%ED%99%9C%EC%9A%A9%EA%B0%80%EC%9D%B4%EB%93%9C_241128.docx
|
|
49
|
+
*/
|
|
50
|
+
export function convertCoordinateToGrid({
|
|
51
|
+
latitude,
|
|
52
|
+
longitude,
|
|
53
|
+
}: GeoCoordinate): KMA_GridCoordinate {
|
|
54
|
+
const res: KMA_GridCoordinate = { x: 0, y: 0 };
|
|
55
|
+
|
|
56
|
+
if (latitude === null || longitude === null) return res;
|
|
57
|
+
|
|
58
|
+
const RE = 6371.00877; // Earth radius (km)
|
|
59
|
+
const GRID = 5.0; // Grid spacing (km)
|
|
60
|
+
const SLAT1 = 30.0; // Projection latitude 1 (degree)
|
|
61
|
+
const SLAT2 = 60.0; // Projection latitude 2 (degree)
|
|
62
|
+
const ORIGIN_LONGITUDE = 126.0; // Origin longitude (degree)
|
|
63
|
+
const ORIGIN_LATITUDE = 38.0; // Origin latitude (degree)
|
|
64
|
+
const XO = 43; // Origin X coordinate (GRID)
|
|
65
|
+
const YO = 136; // Origin Y coordinate (GRID)
|
|
66
|
+
|
|
67
|
+
const RADIAN = Math.PI / 180.0;
|
|
68
|
+
const re = RE / GRID;
|
|
69
|
+
const slat1 = SLAT1 * RADIAN;
|
|
70
|
+
const slat2 = SLAT2 * RADIAN;
|
|
71
|
+
const origin_longitude = ORIGIN_LONGITUDE * RADIAN;
|
|
72
|
+
const origin_latitude = ORIGIN_LATITUDE * RADIAN;
|
|
73
|
+
|
|
74
|
+
const sn =
|
|
75
|
+
Math.log(Math.cos(slat1) / Math.cos(slat2)) /
|
|
76
|
+
Math.log(
|
|
77
|
+
Math.tan(Math.PI * 0.25 + slat2 * 0.5) /
|
|
78
|
+
Math.tan(Math.PI * 0.25 + slat1 * 0.5),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
|
|
82
|
+
const sfPow = (Math.pow(sf, sn) * Math.cos(slat1)) / sn;
|
|
83
|
+
|
|
84
|
+
const ro =
|
|
85
|
+
(re * sfPow) /
|
|
86
|
+
Math.pow(Math.tan(Math.PI * 0.25 + origin_latitude * 0.5), sn);
|
|
87
|
+
|
|
88
|
+
const ra =
|
|
89
|
+
(re * sfPow) /
|
|
90
|
+
Math.pow(Math.tan(Math.PI * 0.25 + latitude * RADIAN * 0.5), sn);
|
|
91
|
+
let theta = longitude * RADIAN - origin_longitude;
|
|
92
|
+
if (theta > Math.PI) theta -= 2.0 * Math.PI;
|
|
93
|
+
if (theta < -Math.PI) theta += 2.0 * Math.PI;
|
|
94
|
+
theta *= sn;
|
|
95
|
+
|
|
96
|
+
res.x = Math.floor(ra * Math.sin(theta) + XO + 0.5);
|
|
97
|
+
res.y = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
|
|
98
|
+
|
|
99
|
+
// console.log("[WEATHER] 위/경도 -> 기상청 격자값", res);
|
|
100
|
+
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 날씨 도구; 위도/경도 -> 기상청 격자 좌표 변환
|
|
106
|
+
* @util
|
|
107
|
+
* @param {WeatherCoordinate} [coordinate] - 사용자 위치 이외의 특정 위도/경도 좌표
|
|
108
|
+
* @param {number} coordinate.lat - 위도
|
|
109
|
+
* @param {number} coordinate.lng - 경도
|
|
110
|
+
* @return {KMA_Req_BaseGridCoordinate} 기상청 격자 좌표
|
|
111
|
+
*/
|
|
112
|
+
export async function getWeatherGridLocation(
|
|
113
|
+
coordinate?: WeatherCoordinate,
|
|
114
|
+
): Promise<KMA_Req_BaseGridCoordinate> {
|
|
115
|
+
const res: KMA_Req_BaseGridCoordinate = { nx: 0, ny: 0 };
|
|
116
|
+
|
|
117
|
+
if (typeof window === "undefined") return res;
|
|
118
|
+
|
|
119
|
+
const geo: GeoCoordinate = {
|
|
120
|
+
latitude: coordinate?.lat ?? null,
|
|
121
|
+
longitude: coordinate?.lng ?? null,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// console.log("[getWeatherLocation] 특정 좌표지정 확인", coordinate);
|
|
125
|
+
|
|
126
|
+
// 특정 위경도 좌표를 받지 못하는 경우, 현재 사용자 위치로 지정
|
|
127
|
+
if (
|
|
128
|
+
typeof window !== "undefined" &&
|
|
129
|
+
(typeof coordinate === "undefined" ||
|
|
130
|
+
geo.latitude === null ||
|
|
131
|
+
geo.longitude === null)
|
|
132
|
+
) {
|
|
133
|
+
const { latitude, longitude } = await userLocation();
|
|
134
|
+
|
|
135
|
+
if (latitude === null || longitude === null) {
|
|
136
|
+
// console.log("[getWeatherLocation] window.navigator 실패", geo);
|
|
137
|
+
return res;
|
|
138
|
+
}
|
|
139
|
+
geo.latitude = latitude;
|
|
140
|
+
geo.longitude = longitude;
|
|
141
|
+
// console.log("[getWeatherLocation] 사용자 위치", geo);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 위경도 좌표를 기상청 격자좌표로 변환
|
|
145
|
+
const grid = convertCoordinateToGrid(geo);
|
|
146
|
+
|
|
147
|
+
if (grid.x === 0 || grid.y === 0) {
|
|
148
|
+
// console.error("[getWeatherLocation] 위치 정보를 가져오는데 실패했습니다.", {
|
|
149
|
+
// latitude: geo.latitude,
|
|
150
|
+
// longitude: geo.longitude,
|
|
151
|
+
// });
|
|
152
|
+
return res;
|
|
153
|
+
}
|
|
154
|
+
// console.log("[getWeatherLocation] 기상청 격자좌표", grid);
|
|
155
|
+
|
|
156
|
+
res.nx = grid.x;
|
|
157
|
+
res.ny = grid.y;
|
|
158
|
+
|
|
159
|
+
// console.log("[getWeatherLocation] result", res);
|
|
160
|
+
return res;
|
|
161
|
+
}
|