@things-factory/weather 9.0.0-beta.41

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 (89) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/assets/images/wind.png +0 -0
  3. package/client/bootstrap.ts +1 -0
  4. package/client/index.ts +0 -0
  5. package/client/ol-components/webgl-canvas-layer.ts +413 -0
  6. package/client/ol-components/webgl-image-static.ts +174 -0
  7. package/client/ol-components/wind.ts +120 -0
  8. package/client/pages/weather-page.ts +318 -0
  9. package/client/route.ts +7 -0
  10. package/client/tsconfig.json +11 -0
  11. package/config/config.development.js +25 -0
  12. package/config/config.production.js +7 -0
  13. package/data/geo-json/countries-max-geo.json +1 -0
  14. package/dist-client/bootstrap.d.ts +1 -0
  15. package/dist-client/bootstrap.js +2 -0
  16. package/dist-client/bootstrap.js.map +1 -0
  17. package/dist-client/index.d.ts +0 -0
  18. package/dist-client/index.js +2 -0
  19. package/dist-client/index.js.map +1 -0
  20. package/dist-client/ol-components/webgl-canvas-layer.d.ts +61 -0
  21. package/dist-client/ol-components/webgl-canvas-layer.js +364 -0
  22. package/dist-client/ol-components/webgl-canvas-layer.js.map +1 -0
  23. package/dist-client/ol-components/webgl-image-static.d.ts +41 -0
  24. package/dist-client/ol-components/webgl-image-static.js +135 -0
  25. package/dist-client/ol-components/webgl-image-static.js.map +1 -0
  26. package/dist-client/ol-components/wind.d.ts +3 -0
  27. package/dist-client/ol-components/wind.js +104 -0
  28. package/dist-client/ol-components/wind.js.map +1 -0
  29. package/dist-client/pages/weather-page.d.ts +18 -0
  30. package/dist-client/pages/weather-page.js +300 -0
  31. package/dist-client/pages/weather-page.js.map +1 -0
  32. package/dist-client/route.d.ts +1 -0
  33. package/dist-client/route.js +8 -0
  34. package/dist-client/route.js.map +1 -0
  35. package/dist-client/tsconfig.tsbuildinfo +1 -0
  36. package/dist-server/controllers/open-meteo-adapter.d.ts +4 -0
  37. package/dist-server/controllers/open-meteo-adapter.js +83 -0
  38. package/dist-server/controllers/open-meteo-adapter.js.map +1 -0
  39. package/dist-server/controllers/types.d.ts +55 -0
  40. package/dist-server/controllers/types.js +3 -0
  41. package/dist-server/controllers/types.js.map +1 -0
  42. package/dist-server/controllers/weather-controller.d.ts +12 -0
  43. package/dist-server/controllers/weather-controller.js +164 -0
  44. package/dist-server/controllers/weather-controller.js.map +1 -0
  45. package/dist-server/index.d.ts +1 -0
  46. package/dist-server/index.js +4 -0
  47. package/dist-server/index.js.map +1 -0
  48. package/dist-server/routers/geo-router.d.ts +59 -0
  49. package/dist-server/routers/geo-router.js +277 -0
  50. package/dist-server/routers/geo-router.js.map +1 -0
  51. package/dist-server/routers/weather-router.d.ts +1 -0
  52. package/dist-server/routers/weather-router.js +10 -0
  53. package/dist-server/routers/weather-router.js.map +1 -0
  54. package/dist-server/routes.d.ts +1 -0
  55. package/dist-server/routes.js +11 -0
  56. package/dist-server/routes.js.map +1 -0
  57. package/dist-server/services/chrome-renderer.d.ts +13 -0
  58. package/dist-server/services/chrome-renderer.js +137 -0
  59. package/dist-server/services/chrome-renderer.js.map +1 -0
  60. package/dist-server/services/headless-pool.d.ts +4 -0
  61. package/dist-server/services/headless-pool.js +89 -0
  62. package/dist-server/services/headless-pool.js.map +1 -0
  63. package/dist-server/tsconfig.tsbuildinfo +1 -0
  64. package/dist-server/utils/svg-utils.d.ts +35 -0
  65. package/dist-server/utils/svg-utils.js +89 -0
  66. package/dist-server/utils/svg-utils.js.map +1 -0
  67. package/dist-server/utils/tile-utils.d.ts +38 -0
  68. package/dist-server/utils/tile-utils.js +64 -0
  69. package/dist-server/utils/tile-utils.js.map +1 -0
  70. package/package.json +45 -0
  71. package/server/controllers/open-meteo-adapter.ts +91 -0
  72. package/server/controllers/types.ts +63 -0
  73. package/server/controllers/weather-controller.ts +176 -0
  74. package/server/index.ts +1 -0
  75. package/server/routers/geo-router.ts +297 -0
  76. package/server/routers/weather-router.ts +8 -0
  77. package/server/routes.ts +10 -0
  78. package/server/services/chrome-renderer.ts +175 -0
  79. package/server/services/data.json +8438 -0
  80. package/server/services/headless-pool.ts +97 -0
  81. package/server/tsconfig.json +10 -0
  82. package/server/utils/svg-utils.ts +108 -0
  83. package/server/utils/tile-utils.ts +77 -0
  84. package/things-factory.config.js +8 -0
  85. package/translations/en.json +1 -0
  86. package/translations/ja.json +1 -0
  87. package/translations/ko.json +1 -0
  88. package/translations/ms.json +1 -0
  89. package/translations/zh.json +1 -0
@@ -0,0 +1,175 @@
1
+ import { getBrowserPool } from './headless-pool'
2
+
3
+ export async function renderWeatherMap(
4
+ type: string,
5
+ data: {
6
+ lat: number
7
+ lng: number
8
+ value: number
9
+ description: number
10
+ temperature: number
11
+ humidity: number
12
+ cloudCoverage: number
13
+ solarRadiation: number
14
+ windSpeed: number
15
+ windDirection: number
16
+ precipitation?: number
17
+ }[],
18
+ width: number = 1800,
19
+ height: number = 900
20
+ ): Promise<Buffer> {
21
+ console.log(`renderWeatherMap 호출됨: 타입=${type}, 데이터 개수=${data.length}`)
22
+
23
+ if (!data || data.length === 0) {
24
+ throw new Error('데이터가 없습니다. PNG 생성을 중단합니다.')
25
+ }
26
+
27
+ const browser = await getBrowserPool().acquire()
28
+
29
+ try {
30
+ console.log('브라우저 인스턴스 획득')
31
+ const page = await browser.newPage()
32
+
33
+ // 모든 콘솔 로그를 캡처하여 Node.js 프로세스에 출력
34
+ page.on('console', msg => {
35
+ console.log(`PAGE LOG [${msg.type()}]: ${msg.text()}`)
36
+ })
37
+
38
+ // 오류 이벤트도 추적 (예: Unhandled Promise Rejection)
39
+ page.on('pageerror', err => {
40
+ console.error('BROWSER PAGE ERROR:', err)
41
+ })
42
+
43
+ await page.setViewport({ width, height })
44
+
45
+ await page.setContent(`
46
+ <!DOCTYPE html>
47
+ <html>
48
+ <head>
49
+ <style>body { margin: 0; }</style>
50
+ </head>
51
+ <body>
52
+ <canvas id="weatherCanvas" width="${width}" height="${height}"></canvas>
53
+ </body>
54
+ </html>
55
+ `)
56
+
57
+ console.log('HTML 콘텐츠 설정 완료')
58
+
59
+ await page.evaluate(
60
+ (type, data, width, height) => {
61
+ try {
62
+ console.log('Canvas 렌더링 시작')
63
+
64
+ const canvas = document.getElementById('weatherCanvas') as HTMLCanvasElement
65
+ if (!canvas) {
66
+ throw new Error('캔버스를 찾을 수 없습니다.')
67
+ }
68
+
69
+ const ctx = canvas.getContext('2d')
70
+ if (!ctx) {
71
+ throw new Error('CanvasRenderingContext2D를 가져올 수 없습니다.')
72
+ }
73
+
74
+ ctx.clearRect(0, 0, width, height)
75
+
76
+ function latLngToPixel(lat, lng) {
77
+ if (isNaN(lat) || isNaN(lng)) return null
78
+ const x = Math.floor(((lng + 180) / 360) * width)
79
+ const y = Math.floor(((90 - lat) / 180) * height)
80
+ if (x < 0 || x >= width || y < 0 || y >= height) return null
81
+ return { x, y }
82
+ }
83
+
84
+ function valueToRGB(type, dataPoint) {
85
+ if (!dataPoint || typeof dataPoint.value !== 'number') {
86
+ console.warn('잘못된 데이터 포인트:', dataPoint)
87
+ return { r: 0, g: 0, b: 0, a: 0 }
88
+ }
89
+ switch (type) {
90
+ case 'temperature':
91
+ return { r: Math.min(255, Math.max(0, (dataPoint.value + 50) * 2.55)), g: 0, b: 0, a: 255 }
92
+ case 'humidity':
93
+ return { r: 0, g: Math.min(255, Math.max(0, dataPoint.humidity * 2.55)), b: 0, a: 255 }
94
+ case 'wind':
95
+ return { r: Math.min(255, Math.max(0, dataPoint.windSpeed * 5)), g: 0, b: 255, a: 255 }
96
+ case 'precipitation':
97
+ return { r: 0, g: 0, b: Math.min(255, Math.max(0, dataPoint.precipitation * 2.55)), a: 255 }
98
+ default:
99
+ return { r: 255, g: 255, b: 255, a: 255 }
100
+ }
101
+ }
102
+
103
+ const imageData = ctx.createImageData(width, height)
104
+
105
+ data.forEach(point => {
106
+ const pixel = latLngToPixel(point.lat, point.lng)
107
+ if (!pixel) return
108
+
109
+ const color = valueToRGB(type, point)
110
+ const index = (pixel.y * width + pixel.x) * 4
111
+
112
+ if (index >= 0 && index < imageData.data.length) {
113
+ imageData.data[index] = color.r
114
+ imageData.data[index + 1] = color.g
115
+ imageData.data[index + 2] = color.b
116
+ imageData.data[index + 3] = color.a
117
+ } else {
118
+ console.warn('잘못된 인덱스:', index)
119
+ }
120
+
121
+ /* only for test */
122
+ for (let dy = -18; dy <= 18; dy++) {
123
+ for (let dx = -18; dx <= 18; dx++) {
124
+ const fillIndex = ((pixel.y + dy) * width + (pixel.x + dx)) * 4
125
+ if (fillIndex >= 0 && fillIndex < imageData.data.length) {
126
+ imageData.data[fillIndex] = color.r
127
+ imageData.data[fillIndex + 1] = color.g
128
+ imageData.data[fillIndex + 2] = color.b
129
+ imageData.data[fillIndex + 3] = color.a
130
+ }
131
+ }
132
+ }
133
+ })
134
+
135
+ ctx.putImageData(imageData, 0, 0)
136
+ console.log('Canvas 렌더링 완료')
137
+ ;(window as any).renderComplete = true
138
+ } catch (err) {
139
+ console.error('렌더링 오류:', err)
140
+ ;(window as any).renderComplete = false
141
+ }
142
+ },
143
+ type,
144
+ data,
145
+ width,
146
+ height
147
+ )
148
+
149
+ console.log('렌더링 완료')
150
+
151
+ const renderComplete = await page
152
+ .waitForFunction('window.renderComplete === true', { timeout: 10000 })
153
+ .catch(err => {
154
+ console.error('렌더링 완료 신호 감지 실패:', err)
155
+ return false
156
+ })
157
+
158
+ if (!renderComplete) {
159
+ throw new Error('렌더링이 완료되지 않았습니다.')
160
+ }
161
+
162
+ const pngBuffer = await page.screenshot({
163
+ type: 'png',
164
+ omitBackground: true
165
+ })
166
+
167
+ console.log(`PNG 생성 완료: ${pngBuffer.length} 바이트`)
168
+ return pngBuffer
169
+ } catch (error) {
170
+ console.error('날씨 맵 렌더링 오류:', error)
171
+ throw error
172
+ } finally {
173
+ await getBrowserPool().release(browser)
174
+ }
175
+ }