@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.
Files changed (93) hide show
  1. package/dist/styles.css +139 -10
  2. package/package.json +18 -11
  3. package/src/auth/common/complete/Template.tsx +3 -0
  4. package/src/auth/common/complete/types.ts +4 -1
  5. package/src/auth/common/find/markup/CodeStep.tsx +2 -1
  6. package/src/auth/common/find/markup/InfoStep.tsx +2 -1
  7. package/src/auth/common/find/styles/result.scss +1 -1
  8. package/src/auth/find-password/markup/StepResetPassword.tsx +2 -1
  9. package/src/auth/index.tsx +8 -1
  10. package/src/auth/login/markup/FormField.tsx +2 -1
  11. package/src/auth/signup/markup/AccountForm.tsx +2 -1
  12. package/src/auth/signup/markup/Complete.tsx +2 -1
  13. package/src/auth/signup/markup/UserInfoForm.tsx +2 -1
  14. package/src/auth/signup/markup/VerificationForm.tsx +4 -2
  15. package/src/index.scss +1 -0
  16. package/src/index.tsx +1 -0
  17. package/src/modal/core/components/FooterButtons.tsx +4 -1
  18. package/src/modal/templates/Dialog.tsx +4 -2
  19. package/src/modal/types/footer.ts +4 -2
  20. package/src/page-frame/container/index.scss +14 -8
  21. package/src/page-frame/desktop/components/header/Container.tsx +22 -0
  22. package/src/page-frame/desktop/components/header/Section.tsx +20 -0
  23. package/src/page-frame/desktop/components/header/index.tsx +19 -0
  24. package/src/page-frame/desktop/components/header/util/Button.tsx +23 -0
  25. package/src/page-frame/desktop/components/header/util/Container.tsx +18 -0
  26. package/src/page-frame/desktop/components/header/util/Item.tsx +13 -0
  27. package/src/page-frame/desktop/components/header/util/Logout.tsx +26 -0
  28. package/src/page-frame/desktop/components/header/util/setting/Button.tsx +39 -0
  29. package/src/page-frame/desktop/components/index.tsx +15 -0
  30. package/src/page-frame/desktop/components/nav/Button.tsx +24 -0
  31. package/src/page-frame/desktop/components/nav/Container.tsx +70 -0
  32. package/src/page-frame/desktop/components/nav/Logo.tsx +46 -0
  33. package/src/page-frame/desktop/components/nav/index.tsx +5 -0
  34. package/src/page-frame/desktop/components/page/Container.tsx +13 -0
  35. package/src/page-frame/desktop/components/page/ServiceFrame.tsx +15 -0
  36. package/src/page-frame/desktop/components/page/ServiceMain.tsx +13 -0
  37. package/src/page-frame/desktop/components/page/ServiceMainWrapper.tsx +24 -0
  38. package/src/page-frame/desktop/components/page/index.tsx +14 -0
  39. package/src/page-frame/desktop/components/popup/Container.tsx +18 -0
  40. package/src/page-frame/desktop/components/popup/frame/Body.tsx +14 -0
  41. package/src/page-frame/desktop/components/popup/frame/Container.tsx +16 -0
  42. package/src/page-frame/desktop/components/popup/frame/Header.tsx +26 -0
  43. package/src/page-frame/desktop/components/popup/frame/index.tsx +11 -0
  44. package/src/page-frame/desktop/components/popup/index.tsx +9 -0
  45. package/src/page-frame/desktop/index.scss +5 -0
  46. package/src/page-frame/desktop/index.tsx +5 -0
  47. package/src/page-frame/desktop/styles/page/common.scss +40 -0
  48. package/src/page-frame/desktop/styles/page/header.scss +49 -0
  49. package/src/page-frame/desktop/styles/page/nav.scss +128 -0
  50. package/src/page-frame/desktop/styles/popup/popup.scss +45 -0
  51. package/src/page-frame/desktop/styles/variables.scss +28 -0
  52. package/src/page-frame/desktop/types/nav.ts +77 -0
  53. package/src/page-frame/index.tsx +2 -0
  54. package/src/page-frame/mobile/index.scss +1 -1
  55. package/src/page-frame/types/index.ts +69 -0
  56. package/src/weather/apis/index.ts +4 -0
  57. package/src/weather/apis/korea/client.ts +93 -0
  58. package/src/weather/apis/korea/server.ts +253 -0
  59. package/src/weather/apis/open-weather-map/client.ts +69 -0
  60. package/src/weather/apis/open-weather-map/server.ts +134 -0
  61. package/src/weather/components/icon/Address.tsx +13 -0
  62. package/src/weather/components/icon/Weather.tsx +32 -0
  63. package/src/weather/components/index.tsx +7 -0
  64. package/src/weather/components/page-header/Address.tsx +21 -0
  65. package/src/weather/components/page-header/Alert.tsx +42 -0
  66. package/src/weather/components/page-header/Container.tsx +14 -0
  67. package/src/weather/components/page-header/Forecast.tsx +26 -0
  68. package/src/weather/components/page-header/NextDays.tsx +32 -0
  69. package/src/weather/components/page-header/Today.tsx +135 -0
  70. package/src/weather/context/mock.tsx +45 -0
  71. package/src/weather/data/alert-regions-meta.json +1286 -0
  72. package/src/weather/data/response.ts +36 -0
  73. package/src/weather/data/weather-regions-meta.json +9833 -0
  74. package/src/weather/hooks/index.ts +4 -0
  75. package/src/weather/hooks/useOpenWeatherMap.ts +42 -0
  76. package/src/weather/hooks/useWeatherKorea.ts +78 -0
  77. package/src/weather/hooks/useWeatherKoreaAlert.ts +36 -0
  78. package/src/weather/index.tsx +11 -0
  79. package/src/weather/jotai/coordinate.ts +16 -0
  80. package/src/weather/jotai/farm-idx.ts +5 -0
  81. package/src/weather/jotai/index.ts +2 -0
  82. package/src/weather/styles/weather.scss +151 -0
  83. package/src/weather/types/api.ts +215 -0
  84. package/src/weather/types/base.ts +50 -0
  85. package/src/weather/types/index.ts +4 -0
  86. package/src/weather/types/korea.ts +228 -0
  87. package/src/weather/types/open-weather-map.ts +164 -0
  88. package/src/weather/utils/alert.ts +30 -0
  89. package/src/weather/utils/date-time.ts +65 -0
  90. package/src/weather/utils/index.ts +4 -0
  91. package/src/weather/utils/location.ts +161 -0
  92. package/src/weather/utils/validate.ts +9 -0
  93. 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,4 @@
1
+ export * from "./location";
2
+ export * from "./date-time";
3
+ export * from "./weather";
4
+ export * from "./alert";
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ import type { KMA_Req_BaseGridCoordinate } from "../types";
2
+
3
+ export const isValidGridCoordinate = ({
4
+ nx,
5
+ ny,
6
+ }: KMA_Req_BaseGridCoordinate) => {
7
+ if (isNaN(Number(nx)) || isNaN(Number(ny))) return false;
8
+ return nx > 0 && ny > 0;
9
+ };