@uniai-fe/uds-templates 0.5.29 → 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.
Files changed (54) hide show
  1. package/README.md +3 -6
  2. package/package.json +1 -1
  3. package/src/weather/_legacy/apis/index.ts +4 -0
  4. package/src/weather/_legacy/data/response.ts +36 -0
  5. package/src/weather/_legacy/hooks/index.ts +5 -0
  6. package/src/weather/{hooks → _legacy/hooks}/useOpenWeatherMap.ts +1 -1
  7. package/src/weather/{hooks → _legacy/hooks}/useWeatherKorea.ts +4 -7
  8. package/src/weather/{hooks → _legacy/hooks}/useWeatherKoreaAlert.ts +2 -4
  9. package/src/weather/_legacy/types/api.ts +221 -0
  10. package/src/weather/_legacy/types/base.ts +70 -0
  11. package/src/weather/_legacy/types/index.ts +4 -0
  12. package/src/weather/_legacy/utils/index.ts +5 -0
  13. package/src/weather/_legacy/utils/locale.ts +28 -0
  14. package/src/weather/_legacy/utils/location.ts +139 -0
  15. package/src/weather/_legacy/utils/weather.ts +460 -0
  16. package/src/weather/apis/client.ts +339 -0
  17. package/src/weather/apis/index.ts +2 -4
  18. package/src/weather/apis/server.ts +264 -0
  19. package/src/weather/components/icon/Address.tsx +7 -0
  20. package/src/weather/components/icon/Weather.tsx +7 -6
  21. package/src/weather/components/page-header/Address.tsx +14 -0
  22. package/src/weather/components/page-header/Alert.tsx +17 -13
  23. package/src/weather/components/page-header/Container.tsx +12 -19
  24. package/src/weather/components/page-header/Forecast.tsx +21 -28
  25. package/src/weather/components/page-header/NextDays.tsx +10 -0
  26. package/src/weather/components/page-header/Today.tsx +86 -158
  27. package/src/weather/components/page-header/index.ts +5 -0
  28. package/src/weather/data/response.ts +4 -23
  29. package/src/weather/hooks/index.ts +3 -3
  30. package/src/weather/hooks/useWeather.ts +52 -0
  31. package/src/weather/hooks/useWeatherAlert.ts +35 -0
  32. package/src/weather/index.tsx +2 -2
  33. package/src/weather/jotai/coordinate.ts +4 -0
  34. package/src/weather/jotai/farm-idx.ts +4 -0
  35. package/src/weather/types/api.ts +393 -114
  36. package/src/weather/types/base.ts +15 -32
  37. package/src/weather/types/index.ts +0 -3
  38. package/src/weather/types/page-header.ts +118 -68
  39. package/src/weather/utils/index.ts +6 -4
  40. package/src/weather/utils/locale.ts +7 -69
  41. package/src/weather/utils/location.ts +6 -141
  42. package/src/weather/utils/weather.ts +53 -456
  43. package/src/weather/data/alert-regions-meta.json +0 -1286
  44. package/src/weather/data/weather-regions-meta.json +0 -9833
  45. package/src/weather/types/provider.ts +0 -34
  46. package/src/weather/utils/alert.ts +0 -30
  47. /package/src/weather/{apis → _legacy/apis}/korea/client.ts +0 -0
  48. /package/src/weather/{apis → _legacy/apis}/korea/server.ts +0 -0
  49. /package/src/weather/{apis → _legacy/apis}/open-weather-map/client.ts +0 -0
  50. /package/src/weather/{apis → _legacy/apis}/open-weather-map/server.ts +0 -0
  51. /package/src/weather/{types → _legacy/types}/korea.ts +0 -0
  52. /package/src/weather/{types → _legacy/types}/open-weather-map.ts +0 -0
  53. /package/src/weather/{utils → _legacy/utils}/date-time.ts +0 -0
  54. /package/src/weather/{utils → _legacy/utils}/validate.ts +0 -0
package/README.md CHANGED
@@ -57,15 +57,12 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
57
57
  - `WeatherPageHeaderForecast`
58
58
  - `WeatherPageHeaderAlert`
59
59
  - `WeatherPageHeaderNextDays`
60
+ - `useWeather`
61
+ - `useWeatherAlert`
60
62
  - `weatherCoordinate`
61
- - `useWeatherKorea`
62
- - `useOpenWeatherMap`
63
- - `resolveWeatherProvider`
64
- - `WEATHER_PROVIDER_CAPABILITIES`
63
+ - `weatherFarmIdx`
65
64
  - `WeatherPageHeaderContainerProps`
66
65
  - `WeatherPageHeaderTexts`
67
- - `WeatherProviderKey`
68
- - `WeatherProviderCapability`
69
66
  - `/service-inquiry`
70
67
  - `ServiceInquiry.Form`
71
68
  - `ServiceInquiry.OpenButton`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-templates",
3
- "version": "0.5.29",
3
+ "version": "0.6.0",
4
4
  "description": "UNIAI Design System; UI Templates Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,4 @@
1
+ export * from "./korea/client";
2
+ export * from "./korea/server";
3
+ export * from "./open-weather-map/client";
4
+ export * from "./open-weather-map/server";
@@ -0,0 +1,36 @@
1
+ import type { API_Res_WeatherKoreaBase } from "../types";
2
+
3
+ const API_RES_RAW = {
4
+ response: {
5
+ header: {
6
+ resultCode: "",
7
+ resultMsg: "",
8
+ },
9
+ body: {
10
+ dataType: "",
11
+ items: {
12
+ item: [],
13
+ },
14
+ pageNo: 0,
15
+ numOfRows: 0,
16
+ totalCount: 0,
17
+ },
18
+ },
19
+ };
20
+
21
+ const API_RES_BASE: API_Res_WeatherKoreaBase = {
22
+ sky: null,
23
+ drop: null,
24
+ rainAmount: null,
25
+ snowAmount: null,
26
+ windSpeed: null,
27
+ condition: null,
28
+ conditionName: null,
29
+ };
30
+
31
+ const WEATHER_KOREA_RESPONSE = {
32
+ API_RES_RAW,
33
+ API_RES_BASE,
34
+ };
35
+
36
+ export default WEATHER_KOREA_RESPONSE;
@@ -0,0 +1,5 @@
1
+ import useOpenWeatherMap from "./useOpenWeatherMap";
2
+ import useWeatherKorea from "./useWeatherKorea";
3
+ import useWeatherKoreaAlert from "./useWeatherKoreaAlert";
4
+
5
+ export { useOpenWeatherMap, useWeatherKorea, useWeatherKoreaAlert };
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import { useAtomValue } from "jotai";
4
- import { weatherCoordinate } from "../jotai";
5
4
  import {
6
5
  useQueryWeatherOpenWeatherMapForecast,
7
6
  useQueryWeatherOpenWeatherMapNow,
8
7
  } from "../apis/open-weather-map/client";
8
+ import { weatherCoordinate } from "../../jotai";
9
9
  import type {
10
10
  WeatherApiLocaleOptions,
11
11
  WeatherOpenWeatherMapParams,
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect } from "react";
3
+ import { useEffect, useState } from "react";
4
4
  import { useAtomValue } from "jotai";
5
5
  import {
6
6
  useQueryWeatherKoreaForecast,
7
7
  useQueryWeatherKoreaNow,
8
- } from "../apis/korea/client"; // 클라이언트 전용 API만 import해 Storybook 번들에서 server 코드를 배제한다.
8
+ } from "../apis/korea/client";
9
+ import { weatherCoordinate } from "../../jotai/coordinate";
9
10
  import type { API_Req_WeatherKorea } from "../types";
10
- import { weatherCoordinate } from "../jotai/coordinate";
11
11
  import { getWeatherBaseMoments } from "../utils/date-time";
12
12
  import { getWeatherGridLocation } from "../utils/location";
13
13
 
@@ -35,10 +35,7 @@ export default function useWeatherKorea({
35
35
  coordinate.lat !== null ||
36
36
  coordinate.lng !== null;
37
37
 
38
- if (!hasInjectedCoordinate) {
39
- // 변경 설명: 초기 mount에서 coordinate 주입 전에는 geolocation/web API 준비를 시도하지 않는다.
40
- return;
41
- }
38
+ if (!hasInjectedCoordinate) return;
42
39
 
43
40
  const nowMoments = getWeatherBaseMoments("now");
44
41
  const forecastMoments = getWeatherBaseMoments("forecast");
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { useMemo } from "react";
4
4
  import { useAtomValue } from "jotai";
5
- import { useQueryWeatherKoreaAlert } from "../apis/korea/client"; // server 번들을 피하기 위해 client 모듈에서 직접 import한다.
6
- import { weatherFarmIdx } from "../jotai/farm-idx";
5
+ import { useQueryWeatherKoreaAlert } from "../apis/korea/client";
6
+ import { weatherFarmIdx } from "../../jotai/farm-idx";
7
7
 
8
8
  type UseWeatherKoreaAlertOptions = {
9
9
  enabled?: boolean;
@@ -13,8 +13,6 @@ export default function useWeatherKoreaAlert({
13
13
  enabled = true,
14
14
  }: UseWeatherKoreaAlertOptions = {}) {
15
15
  const farm_idx = useAtomValue(weatherFarmIdx);
16
-
17
- // 특보 조회 식별자는 템플릿이 보정하지 않고, 서비스가 주입한 값만 그대로 사용한다.
18
16
  const { data, isFetching } = useQueryWeatherKoreaAlert(
19
17
  {
20
18
  farm_idx,
@@ -0,0 +1,221 @@
1
+ import type {
2
+ KMA_Res_WeatherNow,
3
+ KMA_Res_WeatherForecast,
4
+ KMA_Res_Base,
5
+ KMA_Req_BaseGridCoordinate,
6
+ KMA_Req_BaseMoments,
7
+ KMA_Res_AlertType,
8
+ KMA_Res_AlertLevel,
9
+ KMA_Res_AlertCommand,
10
+ KMA_Res_WeatherItem,
11
+ } from "./korea";
12
+
13
+ export type API_Req_WeatherKoreaGrid = KMA_Req_BaseGridCoordinate;
14
+ export type API_Req_WeatherKoreaMoments = KMA_Req_BaseMoments;
15
+
16
+ /**
17
+ * 기상청 API; 날씨 요청.
18
+ */
19
+ export type API_Req_WeatherKorea = API_Req_WeatherKoreaGrid &
20
+ Partial<API_Req_WeatherKoreaMoments>;
21
+
22
+ /**
23
+ * 기상청 API; 특보 요청 파라미터
24
+ * @property {number | null} farm_idx 농장 식별자
25
+ */
26
+ export type API_Req_WeatherKoreaAlert = { farm_idx: number | null };
27
+
28
+ /**
29
+ * 기상청 API; 특보 응답 타입 - 지역별 특보정보
30
+ */
31
+ export type API_Res_WeatherKoreaAlertEach = {
32
+ /**
33
+ * 상위 지역 코드
34
+ * @see https://apihub.kma.go.kr/api/typ01/url/wrn_reg.php?tmfc=0&authKey=njld-D40Rb25Xfg-NAW9hA
35
+ */
36
+ upper_region_code: string;
37
+ /**
38
+ * 상위 지역 이름
39
+ * @see https://apihub.kma.go.kr/api/typ01/url/wrn_reg.php?tmfc=0&authKey=njld-D40Rb25Xfg-NAW9hA
40
+ */
41
+ upper_region_name: string;
42
+ /**
43
+ * 특보 지역 코드
44
+ * @see https://apihub.kma.go.kr/api/typ01/url/wrn_reg.php?tmfc=0&authKey=njld-D40Rb25Xfg-NAW9hA
45
+ */
46
+ alert_region_code: string;
47
+ /**
48
+ * 특보 지역 이름
49
+ * @see https://apihub.kma.go.kr/api/typ01/url/wrn_reg.php?tmfc=0&authKey=njld-D40Rb25Xfg-NAW9hA
50
+ */
51
+ alert_region_name: string;
52
+ /**
53
+ * 특보 발표시간
54
+ * - yyyymmddhhmm
55
+ */
56
+ announcement_time: string;
57
+ /**
58
+ * 특보 발효시간
59
+ * - yyyymmddhhmm
60
+ */
61
+ effective_time: string;
62
+ /** * 특보 종류 (한글) */
63
+ alert_type: KMA_Res_AlertType;
64
+ /**
65
+ * 특보 수준 (한글)
66
+ * - 경보, 주의, 예비
67
+ */
68
+ alert_level: KMA_Res_AlertLevel;
69
+ /**
70
+ * 특보 명령 (한글)
71
+ * - 발표, 변경, 해제
72
+ */
73
+ alert_command: KMA_Res_AlertCommand;
74
+ /**
75
+ * 특보 해제 예고시점 (한글)
76
+ * - dd일 오전/오후/밤(HH시 ~ HH시)
77
+ */
78
+ cancel_notice_time: string;
79
+ };
80
+ /**
81
+ * 기상청 API; 특보 응답 타입
82
+ */
83
+ export type API_Res_WeatherKoreaAlert = {
84
+ /** * 특보 발표 시간 */
85
+ api_announcement_time: string | null;
86
+ /** * 총 특보 개수 */
87
+ total_count: number;
88
+ /** * 지역별 특보 */
89
+ alerts: API_Res_WeatherKoreaAlertEach[];
90
+ };
91
+
92
+ /**
93
+ * 기상청 API; 기초 정보
94
+ * @property {string} condition - 개황 정보
95
+ */
96
+ export type API_Res_WeatherKoreaBase = {
97
+ /**
98
+ * 하늘상태 코드
99
+ * - 1(맑음)
100
+ * - 2(구름조금)
101
+ * - 3(구름많음)
102
+ * - 4(흐림)
103
+ */
104
+ sky: string | null;
105
+ /**
106
+ * 강수형태 코드
107
+ * - 0(없음)
108
+ * - 1(비)
109
+ * - 2(비/눈)
110
+ * - 3(눈)
111
+ * - 4(소나기)
112
+ */
113
+ drop: string | null;
114
+ /**
115
+ * 강수량 코드
116
+ * - 1(약한 비); 3mm/h 미만
117
+ * - 2(보통 비); 3mm/h 이상 15mm/h 미만
118
+ * - 3(강한 비); 15mm/h 이상
119
+ */
120
+ rainAmount: string | null;
121
+ /**
122
+ * 강설량 코드
123
+ * - 1(보통 눈); 1cm/h 미만
124
+ * - 2(많은 눈); 1cm/h 이상
125
+ */
126
+ snowAmount: string | null;
127
+ /**
128
+ * 풍속 코드
129
+ * - 1(약한 바람); 4m/s 미만
130
+ * - 2(약간 강한 바람); 4m/s 이상 9m/s 미만
131
+ * - 3(강한 바람); 9m/s 이상
132
+ */
133
+ windSpeed: string | null;
134
+ /**
135
+ * 날씨 상태 코드
136
+ * - sky-1(맑음)
137
+ * - sky-2(구름조금)
138
+ * - sky-3(구름많음)
139
+ * - sky-4(흐림)
140
+ * - drop-rain-shower(소나기)
141
+ * - drop-rain(비)
142
+ * - drop-rain-1(약한 비)
143
+ * - drop-rain-2(보통 비)
144
+ * - drop-rain-3(강한 비)
145
+ * - drop-rain-snow(비/눈)
146
+ * - drop-rain-snow-1(약한 비/눈)
147
+ * - drop-rain-snow-2(보통 비/눈)
148
+ * - drop-rain-snow-3(강한 비/눈)
149
+ * - drop-snow(눈)
150
+ * - drop-snow-1(보통 눈)
151
+ * - drop-snow-2(많은 눈)
152
+ */
153
+ condition: string | null;
154
+ /** 날씨 상태 텍스트 */
155
+ conditionName: string | null;
156
+ };
157
+
158
+ /**
159
+ * 기상청 API; 오늘 날씨
160
+ * @property {number|string|null} temperature 기온(℃)
161
+ * @property {number|string|null} max_temperature 최고기온(℃)
162
+ * @property {number|string|null} min_temperature 최저기온(℃)
163
+ * @property {number|string|null} humidity 습도(%)
164
+ */
165
+ export type API_Res_WeatherKoreaToday = API_Res_WeatherKoreaBase & {
166
+ /** 기온(℃) */
167
+ temperature: number | string | null;
168
+ /** 최고 기온(℃) */
169
+ max_temperature: number | string | null;
170
+ /** 최저 기온(℃) */
171
+ min_temperature: number | string | null;
172
+ /** 습도(%) */
173
+ humidity: number | string | null;
174
+ };
175
+
176
+ /**
177
+ * 기상청 API; 내일/모레 날씨
178
+ * @property {number|string|null} max_temperature 최고기온(℃)
179
+ * @property {number|string|null} min_temperature 최저기온(℃)
180
+ */
181
+ export type API_Res_WeatherKoreaNextDays = API_Res_WeatherKoreaBase & {
182
+ /** 최고 기온(℃) */
183
+ max_temperature: number | string | null;
184
+ /** 최저 기온(℃) */
185
+ min_temperature: number | string | null;
186
+ };
187
+
188
+ /**
189
+ * 기상청 API; 원본 응답
190
+ * @property {RAW} raw API 원본 데이터
191
+ */
192
+ export type API_Res_WeatherKoreaRaw<RAW = KMA_Res_Base<KMA_Res_WeatherItem>> = {
193
+ /** API 원본 */
194
+ raw: RAW;
195
+ };
196
+
197
+ /**
198
+ * 기상청 API; 초단기 실황 구조
199
+ * @property {API_Res_WeatherKoreaToday} today 오늘 데이터
200
+ */
201
+ export type API_Res_WeatherKoreaNow =
202
+ API_Res_WeatherKoreaRaw<KMA_Res_WeatherNow> & {
203
+ /** 오늘 날씨 */
204
+ today: API_Res_WeatherKoreaToday;
205
+ };
206
+
207
+ /**
208
+ * 기상청 API; 단기 예보 구조
209
+ * @property {API_Res_WeatherKoreaToday} today 오늘 데이터
210
+ * @property {API_Res_WeatherKoreaNextDays} day_1 내일 데이터
211
+ * @property {API_Res_WeatherKoreaNextDays} day_2 모레 데이터
212
+ */
213
+ export type API_Res_WeatherKoreaForecast =
214
+ API_Res_WeatherKoreaRaw<KMA_Res_WeatherForecast> & {
215
+ /** 오늘 날씨 */
216
+ today: API_Res_WeatherKoreaToday;
217
+ /** 내일 날씨 */
218
+ day_1: API_Res_WeatherKoreaNextDays;
219
+ /** 모레 날씨 */
220
+ day_2: API_Res_WeatherKoreaNextDays;
221
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 위경도 좌표.
3
+ * @property {number | null} latitude 위도
4
+ * @property {number | null} longitude 경도
5
+ */
6
+ export type GeoCoordinate = {
7
+ /** 위도 */
8
+ latitude: number | null;
9
+ /** 경도 */
10
+ longitude: number | null;
11
+ };
12
+
13
+ /**
14
+ * 날씨 API에서 사용하는 위경도 좌표.
15
+ * @property {number | null} lat 위도
16
+ * @property {number | null} lng 경도
17
+ */
18
+ export type WeatherGeoCoordinate = {
19
+ /** 위도 */
20
+ lat: number | null;
21
+ /** 경도 */
22
+ lng: number | null;
23
+ };
24
+
25
+ /**
26
+ * 날씨 API locale 요청 옵션.
27
+ * @property {string} [locale] 요청 언어 코드. 기본 지원값은 ko, en, ja이며 타입은 확장 가능한 string으로 유지한다.
28
+ */
29
+ export interface WeatherApiLocaleOptions {
30
+ /**
31
+ * 요청 언어 코드. 기본 지원값은 ko, en, ja이며 타입은 확장 가능한 string으로 유지한다.
32
+ */
33
+ locale?: string;
34
+ }
35
+
36
+ /**
37
+ * OpenWeatherMap 요청 파라미터.
38
+ * @property {number | null} lat 위도
39
+ * @property {number | null} lng 경도
40
+ * @property {string} [locale] 요청 언어 코드
41
+ */
42
+ export type WeatherOpenWeatherMapParams = WeatherGeoCoordinate &
43
+ WeatherApiLocaleOptions;
44
+
45
+ /**
46
+ * 좌표와 주소 정보를 함께 담는 구조.
47
+ * @property {string | null} address 좌표 주소
48
+ */
49
+ export type WeatherCoordinate = {
50
+ /** 좌표 주소 */
51
+ address: string | null;
52
+ } & WeatherGeoCoordinate;
53
+
54
+ /**
55
+ * 날씨 로직에서 사용하는 날짜 기준값 모음.
56
+ * @property {Date} now 현재 시각
57
+ * @property {string} today 오늘 날짜(yyyymmdd)
58
+ * @property {string} tomorrow 내일 날짜(yyyymmdd)
59
+ * @property {string} dayAfterTomorrow 모레 날짜(yyyymmdd)
60
+ */
61
+ export type WeatherUtilDateTimeMoments = {
62
+ /** 현재 시각 */
63
+ now: Date;
64
+ /** 오늘 날짜(yyyymmdd) */
65
+ today: string;
66
+ /** 내일 날짜(yyyymmdd) */
67
+ tomorrow: string;
68
+ /** 모레 날짜(yyyymmdd) */
69
+ dayAfterTomorrow: string;
70
+ };
@@ -0,0 +1,4 @@
1
+ export type * from "./base";
2
+ export type * from "./api";
3
+ export type * from "./korea";
4
+ export type * from "./open-weather-map";
@@ -0,0 +1,5 @@
1
+ export * from "./date-time";
2
+ export * from "./location";
3
+ export * from "./weather";
4
+ export * from "./locale";
5
+ export * from "./validate";
@@ -0,0 +1,28 @@
1
+ const OPEN_WEATHER_MAP_LANG_BY_LOCALE: Record<string, string> = {
2
+ ko: "kr",
3
+ en: "en",
4
+ ja: "ja",
5
+ };
6
+
7
+ /**
8
+ * Weather Locale; locale 기준 키 정규화
9
+ * @param {string} [locale] locale 코드
10
+ * @return {string} 전달된 locale의 기본 언어 코드
11
+ * @example
12
+ * getWeatherLocaleKey("ko-KR")
13
+ */
14
+ export const getWeatherLocaleKey = (locale?: string): string =>
15
+ locale?.trim().toLowerCase().split(/[-_]/)[0] || "ko";
16
+
17
+ /**
18
+ * Weather Locale; OpenWeatherMap API lang 값 변환
19
+ * @param {string} [locale] locale 코드
20
+ * @return {string} OpenWeatherMap lang 파라미터
21
+ * @example
22
+ * getOpenWeatherMapLang("ko")
23
+ */
24
+ export const getOpenWeatherMapLang = (locale?: string): string => {
25
+ const localeKey = getWeatherLocaleKey(locale);
26
+
27
+ return OPEN_WEATHER_MAP_LANG_BY_LOCALE[localeKey] ?? localeKey;
28
+ };
@@ -0,0 +1,139 @@
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
+ return { latitude: null, longitude: null };
16
+ }
17
+
18
+ try {
19
+ const position = await new Promise<GeolocationPosition>(
20
+ (resolve, reject) => {
21
+ window.navigator.geolocation.getCurrentPosition(resolve, reject);
22
+ },
23
+ );
24
+ return {
25
+ latitude: position.coords.latitude,
26
+ longitude: position.coords.longitude,
27
+ };
28
+ } catch (error) {
29
+ console.error("[userLocation] 위치 정보 에러", error);
30
+ return { latitude: null, longitude: null };
31
+ }
32
+ };
33
+
34
+ /**
35
+ * 날씨 도구; 위경도 -> 격자 좌표 변환
36
+ * @util
37
+ * @param {GeoCoordinate} coordinate 위경도 좌표
38
+ * @param {number} coordinate.latitude 위도
39
+ * @param {number} coordinate.longitude 경도
40
+ * @return {KMA_GridCoordinate} 격자 좌표
41
+ * @see https://apihub.kma.go.kr/
42
+ */
43
+ export function convertCoordinateToGrid({
44
+ latitude,
45
+ longitude,
46
+ }: GeoCoordinate): KMA_GridCoordinate {
47
+ const res: KMA_GridCoordinate = { x: 0, y: 0 };
48
+
49
+ if (latitude === null || longitude === null) return res;
50
+
51
+ const RE = 6371.00877;
52
+ const GRID = 5.0;
53
+ const SLAT1 = 30.0;
54
+ const SLAT2 = 60.0;
55
+ const ORIGIN_LONGITUDE = 126.0;
56
+ const ORIGIN_LATITUDE = 38.0;
57
+ const XO = 43;
58
+ const YO = 136;
59
+
60
+ const RADIAN = Math.PI / 180.0;
61
+ const re = RE / GRID;
62
+ const slat1 = SLAT1 * RADIAN;
63
+ const slat2 = SLAT2 * RADIAN;
64
+ const origin_longitude = ORIGIN_LONGITUDE * RADIAN;
65
+ const origin_latitude = ORIGIN_LATITUDE * RADIAN;
66
+
67
+ const sn =
68
+ Math.log(Math.cos(slat1) / Math.cos(slat2)) /
69
+ Math.log(
70
+ Math.tan(Math.PI * 0.25 + slat2 * 0.5) /
71
+ Math.tan(Math.PI * 0.25 + slat1 * 0.5),
72
+ );
73
+
74
+ const sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
75
+ const sfPow = (Math.pow(sf, sn) * Math.cos(slat1)) / sn;
76
+
77
+ const ro =
78
+ (re * sfPow) /
79
+ Math.pow(Math.tan(Math.PI * 0.25 + origin_latitude * 0.5), sn);
80
+
81
+ const ra =
82
+ (re * sfPow) /
83
+ Math.pow(Math.tan(Math.PI * 0.25 + latitude * RADIAN * 0.5), sn);
84
+ let theta = longitude * RADIAN - origin_longitude;
85
+ if (theta > Math.PI) theta -= 2.0 * Math.PI;
86
+ if (theta < -Math.PI) theta += 2.0 * Math.PI;
87
+ theta *= sn;
88
+
89
+ res.x = Math.floor(ra * Math.sin(theta) + XO + 0.5);
90
+ res.y = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
91
+
92
+ return res;
93
+ }
94
+
95
+ /**
96
+ * 날씨 도구; 위도/경도 -> 기상청 격자 좌표 변환
97
+ * @util
98
+ * @param {WeatherCoordinate} [coordinate] 사용자 위치 이외의 특정 위도/경도 좌표
99
+ * @param {number} coordinate.lat 위도
100
+ * @param {number} coordinate.lng 경도
101
+ * @return {KMA_Req_BaseGridCoordinate} 기상청 격자 좌표
102
+ */
103
+ export async function getWeatherGridLocation(
104
+ coordinate?: WeatherCoordinate,
105
+ ): Promise<KMA_Req_BaseGridCoordinate> {
106
+ const res: KMA_Req_BaseGridCoordinate = { nx: 0, ny: 0 };
107
+
108
+ if (typeof window === "undefined") return res;
109
+
110
+ const geo: GeoCoordinate = {
111
+ latitude: coordinate?.lat ?? null,
112
+ longitude: coordinate?.lng ?? null,
113
+ };
114
+ const hasInjectedAddress =
115
+ typeof coordinate?.address === "string" && coordinate.address.trim() !== "";
116
+
117
+ if (
118
+ typeof window !== "undefined" &&
119
+ !hasInjectedAddress &&
120
+ (typeof coordinate === "undefined" ||
121
+ geo.latitude === null ||
122
+ geo.longitude === null)
123
+ ) {
124
+ const { latitude, longitude } = await userLocation();
125
+
126
+ if (latitude === null || longitude === null) return res;
127
+ geo.latitude = latitude;
128
+ geo.longitude = longitude;
129
+ }
130
+
131
+ const grid = convertCoordinateToGrid(geo);
132
+
133
+ if (grid.x === 0 || grid.y === 0) return res;
134
+
135
+ res.nx = grid.x;
136
+ res.ny = grid.y;
137
+
138
+ return res;
139
+ }