@operato/weather 9.0.0 → 9.0.2

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 (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/src/index.d.ts +1 -0
  3. package/dist/src/index.js +2 -0
  4. package/dist/src/index.js.map +1 -0
  5. package/dist/src/layers/base-weather-layer.d.ts +56 -0
  6. package/dist/src/layers/base-weather-layer.js +83 -0
  7. package/dist/src/layers/base-weather-layer.js.map +1 -0
  8. package/dist/src/layers/cloud-layer.d.ts +17 -0
  9. package/dist/src/layers/cloud-layer.js +203 -0
  10. package/dist/src/layers/cloud-layer.js.map +1 -0
  11. package/dist/src/layers/precipitation-layer.d.ts +27 -0
  12. package/dist/src/layers/precipitation-layer.js +344 -0
  13. package/dist/src/layers/precipitation-layer.js.map +1 -0
  14. package/dist/src/layers/solar-layer.d.ts +17 -0
  15. package/dist/src/layers/solar-layer.js +253 -0
  16. package/dist/src/layers/solar-layer.js.map +1 -0
  17. package/dist/src/layers/temperature-layer.d.ts +13 -0
  18. package/dist/src/layers/temperature-layer.js +230 -0
  19. package/dist/src/layers/temperature-layer.js.map +1 -0
  20. package/dist/src/layers/wind-layer.d.ts +60 -0
  21. package/dist/src/layers/wind-layer.js +311 -0
  22. package/dist/src/layers/wind-layer.js.map +1 -0
  23. package/dist/src/ox-weather.d.ts +112 -0
  24. package/dist/src/ox-weather.js +645 -0
  25. package/dist/src/ox-weather.js.map +1 -0
  26. package/dist/src/ox-wind.d.ts +91 -0
  27. package/dist/src/ox-wind.js +425 -0
  28. package/dist/src/ox-wind.js.map +1 -0
  29. package/dist/src/services/weather-service.d.ts +79 -0
  30. package/dist/src/services/weather-service.js +560 -0
  31. package/dist/src/services/weather-service.js.map +1 -0
  32. package/dist/src/types.d.ts +71 -0
  33. package/dist/src/types.js +2 -0
  34. package/dist/src/types.js.map +1 -0
  35. package/package.json +5 -5
@@ -0,0 +1,311 @@
1
+ import BaseLayer from 'ol/layer/Base';
2
+ import { BaseWeatherLayer } from './base-weather-layer';
3
+ import { transform } from 'ol/proj';
4
+ import Flow from 'ol/layer/Flow';
5
+ import DataTileSource from 'ol/source/DataTile';
6
+ import { get as getProjection } from 'ol/proj';
7
+ import { createXYZ, wrapX } from 'ol/tilegrid';
8
+ /**
9
+ * 바람 레이어 클래스
10
+ * OpenLayers Flow 레이어를 사용하여 바람 흐름을 표현합니다.
11
+ * https://github.com/openlayers/openlayers/blob/v10.4.0/examples/wind.js 참고
12
+ */
13
+ export class WindLayer extends BaseWeatherLayer {
14
+ /**
15
+ * 생성자
16
+ */
17
+ constructor(options = {}) {
18
+ super();
19
+ this.layerType = 'wind';
20
+ // 바람 속도 범위 설정
21
+ this.maxSpeed = 15; // 최대 바람 속도 (m/s)
22
+ this.minSpeed = 0; // 최소 바람 속도
23
+ // 타일 생성 관련 설정
24
+ this.tileGrid = createXYZ();
25
+ this.tileSize = 256;
26
+ this.weatherPoints = [];
27
+ // 옵션 설정
28
+ this.maxSpeed = options.maxSpeed || 15;
29
+ }
30
+ /**
31
+ * 레이어 타입 반환
32
+ */
33
+ getType() {
34
+ return 'wind';
35
+ }
36
+ /**
37
+ * 레이어 생성
38
+ * OpenLayers Flow 레이어를 사용하여 바람 흐름을 표현합니다.
39
+ * OpenLayers 공식 예제 참고: https://github.com/openlayers/openlayers/blob/v10.4.0/examples/wind.js
40
+ */
41
+ createLayer() {
42
+ console.log('바람 레이어 생성 중...');
43
+ // 이미 생성된 레이어가 있다면 반환
44
+ if (this.windLayer) {
45
+ return this.windLayer;
46
+ }
47
+ // 날씨 데이터를 풍속 벡터로 변환 (바람 데이터 준비)
48
+ this.prepareWindData();
49
+ try {
50
+ // 바람 데이터 소스 생성
51
+ this.windSource = new DataTileSource({
52
+ tileGrid: this.tileGrid,
53
+ // transition must be 0, see https://github.com/openlayers/openlayers/issues/16119
54
+ transition: 0,
55
+ wrapX: true,
56
+ loader: (z, x, y) => {
57
+ return this.createWindTile(z, x, y);
58
+ }
59
+ });
60
+ // 실제 바람 데이터의 속도 범위 확인
61
+ let minSpeed = Infinity;
62
+ let maxSpeed = 0;
63
+ let totalSpeed = 0;
64
+ this.weatherPoints.forEach(point => {
65
+ minSpeed = Math.min(minSpeed, point.speed);
66
+ maxSpeed = Math.max(maxSpeed, point.speed);
67
+ totalSpeed += point.speed;
68
+ });
69
+ const avgSpeed = totalSpeed / this.weatherPoints.length;
70
+ console.log(`바람 속도 범위: ${minSpeed.toFixed(2)}~${maxSpeed.toFixed(2)} m/s, 평균: ${avgSpeed.toFixed(2)} m/s`);
71
+ // 효과적인 최대 속도 설정 (데이터 기반)
72
+ const effectiveMaxSpeed = maxSpeed > 0 ? Math.max(10, maxSpeed) : 10;
73
+ this.maxSpeed = effectiveMaxSpeed;
74
+ console.log(`Flow 레이어 최대 속도: ${effectiveMaxSpeed.toFixed(2)} m/s`);
75
+ // Flow 레이어 옵션 설정 - 단일 스타일 사용
76
+ const flowOptions = {
77
+ source: this.windSource,
78
+ zIndex: 10,
79
+ className: 'wind-layer',
80
+ maxSpeed: this.maxSpeed,
81
+ // 파티클 세부 설정 최적화
82
+ tracksPerPixel: 0.5, // 트랙 밀도 증가 (더 많은 입자)
83
+ fadeTrails: true, // 트레일 페이드 효과
84
+ trailLength: 0.45, // 중간 길이 트레일
85
+ // 단일 색상 스타일 - 주의: 복잡한 표현식 사용하지 않음
86
+ style: {
87
+ color: 'rgba(0, 191, 255, 0.8)', // 하늘색 (DeepSkyBlue)
88
+ width: 1.5
89
+ }
90
+ };
91
+ // @ts-ignore - Flow 레이어는 TypeScript 정의가 불완전할 수 있음
92
+ this.windLayer = new Flow(flowOptions);
93
+ console.log('Flow 레이어 생성 완료');
94
+ return this.windLayer;
95
+ }
96
+ catch (error) {
97
+ console.error('Flow 레이어 생성 중 오류:', error);
98
+ // 오류 발생 시 완전히 단순화된 옵션으로 다시 시도
99
+ try {
100
+ console.log('완전히 단순화된 옵션으로 다시 시도합니다...');
101
+ const verySimpleOptions = {
102
+ source: this.windSource,
103
+ zIndex: 10,
104
+ className: 'wind-layer',
105
+ maxSpeed: this.maxSpeed
106
+ };
107
+ // @ts-ignore
108
+ this.windLayer = new Flow(verySimpleOptions);
109
+ console.log('단순 Flow 레이어 생성 완료');
110
+ return this.windLayer;
111
+ }
112
+ catch (fallbackError) {
113
+ console.error('단순 Flow 레이어 생성도 실패:', fallbackError);
114
+ return new BaseLayer({});
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * 날씨 데이터에서 바람 정보 추출
120
+ */
121
+ prepareWindData() {
122
+ this.weatherPoints = [];
123
+ if (!this.weatherData || this.weatherData.length === 0) {
124
+ console.warn('날씨 데이터가 없어 목업 데이터를 생성합니다.');
125
+ this.createMockWindData();
126
+ return;
127
+ }
128
+ console.log(`${this.weatherData.length}개의 날씨 데이터에서 바람 정보 추출 중...`);
129
+ // 유효한 바람 데이터만 추출
130
+ this.weatherData.forEach(data => {
131
+ if (data.wind &&
132
+ data.location &&
133
+ typeof data.wind.speed === 'number' &&
134
+ typeof data.wind.direction === 'number' &&
135
+ !isNaN(data.wind.speed) &&
136
+ !isNaN(data.wind.direction) &&
137
+ typeof data.location.longitude === 'number' &&
138
+ typeof data.location.latitude === 'number') {
139
+ // 각도를 라디안으로 변환 (바람 방향은 북쪽이 0도, 시계방향으로 증가)
140
+ const dirRad = (data.wind.direction * Math.PI) / 180;
141
+ // u, v 성분 계산 (동서/남북 벡터)
142
+ // 참고: 바람 벡터는 바람이 불어오는 방향이므로 변환 필요
143
+ const u = -Math.sin(dirRad) * data.wind.speed;
144
+ const v = -Math.cos(dirRad) * data.wind.speed;
145
+ this.weatherPoints.push({
146
+ lon: data.location.longitude,
147
+ lat: data.location.latitude,
148
+ u: u,
149
+ v: v,
150
+ speed: data.wind.speed
151
+ });
152
+ }
153
+ });
154
+ console.log(`바람 데이터 추출 완료: ${this.weatherPoints.length}개 포인트`);
155
+ // 데이터가 없으면 목업 데이터 생성
156
+ if (this.weatherPoints.length === 0) {
157
+ console.warn('유효한 바람 데이터가 없어 목업 데이터를 생성합니다.');
158
+ this.createMockWindData();
159
+ }
160
+ }
161
+ /**
162
+ * 목업 바람 데이터 생성
163
+ */
164
+ createMockWindData() {
165
+ console.log('목업 바람 데이터 생성 중...');
166
+ this.weatherPoints = [];
167
+ // 한국 영역에 그리드 형태로 바람 데이터 생성
168
+ const lonMin = 124.0;
169
+ const lonMax = 132.0;
170
+ const latMin = 33.0;
171
+ const latMax = 38.0;
172
+ const cols = 15;
173
+ const rows = 15;
174
+ for (let i = 0; i <= cols; i++) {
175
+ for (let j = 0; j <= rows; j++) {
176
+ const lon = lonMin + (i / cols) * (lonMax - lonMin);
177
+ const lat = latMin + (j / rows) * (latMax - latMin);
178
+ // 위치에 따라 달라지는 패턴 생성
179
+ const patternX = Math.sin(lon * 0.5) * 2;
180
+ const patternY = Math.cos(lat * 0.5) * 2;
181
+ // 바람 속도와 방향 계산 (목업 데이터)
182
+ const speed = 5 + 5 * Math.sin((lon + lat) * 0.2);
183
+ // u, v 벡터 성분 (동서/남북)
184
+ const u = (patternX * speed) / 5;
185
+ const v = (patternY * speed) / 5;
186
+ this.weatherPoints.push({
187
+ lon,
188
+ lat,
189
+ u,
190
+ v,
191
+ speed
192
+ });
193
+ }
194
+ }
195
+ console.log(`목업 바람 데이터 생성 완료: ${this.weatherPoints.length}개 포인트`);
196
+ }
197
+ /**
198
+ * 타일에 바람 데이터 생성
199
+ */
200
+ createWindTile(z, x, y) {
201
+ // 타일 범위 계산
202
+ const projection = getProjection('EPSG:3857');
203
+ if (!projection) {
204
+ console.error('EPSG:3857 투영 생성 실패');
205
+ return new Float32Array(this.tileSize * this.tileSize * 4);
206
+ }
207
+ const tileCoord = wrapX(this.tileGrid, [z, x, y], projection);
208
+ const extent = this.tileGrid.getTileCoordExtent(tileCoord);
209
+ const resolution = this.tileGrid.getResolution(z);
210
+ // 타일 데이터 생성 (RGBA 형식: u, v, speed, 0)
211
+ const data = new Float32Array(this.tileSize * this.tileSize * 4);
212
+ // 데이터가 없을 경우
213
+ if (this.weatherPoints.length === 0) {
214
+ // 빈 데이터 반환 (모든 값 0)
215
+ return data;
216
+ }
217
+ // 타일의 각 픽셀에 대해 바람 벡터 계산
218
+ for (let row = 0; row < this.tileSize; ++row) {
219
+ for (let col = 0; col < this.tileSize; ++col) {
220
+ // 픽셀 위치를 지리 좌표로 변환
221
+ const mapX = extent[0] + col * resolution;
222
+ const mapY = extent[3] - row * resolution;
223
+ // 메르카토르 좌표를 경위도로 변환
224
+ const lonlat = transform([mapX, mapY], 'EPSG:3857', 'EPSG:4326');
225
+ const lon = lonlat[0];
226
+ const lat = lonlat[1];
227
+ // 주변 바람 데이터를 기반으로 보간
228
+ const vector = this.interpolateWindVector(lon, lat);
229
+ // 벡터 크기 조정 (너무 작은 값은 시각적으로 보이지 않음)
230
+ const normalizedSpeed = vector.speed / this.maxSpeed;
231
+ let adjustedU = vector.u;
232
+ let adjustedV = vector.v;
233
+ // 너무 작은 벡터는 약간 증폭
234
+ if (normalizedSpeed > 0 && normalizedSpeed < 0.1) {
235
+ const amplification = 0.1 / normalizedSpeed;
236
+ adjustedU *= Math.min(amplification, 2);
237
+ adjustedV *= Math.min(amplification, 2);
238
+ }
239
+ // 데이터 저장 (RGBA 형식)
240
+ const offset = (row * this.tileSize + col) * 4;
241
+ data[offset] = adjustedU; // R = u (동서 방향)
242
+ data[offset + 1] = adjustedV; // G = v (남북 방향)
243
+ data[offset + 2] = vector.speed; // B = 풍속
244
+ data[offset + 3] = 1; // A = 사용 (중요: 값을 1로 설정)
245
+ }
246
+ }
247
+ return data;
248
+ }
249
+ /**
250
+ * 주어진 위치의 바람 벡터를 보간하여 반환
251
+ */
252
+ interpolateWindVector(lon, lat) {
253
+ if (this.weatherPoints.length === 0) {
254
+ return { u: 0, v: 0, speed: 0 };
255
+ }
256
+ // 역거리 가중치 방식(IDW) 보간 사용
257
+ let totalWeight = 0;
258
+ let weightedU = 0;
259
+ let weightedV = 0;
260
+ // 주변 포인트를 찾아 가중 평균 계산
261
+ this.weatherPoints.forEach(point => {
262
+ // 거리 계산 (단순화된 버전)
263
+ const dx = lon - point.lon;
264
+ const dy = lat - point.lat;
265
+ const dist = Math.sqrt(dx * dx + dy * dy);
266
+ // 가중치 계산 (거리 제곱의 역수)
267
+ const weight = 1 / Math.max(0.0001, dist * dist);
268
+ totalWeight += weight;
269
+ weightedU += point.u * weight;
270
+ weightedV += point.v * weight;
271
+ });
272
+ if (totalWeight === 0) {
273
+ return { u: 0, v: 0, speed: 0 };
274
+ }
275
+ // 가중 평균
276
+ const u = weightedU / totalWeight;
277
+ const v = weightedV / totalWeight;
278
+ const speed = Math.sqrt(u * u + v * v);
279
+ return { u, v, speed };
280
+ }
281
+ /**
282
+ * 레이어 가시성 설정
283
+ */
284
+ setVisible(visible) {
285
+ if (this.windLayer) {
286
+ this.windLayer.setVisible(visible);
287
+ }
288
+ }
289
+ /**
290
+ * 레이어 제거
291
+ */
292
+ removeLayer() {
293
+ if (this.map && this.windLayer) {
294
+ this.map.removeLayer(this.windLayer);
295
+ this.windLayer = undefined;
296
+ }
297
+ }
298
+ /**
299
+ * 레이어 업데이트
300
+ */
301
+ updateLayer() {
302
+ if (!this.map || !this.windLayer) {
303
+ return;
304
+ }
305
+ this.prepareWindData();
306
+ if (this.windSource) {
307
+ this.windSource.refresh();
308
+ }
309
+ }
310
+ }
311
+ //# sourceMappingURL=wind-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wind-layer.js","sourceRoot":"","sources":["../../../src/layers/wind-layer.ts"],"names":[],"mappings":"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","sourcesContent":["import { Map } from 'ol'\nimport BaseLayer from 'ol/layer/Base'\nimport { WeatherData, WeatherLayerType } from '../types'\nimport { BaseWeatherLayer } from './base-weather-layer'\nimport { fromLonLat, transform, transformExtent } from 'ol/proj'\nimport Flow from 'ol/layer/Flow'\nimport DataTileSource from 'ol/source/DataTile'\nimport { createEmpty, extend, getWidth, getHeight } from 'ol/extent'\nimport { getVectorContext } from 'ol/render'\nimport { Options as FlowOptions } from 'ol/layer/Flow'\nimport VectorSource from 'ol/source/Vector'\nimport WebGLVectorLayer from 'ol/layer/WebGLVector'\nimport GeoJSON from 'ol/format/GeoJSON'\nimport { get as getProjection } from 'ol/proj'\nimport { createXYZ, wrapX } from 'ol/tilegrid'\n\n/**\n * 바람 벡터 데이터 포인트 인터페이스\n */\ninterface WindPoint {\n x: number\n y: number\n u: number\n v: number\n speed: number\n direction?: number\n}\n\n/**\n * 바람 레이어 클래스\n * OpenLayers Flow 레이어를 사용하여 바람 흐름을 표현합니다.\n * https://github.com/openlayers/openlayers/blob/v10.4.0/examples/wind.js 참고\n */\nexport class WindLayer extends BaseWeatherLayer {\n private windLayer?: BaseLayer\n private windSource?: DataTileSource\n protected layerType = 'wind'\n\n // 바람 속도 범위 설정\n private maxSpeed = 15 // 최대 바람 속도 (m/s)\n private minSpeed = 0 // 최소 바람 속도\n\n // 타일 생성 관련 설정\n private tileGrid = createXYZ()\n private tileSize = 256\n private weatherPoints: Array<{\n lon: number\n lat: number\n u: number\n v: number\n speed: number\n }> = []\n\n /**\n * 생성자\n */\n constructor(options: any = {}) {\n super()\n\n // 옵션 설정\n this.maxSpeed = options.maxSpeed || 15\n }\n\n /**\n * 레이어 타입 반환\n */\n getType(): WeatherLayerType {\n return 'wind' as WeatherLayerType\n }\n\n /**\n * 레이어 생성\n * OpenLayers Flow 레이어를 사용하여 바람 흐름을 표현합니다.\n * OpenLayers 공식 예제 참고: https://github.com/openlayers/openlayers/blob/v10.4.0/examples/wind.js\n */\n createLayer(): BaseLayer {\n console.log('바람 레이어 생성 중...')\n\n // 이미 생성된 레이어가 있다면 반환\n if (this.windLayer) {\n return this.windLayer\n }\n\n // 날씨 데이터를 풍속 벡터로 변환 (바람 데이터 준비)\n this.prepareWindData()\n\n try {\n // 바람 데이터 소스 생성\n this.windSource = new DataTileSource({\n tileGrid: this.tileGrid,\n // transition must be 0, see https://github.com/openlayers/openlayers/issues/16119\n transition: 0,\n wrapX: true,\n loader: (z, x, y) => {\n return this.createWindTile(z, x, y)\n }\n })\n\n // 실제 바람 데이터의 속도 범위 확인\n let minSpeed = Infinity\n let maxSpeed = 0\n let totalSpeed = 0\n\n this.weatherPoints.forEach(point => {\n minSpeed = Math.min(minSpeed, point.speed)\n maxSpeed = Math.max(maxSpeed, point.speed)\n totalSpeed += point.speed\n })\n\n const avgSpeed = totalSpeed / this.weatherPoints.length\n console.log(`바람 속도 범위: ${minSpeed.toFixed(2)}~${maxSpeed.toFixed(2)} m/s, 평균: ${avgSpeed.toFixed(2)} m/s`)\n\n // 효과적인 최대 속도 설정 (데이터 기반)\n const effectiveMaxSpeed = maxSpeed > 0 ? Math.max(10, maxSpeed) : 10\n this.maxSpeed = effectiveMaxSpeed\n\n console.log(`Flow 레이어 최대 속도: ${effectiveMaxSpeed.toFixed(2)} m/s`)\n\n // Flow 레이어 옵션 설정 - 단일 스타일 사용\n const flowOptions: any = {\n source: this.windSource,\n zIndex: 10,\n className: 'wind-layer',\n maxSpeed: this.maxSpeed,\n // 파티클 세부 설정 최적화\n tracksPerPixel: 0.5, // 트랙 밀도 증가 (더 많은 입자)\n fadeTrails: true, // 트레일 페이드 효과\n trailLength: 0.45, // 중간 길이 트레일\n // 단일 색상 스타일 - 주의: 복잡한 표현식 사용하지 않음\n style: {\n color: 'rgba(0, 191, 255, 0.8)', // 하늘색 (DeepSkyBlue)\n width: 1.5\n }\n }\n\n // @ts-ignore - Flow 레이어는 TypeScript 정의가 불완전할 수 있음\n this.windLayer = new Flow(flowOptions)\n console.log('Flow 레이어 생성 완료')\n return this.windLayer\n } catch (error) {\n console.error('Flow 레이어 생성 중 오류:', error)\n\n // 오류 발생 시 완전히 단순화된 옵션으로 다시 시도\n try {\n console.log('완전히 단순화된 옵션으로 다시 시도합니다...')\n const verySimpleOptions: any = {\n source: this.windSource,\n zIndex: 10,\n className: 'wind-layer',\n maxSpeed: this.maxSpeed\n }\n\n // @ts-ignore\n this.windLayer = new Flow(verySimpleOptions)\n console.log('단순 Flow 레이어 생성 완료')\n return this.windLayer\n } catch (fallbackError) {\n console.error('단순 Flow 레이어 생성도 실패:', fallbackError)\n return new BaseLayer({})\n }\n }\n }\n\n /**\n * 날씨 데이터에서 바람 정보 추출\n */\n private prepareWindData(): void {\n this.weatherPoints = []\n\n if (!this.weatherData || this.weatherData.length === 0) {\n console.warn('날씨 데이터가 없어 목업 데이터를 생성합니다.')\n this.createMockWindData()\n return\n }\n\n console.log(`${this.weatherData.length}개의 날씨 데이터에서 바람 정보 추출 중...`)\n\n // 유효한 바람 데이터만 추출\n this.weatherData.forEach(data => {\n if (\n data.wind &&\n data.location &&\n typeof data.wind.speed === 'number' &&\n typeof data.wind.direction === 'number' &&\n !isNaN(data.wind.speed) &&\n !isNaN(data.wind.direction) &&\n typeof data.location.longitude === 'number' &&\n typeof data.location.latitude === 'number'\n ) {\n // 각도를 라디안으로 변환 (바람 방향은 북쪽이 0도, 시계방향으로 증가)\n const dirRad = (data.wind.direction * Math.PI) / 180\n\n // u, v 성분 계산 (동서/남북 벡터)\n // 참고: 바람 벡터는 바람이 불어오는 방향이므로 변환 필요\n const u = -Math.sin(dirRad) * data.wind.speed\n const v = -Math.cos(dirRad) * data.wind.speed\n\n this.weatherPoints.push({\n lon: data.location.longitude,\n lat: data.location.latitude,\n u: u,\n v: v,\n speed: data.wind.speed\n })\n }\n })\n\n console.log(`바람 데이터 추출 완료: ${this.weatherPoints.length}개 포인트`)\n\n // 데이터가 없으면 목업 데이터 생성\n if (this.weatherPoints.length === 0) {\n console.warn('유효한 바람 데이터가 없어 목업 데이터를 생성합니다.')\n this.createMockWindData()\n }\n }\n\n /**\n * 목업 바람 데이터 생성\n */\n private createMockWindData(): void {\n console.log('목업 바람 데이터 생성 중...')\n this.weatherPoints = []\n\n // 한국 영역에 그리드 형태로 바람 데이터 생성\n const lonMin = 124.0\n const lonMax = 132.0\n const latMin = 33.0\n const latMax = 38.0\n\n const cols = 15\n const rows = 15\n\n for (let i = 0; i <= cols; i++) {\n for (let j = 0; j <= rows; j++) {\n const lon = lonMin + (i / cols) * (lonMax - lonMin)\n const lat = latMin + (j / rows) * (latMax - latMin)\n\n // 위치에 따라 달라지는 패턴 생성\n const patternX = Math.sin(lon * 0.5) * 2\n const patternY = Math.cos(lat * 0.5) * 2\n\n // 바람 속도와 방향 계산 (목업 데이터)\n const speed = 5 + 5 * Math.sin((lon + lat) * 0.2)\n\n // u, v 벡터 성분 (동서/남북)\n const u = (patternX * speed) / 5\n const v = (patternY * speed) / 5\n\n this.weatherPoints.push({\n lon,\n lat,\n u,\n v,\n speed\n })\n }\n }\n\n console.log(`목업 바람 데이터 생성 완료: ${this.weatherPoints.length}개 포인트`)\n }\n\n /**\n * 타일에 바람 데이터 생성\n */\n private createWindTile(z: number, x: number, y: number): Float32Array {\n // 타일 범위 계산\n const projection = getProjection('EPSG:3857')\n if (!projection) {\n console.error('EPSG:3857 투영 생성 실패')\n return new Float32Array(this.tileSize * this.tileSize * 4)\n }\n\n const tileCoord = wrapX(this.tileGrid, [z, x, y], projection)\n const extent = this.tileGrid.getTileCoordExtent(tileCoord)\n const resolution = this.tileGrid.getResolution(z as number)\n\n // 타일 데이터 생성 (RGBA 형식: u, v, speed, 0)\n const data = new Float32Array(this.tileSize * this.tileSize * 4)\n\n // 데이터가 없을 경우\n if (this.weatherPoints.length === 0) {\n // 빈 데이터 반환 (모든 값 0)\n return data\n }\n\n // 타일의 각 픽셀에 대해 바람 벡터 계산\n for (let row = 0; row < this.tileSize; ++row) {\n for (let col = 0; col < this.tileSize; ++col) {\n // 픽셀 위치를 지리 좌표로 변환\n const mapX = extent[0] + col * resolution\n const mapY = extent[3] - row * resolution\n\n // 메르카토르 좌표를 경위도로 변환\n const lonlat = transform([mapX, mapY], 'EPSG:3857', 'EPSG:4326')\n const lon = lonlat[0]\n const lat = lonlat[1]\n\n // 주변 바람 데이터를 기반으로 보간\n const vector = this.interpolateWindVector(lon, lat)\n\n // 벡터 크기 조정 (너무 작은 값은 시각적으로 보이지 않음)\n const normalizedSpeed = vector.speed / this.maxSpeed\n let adjustedU = vector.u\n let adjustedV = vector.v\n\n // 너무 작은 벡터는 약간 증폭\n if (normalizedSpeed > 0 && normalizedSpeed < 0.1) {\n const amplification = 0.1 / normalizedSpeed\n adjustedU *= Math.min(amplification, 2)\n adjustedV *= Math.min(amplification, 2)\n }\n\n // 데이터 저장 (RGBA 형식)\n const offset = (row * this.tileSize + col) * 4\n data[offset] = adjustedU // R = u (동서 방향)\n data[offset + 1] = adjustedV // G = v (남북 방향)\n data[offset + 2] = vector.speed // B = 풍속\n data[offset + 3] = 1 // A = 사용 (중요: 값을 1로 설정)\n }\n }\n\n return data\n }\n\n /**\n * 주어진 위치의 바람 벡터를 보간하여 반환\n */\n private interpolateWindVector(lon: number, lat: number): { u: number; v: number; speed: number } {\n if (this.weatherPoints.length === 0) {\n return { u: 0, v: 0, speed: 0 }\n }\n\n // 역거리 가중치 방식(IDW) 보간 사용\n let totalWeight = 0\n let weightedU = 0\n let weightedV = 0\n\n // 주변 포인트를 찾아 가중 평균 계산\n this.weatherPoints.forEach(point => {\n // 거리 계산 (단순화된 버전)\n const dx = lon - point.lon\n const dy = lat - point.lat\n const dist = Math.sqrt(dx * dx + dy * dy)\n\n // 가중치 계산 (거리 제곱의 역수)\n const weight = 1 / Math.max(0.0001, dist * dist)\n\n totalWeight += weight\n weightedU += point.u * weight\n weightedV += point.v * weight\n })\n\n if (totalWeight === 0) {\n return { u: 0, v: 0, speed: 0 }\n }\n\n // 가중 평균\n const u = weightedU / totalWeight\n const v = weightedV / totalWeight\n const speed = Math.sqrt(u * u + v * v)\n\n return { u, v, speed }\n }\n\n /**\n * 레이어 가시성 설정\n */\n setVisible(visible: boolean): void {\n if (this.windLayer) {\n this.windLayer.setVisible(visible)\n }\n }\n\n /**\n * 레이어 제거\n */\n public removeLayer(): void {\n if (this.map && this.windLayer) {\n this.map.removeLayer(this.windLayer)\n this.windLayer = undefined\n }\n }\n\n /**\n * 레이어 업데이트\n */\n protected updateLayer() {\n if (!this.map || !this.windLayer) {\n return\n }\n\n this.prepareWindData()\n\n if (this.windSource) {\n this.windSource.refresh()\n }\n }\n}\n"]}
@@ -0,0 +1,112 @@
1
+ import { LitElement } from 'lit';
2
+ import { WeatherLayerType } from './types.js';
3
+ /**
4
+ * 날씨 시각화 웹 컴포넌트
5
+ */
6
+ export declare class OxWeather extends LitElement {
7
+ /**
8
+ * 지도 객체
9
+ */
10
+ private map?;
11
+ /**
12
+ * 지도가 초기화되었는지 여부
13
+ */
14
+ private mapInitialized;
15
+ /**
16
+ * 날씨 데이터 서비스
17
+ */
18
+ private weatherService;
19
+ /**
20
+ * 날씨 데이터
21
+ */
22
+ private weatherData;
23
+ /**
24
+ * 날씨 레이어 목록
25
+ */
26
+ private weatherLayers;
27
+ /**
28
+ * 위도 (지도 중심점)
29
+ */
30
+ latitude: number;
31
+ /**
32
+ * 경도 (지도 중심점)
33
+ */
34
+ longitude: number;
35
+ /**
36
+ * 줌 레벨
37
+ */
38
+ zoom: number;
39
+ /**
40
+ * 활성 레이어 목록
41
+ * - 'wind': 바람 레이어
42
+ * - 'precipitation': 강수량 레이어
43
+ * - 'temperature': 온도 레이어
44
+ * - 'solar': 일사량 레이어
45
+ */
46
+ activeLayers: WeatherLayerType[];
47
+ /**
48
+ * 날씨 데이터 업데이트 쓰로틀링 관련 변수
49
+ */
50
+ private lastUpdateTime;
51
+ private updateThrottleDelay;
52
+ private updatePending;
53
+ isLoading: boolean;
54
+ /**
55
+ * 컴포넌트 스타일
56
+ */
57
+ static styles: import("lit").CSSResult;
58
+ /**
59
+ * 컴포넌트 초기화
60
+ */
61
+ connectedCallback(): void;
62
+ /**
63
+ * 첫 번째 업데이트 후 호출 (DOM 접근 가능)
64
+ */
65
+ firstUpdated(): void;
66
+ /**
67
+ * 지도를 초기화합니다.
68
+ */
69
+ private initMap;
70
+ /**
71
+ * 초기 날씨 데이터를 가져옵니다.
72
+ */
73
+ private initWeatherData;
74
+ /**
75
+ * 날씨 데이터를 가져옵니다.
76
+ */
77
+ private fetchWeatherData;
78
+ /**
79
+ * 날씨 데이터를 업데이트합니다
80
+ */
81
+ private updateWeatherData;
82
+ /**
83
+ * 날씨 데이터와 활성 레이어가 변경될 때 레이어 업데이트
84
+ */
85
+ updated(changedProperties: any): void;
86
+ /**
87
+ * 날씨 레이어를 비동기적으로 업데이트합니다.
88
+ */
89
+ private updateLayersAsync;
90
+ /**
91
+ * 날씨 레이어를 업데이트합니다.
92
+ */
93
+ private updateLayers;
94
+ /**
95
+ * 레이어가 이미 맵에 있는지 확인합니다.
96
+ * 심층 비교를 통해 정확하게 체크합니다.
97
+ */
98
+ private isLayerInMap;
99
+ /**
100
+ * 바람 레이어 토글 처리 (특별 관리)
101
+ */
102
+ private handleWindLayerToggle;
103
+ /**
104
+ * 바람 레이어 업데이트 (최적화)
105
+ */
106
+ private updateWindLayer;
107
+ /**
108
+ * 레이어 토글 처리
109
+ */
110
+ private handleLayerToggle;
111
+ render(): import("lit-html").TemplateResult<1>;
112
+ }