@things-factory/weather 9.0.0-9.0.0-beta.59.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.
- package/CHANGELOG.md +8 -0
- package/assets/images/wind.png +0 -0
- package/assets/images/wind2.png +0 -0
- package/client/bootstrap.ts +1 -0
- package/client/index.ts +0 -0
- package/client/ol-components/webgl-canvas-layer.ts +413 -0
- 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 +353 -0
- package/client/route.ts +7 -0
- package/client/tsconfig.json +11 -0
- package/config/config.development.js +28 -0
- package/config/config.production.js +10 -0
- package/data/geo-json/countries-max-geo.json +1 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +2 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +0 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/ol-components/webgl-canvas-layer.d.ts +61 -0
- package/dist-client/ol-components/webgl-canvas-layer.js +364 -0
- package/dist-client/ol-components/webgl-canvas-layer.js.map +1 -0
- 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 +23 -0
- package/dist-client/pages/weather-page.js +348 -0
- package/dist-client/pages/weather-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +8 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/controllers/open-meteo-adapter.d.ts +4 -0
- package/dist-server/controllers/open-meteo-adapter.js +87 -0
- package/dist-server/controllers/open-meteo-adapter.js.map +1 -0
- package/dist-server/controllers/types.d.ts +55 -0
- package/dist-server/controllers/types.js +3 -0
- package/dist-server/controllers/types.js.map +1 -0
- package/dist-server/controllers/weather-controller.d.ts +12 -0
- package/dist-server/controllers/weather-controller.js +179 -0
- package/dist-server/controllers/weather-controller.js.map +1 -0
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +4 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/routers/geo-router.d.ts +59 -0
- package/dist-server/routers/geo-router.js +277 -0
- package/dist-server/routers/geo-router.js.map +1 -0
- package/dist-server/routers/weather-router.d.ts +1 -0
- package/dist-server/routers/weather-router.js +10 -0
- package/dist-server/routers/weather-router.js.map +1 -0
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +11 -0
- package/dist-server/routes.js.map +1 -0
- package/dist-server/services/chrome-renderer.d.ts +13 -0
- package/dist-server/services/chrome-renderer.js +162 -0
- package/dist-server/services/chrome-renderer.js.map +1 -0
- package/dist-server/services/headless-pool.d.ts +4 -0
- package/dist-server/services/headless-pool.js +89 -0
- package/dist-server/services/headless-pool.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/dist-server/utils/svg-utils.d.ts +35 -0
- package/dist-server/utils/svg-utils.js +89 -0
- package/dist-server/utils/svg-utils.js.map +1 -0
- package/dist-server/utils/tile-utils.d.ts +38 -0
- package/dist-server/utils/tile-utils.js +64 -0
- package/dist-server/utils/tile-utils.js.map +1 -0
- package/package.json +46 -0
- package/server/controllers/open-meteo-adapter.ts +96 -0
- package/server/controllers/types.ts +63 -0
- package/server/controllers/weather-controller.ts +192 -0
- package/server/index.ts +1 -0
- package/server/routers/geo-router.ts +297 -0
- package/server/routers/weather-router.ts +8 -0
- package/server/routes.ts +10 -0
- package/server/services/chrome-renderer.ts +206 -0
- package/server/services/data.json +8438 -0
- package/server/services/headless-pool.ts +97 -0
- package/server/tsconfig.json +10 -0
- package/server/utils/svg-utils.ts +108 -0
- package/server/utils/tile-utils.ts +77 -0
- package/things-factory.config.js +8 -0
- package/translations/en.json +1 -0
- package/translations/ja.json +1 -0
- package/translations/ko.json +1 -0
- package/translations/ms.json +1 -0
- package/translations/zh.json +1 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { css, html } from 'lit'
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
|
+
import { PageView } from '@operato/shell'
|
|
4
|
+
|
|
5
|
+
import Map from 'ol/Map.js'
|
|
6
|
+
import View from 'ol/View.js'
|
|
7
|
+
import TileLayer from 'ol/layer/Tile.js'
|
|
8
|
+
import { fromLonLat } from 'ol/proj.js'
|
|
9
|
+
import 'ol/ol.css'
|
|
10
|
+
import { MVT } from 'ol/format'
|
|
11
|
+
import { VectorTile, XYZ } from 'ol/source'
|
|
12
|
+
import VectorTileLayer from 'ol/layer/VectorTile'
|
|
13
|
+
import { Style, Stroke } from 'ol/style'
|
|
14
|
+
|
|
15
|
+
import { BaseWeatherLayer, TemperatureLayer, PrecipitationLayer, initializeWorkerSystem } from '@hyperviz/weather'
|
|
16
|
+
|
|
17
|
+
import { WindDataTileLayer } from '../ol-components/wind-data-tile-layer.js'
|
|
18
|
+
|
|
19
|
+
// 통합 날씨 레이어 타입 (BaseWeatherLayer와 WindDataTileLayer 모두 포함)
|
|
20
|
+
type WeatherLayerType = BaseWeatherLayer | WindDataTileLayer
|
|
21
|
+
|
|
22
|
+
@customElement('weather-page')
|
|
23
|
+
export class WeatherPage extends PageView {
|
|
24
|
+
static get styles() {
|
|
25
|
+
return css`
|
|
26
|
+
:host {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
height: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.map-container {
|
|
33
|
+
flex: 1;
|
|
34
|
+
display: flex;
|
|
35
|
+
position: relative;
|
|
36
|
+
background-color: #f9f9f9;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#map {
|
|
41
|
+
flex: 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.controls {
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: 10px;
|
|
47
|
+
right: 10px;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: 10px;
|
|
51
|
+
background: rgba(255, 255, 255, 0.8);
|
|
52
|
+
padding: 15px;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
55
|
+
z-index: 10;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.weather-btn {
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
padding: 8px 12px;
|
|
61
|
+
background-color: #f0f0f0;
|
|
62
|
+
border: 1px solid #ddd;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
font-weight: bold;
|
|
65
|
+
transition: all 0.3s;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.weather-btn.active {
|
|
69
|
+
background-color: #4281f5;
|
|
70
|
+
color: white;
|
|
71
|
+
border-color: #2d5ed9;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.refresh-btn {
|
|
75
|
+
margin-top: 10px;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
padding: 8px 12px;
|
|
78
|
+
background-color: #00c851;
|
|
79
|
+
color: white;
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 4px;
|
|
82
|
+
font-weight: bold;
|
|
83
|
+
transition: all 0.3s;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.refresh-btn:hover {
|
|
87
|
+
background-color: #00a651;
|
|
88
|
+
}
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@property({ type: String }) private activeType = 'temperature'
|
|
93
|
+
@state() private map: Map | null = null
|
|
94
|
+
@state() private weatherLayer: WeatherLayerType | null = null
|
|
95
|
+
|
|
96
|
+
constructor() {
|
|
97
|
+
super()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render() {
|
|
101
|
+
return html`
|
|
102
|
+
<div class="map-container">
|
|
103
|
+
<div id="map"></div>
|
|
104
|
+
|
|
105
|
+
<div class="controls">
|
|
106
|
+
<button
|
|
107
|
+
class="weather-btn ${this.activeType === 'temperature' ? 'active' : ''}"
|
|
108
|
+
@click=${() => this._changeWeatherType('temperature')}
|
|
109
|
+
>
|
|
110
|
+
온도
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
class="weather-btn ${this.activeType === 'wind' ? 'active' : ''}"
|
|
114
|
+
@click=${() => this._changeWeatherType('wind')}
|
|
115
|
+
>
|
|
116
|
+
바람
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
class="weather-btn ${this.activeType === 'precipitation' ? 'active' : ''}"
|
|
120
|
+
@click=${() => this._changeWeatherType('precipitation')}
|
|
121
|
+
>
|
|
122
|
+
강수량
|
|
123
|
+
</button>
|
|
124
|
+
<button class="refresh-btn" @click=${this._refreshWeatherMap}>새로고침</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
connectedCallback(): void {
|
|
131
|
+
super.connectedCallback()
|
|
132
|
+
initializeWorkerSystem()
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
this._initMap()
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
disconnectedCallback(): void {
|
|
139
|
+
super.disconnectedCallback()
|
|
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
|
+
}
|
|
151
|
+
this.map = null
|
|
152
|
+
this.weatherLayer = null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _initMap() {
|
|
156
|
+
const lightOSM = new TileLayer({
|
|
157
|
+
source: new XYZ({
|
|
158
|
+
url: 'https://{a-c}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
|
|
159
|
+
attributions: '© OpenStreetMap contributors, © CartoDB'
|
|
160
|
+
}),
|
|
161
|
+
background: 'transparent',
|
|
162
|
+
opacity: 1
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const labelOSM = new TileLayer({
|
|
166
|
+
source: new XYZ({
|
|
167
|
+
url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'
|
|
168
|
+
}),
|
|
169
|
+
background: 'transparent',
|
|
170
|
+
opacity: 1
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const vectorTile = new VectorTile({
|
|
174
|
+
format: new MVT({
|
|
175
|
+
layerName: 'countries',
|
|
176
|
+
layers: ['countries']
|
|
177
|
+
}),
|
|
178
|
+
url: `/geo/tiles/{z}/{x}/{y}.mvt`,
|
|
179
|
+
maxZoom: 10,
|
|
180
|
+
projection: 'EPSG:3857'
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const outlineLayer = new VectorTileLayer({
|
|
184
|
+
source: vectorTile,
|
|
185
|
+
style: new Style({
|
|
186
|
+
stroke: new Stroke({
|
|
187
|
+
color: '#858585',
|
|
188
|
+
// color: '#484848',
|
|
189
|
+
width: 1
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
this.map = new Map({
|
|
195
|
+
target: this.renderRoot.querySelector('#map') as HTMLElement,
|
|
196
|
+
pixelRatio: Math.min(window.devicePixelRatio, 2),
|
|
197
|
+
layers: [lightOSM, outlineLayer, labelOSM],
|
|
198
|
+
view: new View({
|
|
199
|
+
center: fromLonLat([126.9779692, 37.5662952]),
|
|
200
|
+
zoom: 5
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
this._addWeatherLayer()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private _addWeatherLayer() {
|
|
208
|
+
if (!this.map) return
|
|
209
|
+
|
|
210
|
+
switch (this.activeType) {
|
|
211
|
+
case 'temperature':
|
|
212
|
+
this.weatherLayer = new TemperatureLayer()
|
|
213
|
+
break
|
|
214
|
+
case 'wind':
|
|
215
|
+
this.weatherLayer = new WindDataTileLayer()
|
|
216
|
+
break
|
|
217
|
+
case 'precipitation':
|
|
218
|
+
this.weatherLayer = new PrecipitationLayer()
|
|
219
|
+
break
|
|
220
|
+
default:
|
|
221
|
+
this.weatherLayer = new TemperatureLayer()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.weatherLayer) {
|
|
225
|
+
this.weatherLayer.addToMap(this.map)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 날씨 레이어를 변경하고 새 레이어를 맵에 추가
|
|
231
|
+
*/
|
|
232
|
+
private _changeWeatherType(type: string) {
|
|
233
|
+
if (this.activeType === type) return
|
|
234
|
+
|
|
235
|
+
// 현재 활성 타입 저장
|
|
236
|
+
const previousType = this.activeType
|
|
237
|
+
// 새로운 타입으로 변경
|
|
238
|
+
this.activeType = type
|
|
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
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private _refreshWeatherMap() {
|
|
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
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
package/client/route.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const DEFAULT_CACHE_TTL = 3600 // 1시간 (초 단위)
|
|
2
|
+
const DEFAULT_TILE_SIZE = 256
|
|
3
|
+
const DEFAULT_MAX_ZOOM = 14
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
weather: {
|
|
7
|
+
cacheTtl: DEFAULT_CACHE_TTL * 1000, // 밀리초 단위로 변환
|
|
8
|
+
|
|
9
|
+
// 타일 설정
|
|
10
|
+
tileSize: DEFAULT_TILE_SIZE,
|
|
11
|
+
maxZoom: DEFAULT_MAX_ZOOM,
|
|
12
|
+
|
|
13
|
+
// Mock 데이터 사용 여부
|
|
14
|
+
useMockData: true,
|
|
15
|
+
|
|
16
|
+
// 타일 생성 설정
|
|
17
|
+
defaultResolution: 8, // 기본 그리드 해상도
|
|
18
|
+
minResolution: 4, // 최소 그리드 해상도 (낮은 줌 레벨에서 사용)
|
|
19
|
+
maxResolution: 16, // 최대 그리드 해상도 (높은 줌 레벨에서 사용)
|
|
20
|
+
|
|
21
|
+
openWeatherMap: {
|
|
22
|
+
ApiKey: '41937147fdd666b7184c85f8388b845b'
|
|
23
|
+
},
|
|
24
|
+
openMeteo: {
|
|
25
|
+
ApiKey: '' //
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|