@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.
Files changed (90) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/assets/images/wind.png +0 -0
  3. package/assets/images/wind2.png +0 -0
  4. package/client/bootstrap.ts +1 -0
  5. package/client/index.ts +0 -0
  6. package/client/ol-components/webgl-canvas-layer.ts +413 -0
  7. package/client/ol-components/wind-data-tile-layer.ts +485 -0
  8. package/client/ol-components/wind-rasterized-layer.ts +437 -0
  9. package/client/pages/weather-page.ts +353 -0
  10. package/client/route.ts +7 -0
  11. package/client/tsconfig.json +11 -0
  12. package/config/config.development.js +28 -0
  13. package/config/config.production.js +10 -0
  14. package/data/geo-json/countries-max-geo.json +1 -0
  15. package/dist-client/bootstrap.d.ts +1 -0
  16. package/dist-client/bootstrap.js +2 -0
  17. package/dist-client/bootstrap.js.map +1 -0
  18. package/dist-client/index.d.ts +0 -0
  19. package/dist-client/index.js +2 -0
  20. package/dist-client/index.js.map +1 -0
  21. package/dist-client/ol-components/webgl-canvas-layer.d.ts +61 -0
  22. package/dist-client/ol-components/webgl-canvas-layer.js +364 -0
  23. package/dist-client/ol-components/webgl-canvas-layer.js.map +1 -0
  24. package/dist-client/ol-components/wind-data-tile-layer.d.ts +59 -0
  25. package/dist-client/ol-components/wind-data-tile-layer.js +414 -0
  26. package/dist-client/ol-components/wind-data-tile-layer.js.map +1 -0
  27. package/dist-client/ol-components/wind-rasterized-layer.d.ts +59 -0
  28. package/dist-client/ol-components/wind-rasterized-layer.js +363 -0
  29. package/dist-client/ol-components/wind-rasterized-layer.js.map +1 -0
  30. package/dist-client/pages/weather-page.d.ts +23 -0
  31. package/dist-client/pages/weather-page.js +348 -0
  32. package/dist-client/pages/weather-page.js.map +1 -0
  33. package/dist-client/route.d.ts +1 -0
  34. package/dist-client/route.js +8 -0
  35. package/dist-client/route.js.map +1 -0
  36. package/dist-client/tsconfig.tsbuildinfo +1 -0
  37. package/dist-server/controllers/open-meteo-adapter.d.ts +4 -0
  38. package/dist-server/controllers/open-meteo-adapter.js +87 -0
  39. package/dist-server/controllers/open-meteo-adapter.js.map +1 -0
  40. package/dist-server/controllers/types.d.ts +55 -0
  41. package/dist-server/controllers/types.js +3 -0
  42. package/dist-server/controllers/types.js.map +1 -0
  43. package/dist-server/controllers/weather-controller.d.ts +12 -0
  44. package/dist-server/controllers/weather-controller.js +179 -0
  45. package/dist-server/controllers/weather-controller.js.map +1 -0
  46. package/dist-server/index.d.ts +1 -0
  47. package/dist-server/index.js +4 -0
  48. package/dist-server/index.js.map +1 -0
  49. package/dist-server/routers/geo-router.d.ts +59 -0
  50. package/dist-server/routers/geo-router.js +277 -0
  51. package/dist-server/routers/geo-router.js.map +1 -0
  52. package/dist-server/routers/weather-router.d.ts +1 -0
  53. package/dist-server/routers/weather-router.js +10 -0
  54. package/dist-server/routers/weather-router.js.map +1 -0
  55. package/dist-server/routes.d.ts +1 -0
  56. package/dist-server/routes.js +11 -0
  57. package/dist-server/routes.js.map +1 -0
  58. package/dist-server/services/chrome-renderer.d.ts +13 -0
  59. package/dist-server/services/chrome-renderer.js +162 -0
  60. package/dist-server/services/chrome-renderer.js.map +1 -0
  61. package/dist-server/services/headless-pool.d.ts +4 -0
  62. package/dist-server/services/headless-pool.js +89 -0
  63. package/dist-server/services/headless-pool.js.map +1 -0
  64. package/dist-server/tsconfig.tsbuildinfo +1 -0
  65. package/dist-server/utils/svg-utils.d.ts +35 -0
  66. package/dist-server/utils/svg-utils.js +89 -0
  67. package/dist-server/utils/svg-utils.js.map +1 -0
  68. package/dist-server/utils/tile-utils.d.ts +38 -0
  69. package/dist-server/utils/tile-utils.js +64 -0
  70. package/dist-server/utils/tile-utils.js.map +1 -0
  71. package/package.json +46 -0
  72. package/server/controllers/open-meteo-adapter.ts +96 -0
  73. package/server/controllers/types.ts +63 -0
  74. package/server/controllers/weather-controller.ts +192 -0
  75. package/server/index.ts +1 -0
  76. package/server/routers/geo-router.ts +297 -0
  77. package/server/routers/weather-router.ts +8 -0
  78. package/server/routes.ts +10 -0
  79. package/server/services/chrome-renderer.ts +206 -0
  80. package/server/services/data.json +8438 -0
  81. package/server/services/headless-pool.ts +97 -0
  82. package/server/tsconfig.json +10 -0
  83. package/server/utils/svg-utils.ts +108 -0
  84. package/server/utils/tile-utils.ts +77 -0
  85. package/things-factory.config.js +8 -0
  86. package/translations/en.json +1 -0
  87. package/translations/ja.json +1 -0
  88. package/translations/ko.json +1 -0
  89. package/translations/ms.json +1 -0
  90. 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
+ }
@@ -0,0 +1,7 @@
1
+ export default function route(page: string) {
2
+ switch (page) {
3
+ case 'weather-page':
4
+ import('./pages/weather-page.js')
5
+ return page
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "declaration": true,
6
+ "module": "esnext",
7
+ "outDir": "../dist-client",
8
+ "baseUrl": "./"
9
+ },
10
+ "include": ["./**/*"]
11
+ }
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ weather: {
3
+ openWeatherMap: {
4
+ ApiKey: '1234567890'
5
+ },
6
+ openMeteo: {
7
+ ApiKey: '' //
8
+ }
9
+ }
10
+ }