@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.
- package/client/ol-components/wind-data-tile-layer.ts +485 -0
- package/client/ol-components/wind-rasterized-layer.ts +437 -0
- package/client/pages/weather-page.ts +149 -144
- package/dist-client/ol-components/wind-data-tile-layer.d.ts +59 -0
- package/dist-client/ol-components/wind-data-tile-layer.js +414 -0
- package/dist-client/ol-components/wind-data-tile-layer.js.map +1 -0
- package/dist-client/ol-components/wind-rasterized-layer.d.ts +59 -0
- package/dist-client/ol-components/wind-rasterized-layer.js +363 -0
- package/dist-client/ol-components/wind-rasterized-layer.js.map +1 -0
- package/dist-client/pages/weather-page.d.ts +7 -2
- package/dist-client/pages/weather-page.js +145 -125
- package/dist-client/pages/weather-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/client/ol-components/webgl-image-static.ts +0 -244
- package/client/ol-components/wind.ts +0 -120
|
@@ -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
|
|
18
|
-
import
|
|
12
|
+
import VectorTileLayer from 'ol/layer/VectorTile'
|
|
13
|
+
import { Style, Stroke } from 'ol/style'
|
|
19
14
|
|
|
20
|
-
import
|
|
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
|
|
26
|
-
import VectorTileLayer from 'ol/layer/VectorTile'
|
|
17
|
+
import { WindDataTileLayer } from '../ol-components/wind-data-tile-layer.js'
|
|
27
18
|
|
|
28
|
-
|
|
29
|
-
|
|
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:
|
|
107
|
-
@state() private weatherLayer:
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
253
|
-
layers: [lightOSM,
|
|
196
|
+
pixelRatio: Math.min(window.devicePixelRatio, 2),
|
|
197
|
+
layers: [lightOSM, outlineLayer, labelOSM],
|
|
254
198
|
view: new View({
|
|
255
|
-
center: fromLonLat([
|
|
256
|
-
zoom:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
blendMode = 'screen'
|
|
215
|
+
this.weatherLayer = new WindDataTileLayer()
|
|
311
216
|
break
|
|
312
217
|
case 'precipitation':
|
|
313
|
-
|
|
314
|
-
blendMode = 'color-dodge'
|
|
218
|
+
this.weatherLayer = new PrecipitationLayer()
|
|
315
219
|
break
|
|
316
220
|
default:
|
|
317
|
-
|
|
318
|
-
blendMode = 'normal'
|
|
221
|
+
this.weatherLayer = new TemperatureLayer()
|
|
319
222
|
}
|
|
320
223
|
|
|
321
|
-
this.weatherLayer
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|