@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.
- package/CHANGELOG.md +8 -0
- package/assets/images/wind.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/webgl-image-static.ts +174 -0
- package/client/ol-components/wind.ts +120 -0
- package/client/pages/weather-page.ts +318 -0
- package/client/route.ts +7 -0
- package/client/tsconfig.json +11 -0
- package/config/config.development.js +25 -0
- package/config/config.production.js +7 -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/webgl-image-static.d.ts +41 -0
- package/dist-client/ol-components/webgl-image-static.js +135 -0
- package/dist-client/ol-components/webgl-image-static.js.map +1 -0
- package/dist-client/ol-components/wind.d.ts +3 -0
- package/dist-client/ol-components/wind.js +104 -0
- package/dist-client/ol-components/wind.js.map +1 -0
- package/dist-client/pages/weather-page.d.ts +18 -0
- package/dist-client/pages/weather-page.js +300 -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 +83 -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 +164 -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 +137 -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 +45 -0
- package/server/controllers/open-meteo-adapter.ts +91 -0
- package/server/controllers/types.ts +63 -0
- package/server/controllers/weather-controller.ts +176 -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 +175 -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,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
|
+
}
|