@things-factory/weather 9.0.0-beta.55 → 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.
@@ -8,29 +8,16 @@ import TileLayer from 'ol/layer/Tile.js'
8
8
  import { fromLonLat } from 'ol/proj.js'
9
9
  import 'ol/ol.css'
10
10
  import { MVT } from 'ol/format'
11
- import { DEVICE_PIXEL_RATIO } from 'ol/has.js'
12
- import Flow from 'ol/layer/Flow.js'
13
-
14
- import ImageLayer from 'ol/layer/Image'
15
- import WebGLImageStatic from '../ol-components/webgl-image-static'
16
11
  import { VectorTile, XYZ } from 'ol/source'
17
- import VectorSource from 'ol/source/Vector'
18
- import colormap from 'colormap'
12
+ import VectorTileLayer from 'ol/layer/VectorTile'
13
+ import { Style, Stroke } from 'ol/style'
19
14
 
20
- import Heatmap from 'ol/layer/Heatmap.js'
21
- import Feature from 'ol/Feature.js'
22
- import Point from 'ol/geom/Point.js'
23
- import { Stroke, Style } from 'ol/style.js'
15
+ import { BaseWeatherLayer, TemperatureLayer, PrecipitationLayer, initializeWorkerSystem } from '@hyperviz/weather'
24
16
 
25
- import wind from '../ol-components/wind.js'
26
- import VectorTileLayer from 'ol/layer/VectorTile'
17
+ import { WindDataTileLayer } from '../ol-components/wind-data-tile-layer.js'
27
18
 
28
- const colors = colormap({
29
- colormap: 'jet',
30
- nshades: 10,
31
- alpha: 0.3,
32
- format: 'rgba'
33
- })
19
+ // 통합 날씨 레이어 타입 (BaseWeatherLayer와 WindDataTileLayer 모두 포함)
20
+ type WeatherLayerType = BaseWeatherLayer | WindDataTileLayer
34
21
 
35
22
  @customElement('weather-page')
36
23
  export class WeatherPage extends PageView {
@@ -103,9 +90,8 @@ export class WeatherPage extends PageView {
103
90
  }
104
91
 
105
92
  @property({ type: String }) private activeType = 'temperature'
106
- @state() private map: any
107
- @state() private weatherLayer: any
108
- @state() private windLayer: any
93
+ @state() private map: Map | null = null
94
+ @state() private weatherLayer: WeatherLayerType | null = null
109
95
 
110
96
  constructor() {
111
97
  super()
@@ -123,12 +109,6 @@ export class WeatherPage extends PageView {
123
109
  >
124
110
  온도
125
111
  </button>
126
- <button
127
- class="weather-btn ${this.activeType === 'humidity' ? 'active' : ''}"
128
- @click=${() => this._changeWeatherType('humidity')}
129
- >
130
- 습도
131
- </button>
132
112
  <button
133
113
  class="weather-btn ${this.activeType === 'wind' ? 'active' : ''}"
134
114
  @click=${() => this._changeWeatherType('wind')}
@@ -149,7 +129,7 @@ export class WeatherPage extends PageView {
149
129
 
150
130
  connectedCallback(): void {
151
131
  super.connectedCallback()
152
-
132
+ initializeWorkerSystem()
153
133
  requestAnimationFrame(() => {
154
134
  this._initMap()
155
135
  })
@@ -157,11 +137,17 @@ export class WeatherPage extends PageView {
157
137
 
158
138
  disconnectedCallback(): void {
159
139
  super.disconnectedCallback()
160
-
161
- this.map.removeLayer(this.weatherLayer)
162
- this.weatherLayer.dispose()
163
- this.map.dispose()
164
-
140
+ if (this.weatherLayer) {
141
+ try {
142
+ this.weatherLayer.removeFromMap()
143
+ this.weatherLayer.dispose()
144
+ } catch (e) {
145
+ console.error('Error disposing weather layer:', e)
146
+ }
147
+ }
148
+ if (this.map) {
149
+ this.map.dispose()
150
+ }
165
151
  this.map = null
166
152
  this.weatherLayer = null
167
153
  }
@@ -169,13 +155,7 @@ export class WeatherPage extends PageView {
169
155
  private _initMap() {
170
156
  const lightOSM = new TileLayer({
171
157
  source: new XYZ({
172
- // url: 'https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png',
173
- // url: 'https://{a-c}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png',
174
158
  url: 'https://{a-c}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
175
- // url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
176
- // url: 'https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
177
- // url: 'https://api.maptiler.com/maps/toner-lines/{z}/{x}/{y}.png?key=uHikv5CybbRm7cDLjRca'
178
- // attributions: '© MapTiler'
179
159
  attributions: '© OpenStreetMap contributors, © CartoDB'
180
160
  }),
181
161
  background: 'transparent',
@@ -185,7 +165,6 @@ export class WeatherPage extends PageView {
185
165
  const labelOSM = new TileLayer({
186
166
  source: new XYZ({
187
167
  url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'
188
- // url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'
189
168
  }),
190
169
  background: 'transparent',
191
170
  opacity: 1
@@ -212,137 +191,163 @@ export class WeatherPage extends PageView {
212
191
  })
213
192
  })
214
193
 
215
- console.log('🛠 벡터 타일 레이어:', outlineLayer, outlineLayer.getSource())
216
-
217
- /* heatmap */
218
-
219
- const generateHeatmapData = () => {
220
- const features: Feature[] = []
221
- for (let i = 0; i < 20000; i++) {
222
- // 더 많은 데이터 포인트 생성
223
- const lon = Math.random() * 360 - 180 // 전 세계 경도 (-180 ~ 180)
224
- const lat = Math.random() * 170 - 85 // 전 세계 위도 (-85 ~ 85)
225
-
226
- const weight = Math.random() * 1 // 가중치 (0 ~ 2)
227
-
228
- const feature = new Feature({
229
- geometry: new Point(fromLonLat([lon, lat])),
230
- weight: weight
231
- })
232
- features.push(feature)
233
- }
234
- return features
235
- }
236
-
237
- const vectorSource = new VectorSource({
238
- features: generateHeatmapData()
239
- })
240
-
241
- const heatmapLayer = new Heatmap({
242
- source: vectorSource,
243
- blur: 60, // 흐림 정도 증가
244
- radius: 60, // 점의 반경 증가
245
- weight: feature => feature.get('weight'), // 가중치 적용
246
- gradient: colors.map(c => `rgba(${c.join(',')})`), // 색상 적용
247
- opacity: 0.8
248
- })
249
-
250
194
  this.map = new Map({
251
195
  target: this.renderRoot.querySelector('#map') as HTMLElement,
252
- pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),
253
- layers: [lightOSM, /*heatmapLayer, */ outlineLayer, labelOSM],
196
+ pixelRatio: Math.min(window.devicePixelRatio, 2),
197
+ layers: [lightOSM, outlineLayer, labelOSM],
254
198
  view: new View({
255
- center: fromLonLat([127.5, 36.5]),
256
- zoom: 7.5
199
+ center: fromLonLat([126.9779692, 37.5662952]),
200
+ zoom: 5
257
201
  })
258
202
  })
259
203
 
260
204
  this._addWeatherLayer()
261
- // this._addWindLayer()
262
- }
263
-
264
- private _addWindLayer() {
265
- if (this.windLayer) {
266
- this.map.removeLayer(this.windLayer)
267
- console.log('🚨 기존 날씨 레이어 제거')
268
- }
269
-
270
- const maxSpeed = 20
271
-
272
- const colorStops: (number | [number, number, number, number])[] = []
273
- for (let i = 0; i < colors.length; ++i) {
274
- colorStops.push((i * maxSpeed) / (colors.length - 1))
275
- colorStops.push(colors[i])
276
- }
277
-
278
- this.windLayer = new Flow({
279
- source: wind,
280
- maxSpeed,
281
- particles: 20000,
282
- style: {
283
- color: ['interpolate', ['linear'], ['get', 'speed'], ...colorStops]
284
- }
285
- })
286
-
287
- this.map.addLayer(this.windLayer)
288
205
  }
289
206
 
290
207
  private _addWeatherLayer() {
291
- if (this.weatherLayer) {
292
- this.map.removeLayer(this.weatherLayer)
293
- console.log('🚨 기존 날씨 레이어 제거')
294
- }
295
-
296
- // 날씨 타입별 최적 설정
297
- let opacity, blendMode
208
+ if (!this.map) return
298
209
 
299
210
  switch (this.activeType) {
300
211
  case 'temperature':
301
- opacity = 0.75
302
- blendMode = 'normal'
303
- break
304
- case 'humidity':
305
- opacity = 0.65
306
- blendMode = 'multiply'
212
+ this.weatherLayer = new TemperatureLayer()
307
213
  break
308
214
  case 'wind':
309
- opacity = 0.8
310
- blendMode = 'screen'
215
+ this.weatherLayer = new WindDataTileLayer()
311
216
  break
312
217
  case 'precipitation':
313
- opacity = 0.7
314
- blendMode = 'color-dodge'
218
+ this.weatherLayer = new PrecipitationLayer()
315
219
  break
316
220
  default:
317
- opacity = 0.7
318
- blendMode = 'normal'
221
+ this.weatherLayer = new TemperatureLayer()
319
222
  }
320
223
 
321
- this.weatherLayer = new ImageLayer({
322
- source: new WebGLImageStatic({
323
- url: `/weather/map/${this.activeType}.png?refresh=${Date.now()}`, // 캐시 방지
324
- // 올바른 지구 투영 비율 사용
325
- imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],
326
- interpolation: true,
327
- projection: 'EPSG:3857'
328
- // opacity: opacity,
329
- // blendMode: blendMode
330
- }),
331
- opacity // WebGLImageStatic에서 투명도 처리
332
- })
333
-
334
- this.map.getLayers().insertAt(1, this.weatherLayer)
335
-
336
- console.log('🚨 날씨 레이어 추가', this.map.getLayers())
224
+ if (this.weatherLayer) {
225
+ this.weatherLayer.addToMap(this.map)
226
+ }
337
227
  }
338
228
 
229
+ /**
230
+ * 날씨 레이어를 변경하고 새 레이어를 맵에 추가
231
+ */
339
232
  private _changeWeatherType(type: string) {
340
233
  if (this.activeType === type) return
234
+
235
+ // 현재 활성 타입 저장
236
+ const previousType = this.activeType
237
+ // 새로운 타입으로 변경
341
238
  this.activeType = type
342
- this._addWeatherLayer()
239
+
240
+ console.log(`날씨 타입 변경 시작: ${previousType} -> ${type}`)
241
+
242
+ // 이전 레이어가 있을 경우 처리
243
+ if (this.map && this.weatherLayer) {
244
+ // 맵 참조 저장
245
+ const mapRef = this.map
246
+
247
+ // 이전 레이어 참조 저장
248
+ const oldLayer = this.weatherLayer
249
+ // 렌더링 참조 제거
250
+ this.weatherLayer = null
251
+
252
+ // 이전 레이어 제거 처리
253
+ try {
254
+ console.log(`이전 레이어(${previousType}) 제거 시작`)
255
+
256
+ // 1. 레이어 시각적으로 즉시 숨김 (특히 WindDataTileLayer 경우 중요)
257
+ if (previousType === 'wind') {
258
+ if (oldLayer['layer'] && typeof oldLayer['layer'].setVisible === 'function') {
259
+ oldLayer['layer'].setVisible(false)
260
+ }
261
+ }
262
+
263
+ // 2. 컴포넌트에서 참조 제거 (렌더링 사이클에서 제외)
264
+ this.weatherLayer = null
265
+
266
+ // 3. 비동기 처리: 렌더링 사이클이 완료된 후 실행
267
+ requestAnimationFrame(() => {
268
+ setTimeout(() => {
269
+ try {
270
+ // 레이어 참조가 남아있을 경우에만 제거 시도
271
+ if (oldLayer) {
272
+ oldLayer.removeFromMap()
273
+
274
+ // 약간의 지연 후 자원 해제
275
+ setTimeout(() => {
276
+ try {
277
+ oldLayer.dispose()
278
+ console.log(`이전 레이어(${previousType}) 완전히 제거됨`)
279
+ } catch (e) {
280
+ console.warn('레이어 리소스 해제 중 오류:', e)
281
+ }
282
+
283
+ // 새 레이어 생성 시작
284
+ this._addNewLayer(type, mapRef)
285
+ }, 50) // dispose 지연
286
+ } else {
287
+ // 레이어 참조가 없는 경우 즉시 새 레이어 생성
288
+ this._addNewLayer(type, mapRef)
289
+ }
290
+ } catch (e) {
291
+ console.error('레이어 제거 중 오류 발생:', e)
292
+ // 오류 발생해도 새 레이어 생성 시도
293
+ this._addNewLayer(type, mapRef)
294
+ }
295
+ }, 100) // 레이어 제거 지연
296
+ })
297
+ } catch (e) {
298
+ console.error('레이어 전환 중 오류:', e)
299
+ // 에러 상태에서도 새 레이어 추가 시도
300
+ this._addNewLayer(type, mapRef)
301
+ }
302
+ } else {
303
+ // 기존 레이어가 없으면 바로 새 레이어 추가
304
+ this._addWeatherLayer()
305
+ }
306
+ }
307
+
308
+ /**
309
+ * 새 레이어를 안전하게 추가하는 헬퍼 메소드
310
+ */
311
+ private _addNewLayer(type: string, mapRef: Map | null): void {
312
+ // 타입이 바뀌지 않았는지 확인
313
+ if (this.activeType !== type) {
314
+ console.log(`타입이 변경되어 레이어 생성 취소: ${this.activeType}로 변경됨`)
315
+ return
316
+ }
317
+
318
+ console.log(`새 레이어(${type}) 생성 시작`)
319
+
320
+ try {
321
+ // 맵 참조 확인
322
+ if (!this.map && mapRef) {
323
+ this.map = mapRef
324
+ }
325
+
326
+ if (this.map) {
327
+ // 새 레이어 생성 및 맵에 추가
328
+ this._addWeatherLayer()
329
+ console.log(`새 레이어(${type}) 생성 완료`)
330
+ } else {
331
+ console.error('맵 참조 없음: 레이어를 추가할 수 없습니다')
332
+ }
333
+ } catch (e) {
334
+ console.error('새 레이어 생성 중 오류:', e)
335
+ }
343
336
  }
344
337
 
345
338
  private _refreshWeatherMap() {
346
- this._addWeatherLayer()
339
+ if (this.weatherLayer) {
340
+ try {
341
+ // 타입에 따라 적절한 메서드 호출
342
+ if (this.activeType === 'wind') {
343
+ ;(this.weatherLayer as WindDataTileLayer).updateLayer()
344
+ } else {
345
+ // @hyperviz/weather 레이어
346
+ ;(this.weatherLayer as BaseWeatherLayer).setWeatherData([])
347
+ }
348
+ } catch (e) {
349
+ console.error('Error refreshing weather layer:', e)
350
+ }
351
+ }
347
352
  }
348
353
  }
@@ -0,0 +1,59 @@
1
+ import BaseLayer from 'ol/layer/Base.js';
2
+ import Map from 'ol/Map.js';
3
+ import { WeatherLayerType } from '@hyperviz/weather';
4
+ export declare class WindDataTileLayer {
5
+ private source;
6
+ private layer;
7
+ private map;
8
+ private useEnhancedColors;
9
+ private __disposed;
10
+ /**
11
+ * 바람 데이터 타일 레이어 생성
12
+ * @param options 옵션 객체
13
+ */
14
+ constructor(options?: {
15
+ useEnhancedColors?: boolean;
16
+ });
17
+ /**
18
+ * 타일 데이터 로딩 함수
19
+ * @private
20
+ */
21
+ private _tileLoader;
22
+ /**
23
+ * 레이어 타입 반환 (호환성용)
24
+ */
25
+ getType(): WeatherLayerType;
26
+ /**
27
+ * 향상된 색상 매핑 사용 여부 설정
28
+ */
29
+ setUseEnhancedColors(use: boolean): void;
30
+ /**
31
+ * OpenLayers 레이어 객체 생성 또는 반환
32
+ */
33
+ createLayer(): BaseLayer;
34
+ /**
35
+ * 요청 중인 데이터 로드 중단 및 소스 무효화
36
+ * @private
37
+ */
38
+ private _clearSourceData;
39
+ /**
40
+ * 레이어를 맵에 추가
41
+ */
42
+ addToMap(map: Map): void;
43
+ /**
44
+ * 레이어를 맵에서 제거
45
+ */
46
+ removeFromMap(): void;
47
+ /**
48
+ * 레이어 관련 리소스 해제
49
+ */
50
+ dispose(): void;
51
+ /**
52
+ * 레이어 업데이트 요청 (데이터 변경 시)
53
+ */
54
+ updateLayer(): void;
55
+ /**
56
+ * 데이터 설정 메서드 (호환성용, 실제 데이터는 PNG 이미지에 고정됨)
57
+ */
58
+ setWeatherData(data: any): void;
59
+ }