@operato/weather 9.0.0-beta.56

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 (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +337 -0
  3. package/assets/images/no-image.png +0 -0
  4. package/dist/src/index.d.ts +1 -0
  5. package/dist/src/index.js +2 -0
  6. package/dist/src/index.js.map +1 -0
  7. package/dist/src/layers/base-weather-layer.d.ts +56 -0
  8. package/dist/src/layers/base-weather-layer.js +83 -0
  9. package/dist/src/layers/base-weather-layer.js.map +1 -0
  10. package/dist/src/layers/cloud-layer.d.ts +17 -0
  11. package/dist/src/layers/cloud-layer.js +203 -0
  12. package/dist/src/layers/cloud-layer.js.map +1 -0
  13. package/dist/src/layers/precipitation-layer.d.ts +27 -0
  14. package/dist/src/layers/precipitation-layer.js +344 -0
  15. package/dist/src/layers/precipitation-layer.js.map +1 -0
  16. package/dist/src/layers/solar-layer.d.ts +17 -0
  17. package/dist/src/layers/solar-layer.js +253 -0
  18. package/dist/src/layers/solar-layer.js.map +1 -0
  19. package/dist/src/layers/temperature-layer.d.ts +13 -0
  20. package/dist/src/layers/temperature-layer.js +230 -0
  21. package/dist/src/layers/temperature-layer.js.map +1 -0
  22. package/dist/src/layers/wind-layer.d.ts +60 -0
  23. package/dist/src/layers/wind-layer.js +311 -0
  24. package/dist/src/layers/wind-layer.js.map +1 -0
  25. package/dist/src/ox-weather.d.ts +112 -0
  26. package/dist/src/ox-weather.js +645 -0
  27. package/dist/src/ox-weather.js.map +1 -0
  28. package/dist/src/ox-wind.d.ts +91 -0
  29. package/dist/src/ox-wind.js +425 -0
  30. package/dist/src/ox-wind.js.map +1 -0
  31. package/dist/src/services/weather-service.d.ts +79 -0
  32. package/dist/src/services/weather-service.js +560 -0
  33. package/dist/src/services/weather-service.js.map +1 -0
  34. package/dist/src/types.d.ts +71 -0
  35. package/dist/src/types.js +2 -0
  36. package/dist/src/types.js.map +1 -0
  37. package/dist/stories/data-weather.stories.d.ts +47 -0
  38. package/dist/stories/data-weather.stories.js +93 -0
  39. package/dist/stories/data-weather.stories.js.map +1 -0
  40. package/dist/tsconfig.tsbuildinfo +1 -0
  41. package/package.json +86 -0
  42. package/themes/app-theme.css +138 -0
  43. package/themes/calendar-theme.css +61 -0
  44. package/themes/dark.css +51 -0
  45. package/themes/form-theme.css +70 -0
  46. package/themes/grist-theme.css +175 -0
  47. package/themes/layout-theme.css +94 -0
  48. package/themes/light.css +51 -0
  49. package/themes/material-theme.css +23 -0
  50. package/themes/md-typescale-styles.css +100 -0
  51. package/themes/oops-theme.css +22 -0
  52. package/themes/report-theme.css +47 -0
  53. package/themes/spacing.css +23 -0
  54. package/themes/state-color.css +6 -0
  55. package/themes/tooltip-theme.css +11 -0
  56. package/translations/en.json +1 -0
  57. package/translations/ja.json +1 -0
  58. package/translations/ko.json +1 -0
  59. package/translations/ms.json +1 -0
  60. package/translations/zh.json +1 -0
@@ -0,0 +1,79 @@
1
+ import { WeatherService as IWeatherService, Location, WeatherData, WeatherGridData, WeatherServiceOptions } from '../types.js';
2
+ /**
3
+ * 날씨 데이터를 가져오고 관리하는 서비스 클래스
4
+ */
5
+ export declare class WeatherService implements IWeatherService {
6
+ private apiKey?;
7
+ private endpoint;
8
+ private cachedData;
9
+ private lastUpdated?;
10
+ private updateInterval;
11
+ private updateTimer?;
12
+ private onDataUpdate?;
13
+ /**
14
+ * 날씨 서비스 생성자
15
+ * @param options 날씨 서비스 설정 옵션
16
+ */
17
+ constructor(options?: WeatherServiceOptions);
18
+ /**
19
+ * 날씨 데이터를 가져옵니다.
20
+ * @param bounds 지도 경계 좌표 (남서, 북동)
21
+ * @param forceUpdate 강제 업데이트 여부
22
+ * @returns 최신 날씨 데이터
23
+ */
24
+ fetchWeatherData(bounds: [[number, number], [number, number]], forceUpdate?: boolean): Promise<WeatherData[]>;
25
+ /**
26
+ * 비동기적으로 모의 날씨 데이터를 생성합니다.
27
+ * 메인 스레드 차단을 방지하기 위해 청크 단위로 처리합니다.
28
+ */
29
+ private generateMockDataAsync;
30
+ /**
31
+ * 데이터 업데이트 자동화 시작
32
+ * @param bounds 지도 경계 좌표
33
+ * @param callback 데이터 업데이트 시 호출될 콜백 함수
34
+ */
35
+ startAutoUpdate(bounds: [[number, number], [number, number]], callback?: (data: WeatherData[]) => void): void;
36
+ /**
37
+ * 자동 업데이트 중지
38
+ */
39
+ stopAutoUpdate(): void;
40
+ /**
41
+ * API 데이터 처리 및 변환
42
+ * @param apiData API에서 받은 데이터
43
+ * @returns 변환된 날씨 데이터 배열
44
+ */
45
+ private processApiData;
46
+ /**
47
+ * 모의 날씨 데이터를 생성합니다.
48
+ * @param bounds 지도 경계 좌표
49
+ * @returns 생성된 목업 날씨 데이터
50
+ */
51
+ private generateMockData;
52
+ /**
53
+ * 위치가 해안 지역인지 확인합니다 (간단한 모의 함수)
54
+ * @param lng 경도
55
+ * @param lat 위도
56
+ * @returns 해안 지역 여부
57
+ */
58
+ private isCoastalRegion;
59
+ /**
60
+ * 특정 위치의 날씨 정보를 가져옵니다.
61
+ */
62
+ getWeatherByLocation(location: Location): Promise<WeatherData>;
63
+ /**
64
+ * 특정 위치 주변의 날씨 정보를 그리드 형태로 가져옵니다.
65
+ */
66
+ getWeatherAroundLocation(location: Location, radius: number): Promise<WeatherGridData>;
67
+ /**
68
+ * 테스트용 날씨 데이터 생성
69
+ */
70
+ private generateMockWeatherData;
71
+ /**
72
+ * 테스트용 그리드 날씨 데이터 생성
73
+ */
74
+ private generateMockWeatherGridData;
75
+ /**
76
+ * 구름량에 따른 날씨 설명을 반환합니다.
77
+ */
78
+ private getWeatherDescription;
79
+ }
@@ -0,0 +1,560 @@
1
+ /**
2
+ * 날씨 데이터를 가져오고 관리하는 서비스 클래스
3
+ */
4
+ export class WeatherService {
5
+ /**
6
+ * 날씨 서비스 생성자
7
+ * @param options 날씨 서비스 설정 옵션
8
+ */
9
+ constructor(options = {}) {
10
+ this.cachedData = [];
11
+ this.updateInterval = 30 * 60 * 1000; // 기본 30분
12
+ this.apiKey = options.apiKey;
13
+ this.endpoint = options.endpoint || 'https://api.weather.example.com/v1';
14
+ if (options.updateInterval) {
15
+ this.updateInterval = options.updateInterval * 1000; // 초 단위를 밀리초로 변환
16
+ }
17
+ if (options.mockData) {
18
+ this.cachedData = options.mockData;
19
+ this.lastUpdated = new Date();
20
+ }
21
+ }
22
+ /**
23
+ * 날씨 데이터를 가져옵니다.
24
+ * @param bounds 지도 경계 좌표 (남서, 북동)
25
+ * @param forceUpdate 강제 업데이트 여부
26
+ * @returns 최신 날씨 데이터
27
+ */
28
+ async fetchWeatherData(bounds, forceUpdate = false) {
29
+ console.log('날씨 데이터 요청 - 경계:', bounds, '강제 업데이트:', forceUpdate);
30
+ // 캐시된 데이터가 있고 강제 업데이트가 아니면 캐시 반환
31
+ const now = Date.now();
32
+ const cacheTTL = 5 * 60 * 1000; // 5분 캐시
33
+ if (!forceUpdate && this.cachedData.length > 0 && this.lastUpdated && now - this.lastUpdated.getTime() < cacheTTL) {
34
+ console.log('캐시된 날씨 데이터 반환 - 데이터 수:', this.cachedData.length);
35
+ return Promise.resolve(this.cachedData);
36
+ }
37
+ try {
38
+ // API 호출 (실제 API 키가 있으면 실행)
39
+ if (this.apiKey && this.endpoint) {
40
+ try {
41
+ console.log('실제 API에서 날씨 데이터 요청 중...');
42
+ // API 호출 구현
43
+ // const apiData = await this.callAPI(bounds)
44
+ // return this.processApiData(apiData)
45
+ }
46
+ catch (error) {
47
+ console.error('API 데이터 가져오기 실패, 모의 데이터로 대체합니다:', error);
48
+ }
49
+ }
50
+ console.log('모의 날씨 데이터 생성 시작');
51
+ // 비동기적으로 데이터 생성 (메인 스레드 차단 방지)
52
+ const mockData = await this.generateMockDataAsync(bounds);
53
+ // 생성된 데이터 캐싱
54
+ this.cachedData = mockData;
55
+ this.lastUpdated = new Date();
56
+ return mockData;
57
+ }
58
+ catch (error) {
59
+ console.error('날씨 데이터 생성 오류:', error);
60
+ return this.cachedData.length > 0 ? this.cachedData : [];
61
+ }
62
+ }
63
+ /**
64
+ * 비동기적으로 모의 날씨 데이터를 생성합니다.
65
+ * 메인 스레드 차단을 방지하기 위해 청크 단위로 처리합니다.
66
+ */
67
+ async generateMockDataAsync(bounds) {
68
+ return new Promise(resolve => {
69
+ // 작업을 비동기로 처리하여 UI 차단 방지
70
+ setTimeout(() => {
71
+ const mockData = this.generateMockData(bounds);
72
+ resolve(mockData);
73
+ }, 0);
74
+ });
75
+ }
76
+ /**
77
+ * 데이터 업데이트 자동화 시작
78
+ * @param bounds 지도 경계 좌표
79
+ * @param callback 데이터 업데이트 시 호출될 콜백 함수
80
+ */
81
+ startAutoUpdate(bounds, callback) {
82
+ if (callback) {
83
+ this.onDataUpdate = callback;
84
+ }
85
+ // 이전 타이머가 있으면 제거
86
+ if (this.updateTimer) {
87
+ clearInterval(this.updateTimer);
88
+ }
89
+ // 즉시 데이터 갱신
90
+ this.fetchWeatherData(bounds).then(data => {
91
+ if (this.onDataUpdate) {
92
+ this.onDataUpdate(data);
93
+ }
94
+ });
95
+ // 갱신 타이머 설정
96
+ this.updateTimer = window.setInterval(() => {
97
+ this.fetchWeatherData(bounds, true).then(data => {
98
+ if (this.onDataUpdate) {
99
+ this.onDataUpdate(data);
100
+ }
101
+ });
102
+ }, this.updateInterval);
103
+ }
104
+ /**
105
+ * 자동 업데이트 중지
106
+ */
107
+ stopAutoUpdate() {
108
+ if (this.updateTimer) {
109
+ clearInterval(this.updateTimer);
110
+ this.updateTimer = undefined;
111
+ }
112
+ }
113
+ /**
114
+ * API 데이터 처리 및 변환
115
+ * @param apiData API에서 받은 데이터
116
+ * @returns 변환된 날씨 데이터 배열
117
+ */
118
+ processApiData(apiData) {
119
+ // API 응답 포맷에 맞게 데이터 변환
120
+ // 실제 API 구현에 따라 수정 필요
121
+ return apiData.map((item) => ({
122
+ location: {
123
+ latitude: item.lat,
124
+ longitude: item.lon
125
+ },
126
+ temperature: {
127
+ current: item.temp,
128
+ unit: 'celsius'
129
+ },
130
+ wind: {
131
+ speed: item.wind_speed,
132
+ direction: item.wind_dir,
133
+ unit: 'm/s'
134
+ },
135
+ cloud: {
136
+ coverage: item.cloud_cover,
137
+ height: item.cloud_height
138
+ },
139
+ solar: {
140
+ radiation: item.solar_rad,
141
+ unit: 'W/m²'
142
+ },
143
+ precipitation: {
144
+ amount: item.precip,
145
+ probability: item.precip_prob,
146
+ unit: 'mm'
147
+ },
148
+ time: new Date(item.timestamp)
149
+ }));
150
+ }
151
+ /**
152
+ * 모의 날씨 데이터를 생성합니다.
153
+ * @param bounds 지도 경계 좌표
154
+ * @returns 생성된 목업 날씨 데이터
155
+ */
156
+ generateMockData(bounds) {
157
+ // 한반도 및 주변 지역 위치로 제한 (북위 33-39도, 동경 124-132도)
158
+ const [[swLng, swLat], [neLng, neLat]] = bounds;
159
+ // 남한 지역 위주의 경계로 제한
160
+ const minLng = Math.max(swLng, 124.0);
161
+ const maxLng = Math.min(neLng, 132.0);
162
+ const minLat = Math.max(swLat, 32.0);
163
+ const maxLat = Math.min(neLat, 40.0);
164
+ console.log('모의 날씨 데이터 생성 중 - 제한된 영역:', [
165
+ [minLng, minLat],
166
+ [maxLng, maxLat]
167
+ ]);
168
+ // 적절한 데이터 밀도 (성능 최적화)
169
+ const gridSize = 30; // 기존 90에서 30으로 축소
170
+ const latStep = (maxLat - minLat) / gridSize;
171
+ const lngStep = (maxLng - minLng) / gridSize;
172
+ // 날씨 패턴 - 주요 지역만 포함
173
+ const weatherPatterns = [
174
+ { center: [126.9, 37.5], type: 'warm', radius: 2.0 }, // 서울 근처
175
+ { center: [129.0, 35.2], type: 'sunny', radius: 1.5 }, // 부산 근처
176
+ { center: [126.5, 34.8], type: 'rainy', radius: 2.0 }, // 목포 근처
177
+ { center: [130.0, 38.0], type: 'cold', radius: 2.5 } // 동해 북부
178
+ ];
179
+ const currentDate = new Date();
180
+ const hour = currentDate.getHours();
181
+ // 시간에 따른 태양광 베이스 값
182
+ const baseSolarRadiation = hour >= 6 && hour <= 18 ? 500 + Math.sin(((hour - 6) * Math.PI) / 12) * 500 : 0;
183
+ // 계절에 따른 기본 온도 (월별)
184
+ const month = currentDate.getMonth(); // 0(1월)~11(12월)
185
+ const baseTempByMonth = [0, 2, 8, 15, 20, 25, 28, 28, 22, 15, 8, 2];
186
+ const baseTemp = baseTempByMonth[month];
187
+ const mockData = [];
188
+ let totalPoints = 0;
189
+ let count = 0;
190
+ // 희소한 샘플링으로 대표 위치만 선택
191
+ for (let lat = minLat; lat <= maxLat; lat += latStep) {
192
+ for (let lng = minLng; lng <= maxLng; lng += lngStep) {
193
+ totalPoints++;
194
+ // 75% 정도의 포인트만 생성 (랜덤 샘플링)
195
+ if (Math.random() > 0.75) {
196
+ continue;
197
+ }
198
+ count++;
199
+ // 가장 가까운 날씨 패턴 찾기
200
+ const nearestPattern = weatherPatterns
201
+ .map(pattern => {
202
+ const [patternLng, patternLat] = pattern.center;
203
+ const distance = Math.sqrt(Math.pow(lng - patternLng, 2) + Math.pow(lat - patternLat, 2));
204
+ return { ...pattern, distance };
205
+ })
206
+ .sort((a, b) => a.distance - b.distance)[0];
207
+ // 패턴에 따른 날씨 변화
208
+ const patternFactor = Math.max(0, 1 - nearestPattern.distance / nearestPattern.radius);
209
+ const randomFactor = Math.random() * 0.5; // 약간의 랜덤성 추가
210
+ // 위도에 따른 온도 변화 (남쪽이 더 따뜻함)
211
+ const latFactor = (lat - minLat) / (maxLat - minLat); // 0 (남) ~ 1 (북)
212
+ const latTempAdjust = (1 - latFactor) * 8; // 최대 8도 차이
213
+ // 기본 온도에 패턴 효과 적용
214
+ let temp = baseTemp + latTempAdjust;
215
+ switch (nearestPattern.type) {
216
+ case 'warm':
217
+ temp += 5 * patternFactor;
218
+ break;
219
+ case 'cold':
220
+ temp -= 8 * patternFactor;
221
+ break;
222
+ default:
223
+ temp += (randomFactor - 0.25) * 2; // -0.5 ~ +0.5
224
+ }
225
+ // 구름 양과 강수 확률
226
+ let cloud = 0;
227
+ let precipProb = 0;
228
+ let precipAmount = 0;
229
+ switch (nearestPattern.type) {
230
+ case 'rainy':
231
+ cloud = 0.7 + 0.3 * patternFactor + randomFactor * 0.2;
232
+ precipProb = Math.min(100, 70 + 30 * patternFactor + randomFactor * 10);
233
+ precipAmount = 5 * patternFactor + randomFactor * 3;
234
+ break;
235
+ case 'sunny':
236
+ cloud = 0.1 + randomFactor * 0.2;
237
+ precipProb = Math.max(0, randomFactor * 5);
238
+ precipAmount = 0;
239
+ break;
240
+ default:
241
+ cloud = 0.3 + randomFactor * 0.4;
242
+ precipProb = 10 + randomFactor * 20;
243
+ precipAmount = randomFactor * 2;
244
+ }
245
+ // 태양광 (구름 양에 따라 감소)
246
+ const solar = baseSolarRadiation * (1 - cloud * 0.8);
247
+ // 바람 속도와 방향
248
+ // 기본 바람 패턴: 저위도에서 고위도로, 바다에서 육지로
249
+ const isCoastal = this.isCoastalRegion(lng, lat);
250
+ // 바람 방향 결정 (0-360, 북쪽이 0도, 시계방향)
251
+ let windDirection = 0;
252
+ // 계절에 따른 주요 바람 방향
253
+ if (month >= 11 || month <= 1) {
254
+ // 겨울: 북서풍 우세
255
+ windDirection = 315 + (Math.random() - 0.5) * 40;
256
+ }
257
+ else if (month >= 5 && month <= 8) {
258
+ // 여름: 남동풍 우세
259
+ windDirection = 135 + (Math.random() - 0.5) * 40;
260
+ }
261
+ else {
262
+ // 봄/가을: 서풍 우세
263
+ windDirection = 270 + (Math.random() - 0.5) * 60;
264
+ }
265
+ // 해안가는 낮에는 육지에서 바다로, 밤에는 바다에서 육지로
266
+ if (isCoastal) {
267
+ if (hour >= 10 && hour <= 16) {
268
+ // 낮
269
+ windDirection = (windDirection + 60) % 360;
270
+ }
271
+ else if (hour >= 22 || hour <= 4) {
272
+ // 밤
273
+ windDirection = (windDirection - 60 + 360) % 360;
274
+ }
275
+ }
276
+ // 패턴에 따른 풍속 조정
277
+ let windSpeed = 0;
278
+ switch (nearestPattern.type) {
279
+ case 'rainy':
280
+ // 비 구역은 바람이 더 강함
281
+ windSpeed = 5 + 10 * patternFactor + randomFactor * 3;
282
+ break;
283
+ case 'warm':
284
+ // 따뜻한 지역은 바람이 약함
285
+ windSpeed = 2 + randomFactor * 3;
286
+ break;
287
+ case 'cold':
288
+ // 추운 지역은 바람이 강함
289
+ windSpeed = 6 + 8 * patternFactor + randomFactor * 3;
290
+ break;
291
+ default:
292
+ windSpeed = 3 + randomFactor * 5;
293
+ }
294
+ // 고도에 따른 풍속 증가 (높은 위도에서 더 강함)
295
+ windSpeed *= 1 + latFactor * 0.5;
296
+ // 풍속 범위 제한 (0.5 ~ 20 m/s)
297
+ windSpeed = Math.max(0.5, Math.min(20, windSpeed));
298
+ // 날씨 데이터 객체 생성
299
+ mockData.push({
300
+ location: {
301
+ latitude: lat,
302
+ longitude: lng
303
+ },
304
+ temperature: {
305
+ current: Number(temp.toFixed(1)),
306
+ unit: 'celsius'
307
+ },
308
+ wind: {
309
+ speed: Number(windSpeed.toFixed(1)),
310
+ direction: Math.round(windDirection),
311
+ unit: 'm/s'
312
+ },
313
+ cloud: {
314
+ coverage: Number((cloud * 100).toFixed(0)),
315
+ height: 1000 + Math.random() * 2000
316
+ },
317
+ solar: {
318
+ radiation: Number(solar.toFixed(0)),
319
+ unit: 'W/m²'
320
+ },
321
+ precipitation: {
322
+ amount: Number(precipAmount.toFixed(1)),
323
+ probability: Math.round(precipProb),
324
+ unit: 'mm'
325
+ },
326
+ time: new Date()
327
+ });
328
+ }
329
+ }
330
+ console.log(`모의 날씨 데이터 생성 완료: 총 ${count}개 생성 (전체 격자 중 ${totalPoints}개)`);
331
+ console.log('샘플 데이터:', mockData.length > 0
332
+ ? [
333
+ {
334
+ location: mockData[0].location,
335
+ temp: mockData[0].temperature.current,
336
+ wind: mockData[0].wind,
337
+ solar: mockData[0].solar
338
+ },
339
+ {
340
+ location: mockData[Math.floor(mockData.length / 2)].location,
341
+ temp: mockData[Math.floor(mockData.length / 2)].temperature.current,
342
+ wind: mockData[Math.floor(mockData.length / 2)].wind,
343
+ solar: mockData[Math.floor(mockData.length / 2)].solar
344
+ }
345
+ ]
346
+ : 'No data');
347
+ return mockData;
348
+ }
349
+ /**
350
+ * 위치가 해안 지역인지 확인합니다 (간단한 모의 함수)
351
+ * @param lng 경도
352
+ * @param lat 위도
353
+ * @returns 해안 지역 여부
354
+ */
355
+ isCoastalRegion(lng, lat) {
356
+ // 한반도 해안선 근사:
357
+ // 서해안: ~126-126.6도
358
+ // 동해안: ~129-130도
359
+ // 남해안: ~34.4-35도
360
+ // 서해안
361
+ if (125.9 <= lng && lng <= 126.7 && lat >= 34.5) {
362
+ return true;
363
+ }
364
+ // 동해안
365
+ if (128.8 <= lng && lng <= 130.1 && lat >= 35.0) {
366
+ return true;
367
+ }
368
+ // 남해안
369
+ if (126.5 <= lng && lng <= 129.3 && 34.3 <= lat && lat <= 35.1) {
370
+ return true;
371
+ }
372
+ // 제주도 주변
373
+ if (126.0 <= lng && lng <= 127.0 && 33.0 <= lat && lat <= 33.6) {
374
+ return true;
375
+ }
376
+ return false;
377
+ }
378
+ /**
379
+ * 특정 위치의 날씨 정보를 가져옵니다.
380
+ */
381
+ async getWeatherByLocation(location) {
382
+ // 실제 구현에서는 API 호출 등으로 데이터를 가져옵니다.
383
+ // 테스트를 위해 임시 데이터를 생성합니다.
384
+ return this.generateMockWeatherData(location);
385
+ }
386
+ /**
387
+ * 특정 위치 주변의 날씨 정보를 그리드 형태로 가져옵니다.
388
+ */
389
+ async getWeatherAroundLocation(location, radius) {
390
+ return this.generateMockWeatherGridData(location, radius);
391
+ }
392
+ /**
393
+ * 테스트용 날씨 데이터 생성
394
+ */
395
+ generateMockWeatherData(location) {
396
+ const cloudCoverage = Math.random() * 100;
397
+ const solarRadiation = Math.random() * 1000;
398
+ const currentTime = new Date();
399
+ return {
400
+ location,
401
+ temperature: {
402
+ current: 20 + Math.random() * 10,
403
+ unit: '°C'
404
+ },
405
+ wind: {
406
+ speed: 5 + Math.random() * 20,
407
+ unit: 'km/h',
408
+ direction: Math.random() * 360
409
+ },
410
+ humidity: 30 + Math.random() * 50,
411
+ cloud: {
412
+ coverage: cloudCoverage,
413
+ height: 1000 + Math.random() * 2000
414
+ },
415
+ cloudCoverage,
416
+ solar: {
417
+ radiation: solarRadiation,
418
+ unit: 'W/m²'
419
+ },
420
+ solarRadiation,
421
+ precipitation: {
422
+ amount: Math.random() * 10,
423
+ probability: Math.random() * 100
424
+ },
425
+ weatherDescription: this.getWeatherDescription(cloudCoverage),
426
+ time: currentTime,
427
+ timestamp: currentTime.toISOString()
428
+ };
429
+ }
430
+ /**
431
+ * 테스트용 그리드 날씨 데이터 생성
432
+ */
433
+ generateMockWeatherGridData(center, radius) {
434
+ const gridSize = {
435
+ rows: 10,
436
+ cols: 10
437
+ };
438
+ const latRange = radius / 111; // 대략적인 위도 1도당 111km
439
+ const lonRange = radius / (111 * Math.cos((center.latitude * Math.PI) / 180));
440
+ const bounds = {
441
+ north: center.latitude + latRange,
442
+ south: center.latitude - latRange,
443
+ east: center.longitude + lonRange,
444
+ west: center.longitude - lonRange
445
+ };
446
+ const data = [];
447
+ // 격자별 위치 계산
448
+ const latStep = (bounds.north - bounds.south) / gridSize.rows;
449
+ const lonStep = (bounds.east - bounds.west) / gridSize.cols;
450
+ // 기상 현상 중심점 생성
451
+ const weatherCenters = [
452
+ { i: gridSize.rows * 0.3, j: gridSize.cols * 0.3, type: 'storm' },
453
+ { i: gridSize.rows * 0.7, j: gridSize.cols * 0.7, type: 'heat' },
454
+ { i: gridSize.rows * 0.5, j: gridSize.cols * 0.2, type: 'cold' },
455
+ { i: gridSize.rows * 0.2, j: gridSize.cols * 0.8, type: 'rain' }
456
+ ];
457
+ for (let i = 0; i < gridSize.rows; i++) {
458
+ data[i] = [];
459
+ for (let j = 0; j < gridSize.cols; j++) {
460
+ const lat = bounds.south + i * latStep;
461
+ const lon = bounds.west + j * lonStep;
462
+ // 각 기상 현상 중심점으로부터의 영향 계산
463
+ let tempEffect = 0;
464
+ let windEffect = 0;
465
+ let cloudEffect = 0;
466
+ let solarEffect = 0;
467
+ let rainEffect = 0;
468
+ weatherCenters.forEach(center => {
469
+ const dist = Math.sqrt(Math.pow(i - center.i, 2) + Math.pow(j - center.j, 2));
470
+ const influence = Math.exp(-dist / 15); // 거리에 따른 영향력 감소
471
+ switch (center.type) {
472
+ case 'storm':
473
+ windEffect += influence * 2;
474
+ cloudEffect += influence * 1.5;
475
+ rainEffect += influence;
476
+ tempEffect -= influence * 5;
477
+ solarEffect -= influence;
478
+ break;
479
+ case 'heat':
480
+ tempEffect += influence * 10;
481
+ solarEffect += influence;
482
+ break;
483
+ case 'cold':
484
+ tempEffect -= influence * 10;
485
+ cloudEffect += influence * 0.5;
486
+ break;
487
+ case 'rain':
488
+ rainEffect += influence * 2;
489
+ cloudEffect += influence;
490
+ solarEffect -= influence;
491
+ break;
492
+ }
493
+ });
494
+ // 바람 방향 계산 (기상 현상 중심에서 멀어지는 방향)
495
+ let windDirection = 0;
496
+ weatherCenters.forEach(center => {
497
+ const dx = j - center.j;
498
+ const dy = i - center.i;
499
+ const dist = Math.sqrt(dx * dx + dy * dy);
500
+ if (dist > 0) {
501
+ const dir = Math.atan2(dx, dy) * (180 / Math.PI);
502
+ windDirection += dir;
503
+ }
504
+ });
505
+ windDirection = (windDirection / weatherCenters.length + 360) % 360;
506
+ // 날씨 데이터 생성
507
+ data[i][j] = {
508
+ location: { latitude: lat, longitude: lon },
509
+ temperature: {
510
+ current: 20 + tempEffect,
511
+ unit: '°C'
512
+ },
513
+ wind: {
514
+ speed: 5 + windEffect * 10,
515
+ unit: 'km/h',
516
+ direction: windDirection
517
+ },
518
+ humidity: Math.min(100, Math.max(0, 50 + rainEffect * 20 + cloudEffect * 10)),
519
+ cloudCoverage: Math.min(100, Math.max(0, 20 + cloudEffect * 50)),
520
+ solarRadiation: Math.max(0, 500 + solarEffect * 300),
521
+ precipitation: {
522
+ amount: Math.max(0, rainEffect * 5),
523
+ probability: Math.min(100, Math.max(0, rainEffect * 70))
524
+ },
525
+ weatherDescription: this.getWeatherDescription(cloudEffect * 100 + rainEffect * 50),
526
+ time: new Date(),
527
+ cloud: {
528
+ coverage: Math.min(100, Math.max(0, 20 + cloudEffect * 50)),
529
+ height: 1000 + Math.random() * 2000
530
+ },
531
+ solar: {
532
+ radiation: Math.max(0, 500 + solarEffect * 300),
533
+ unit: 'W/m²'
534
+ },
535
+ timestamp: new Date().toISOString()
536
+ };
537
+ }
538
+ }
539
+ return {
540
+ bounds,
541
+ gridSize,
542
+ data
543
+ };
544
+ }
545
+ /**
546
+ * 구름량에 따른 날씨 설명을 반환합니다.
547
+ */
548
+ getWeatherDescription(cloudcover) {
549
+ if (cloudcover < 10)
550
+ return '맑음';
551
+ if (cloudcover < 30)
552
+ return '대체로 맑음';
553
+ if (cloudcover < 60)
554
+ return '구름 조금';
555
+ if (cloudcover < 90)
556
+ return '대체로 흐림';
557
+ return '흐림';
558
+ }
559
+ }
560
+ //# sourceMappingURL=weather-service.js.map