@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,97 @@
|
|
|
1
|
+
import * as genericPool from 'generic-pool'
|
|
2
|
+
import { config } from '@things-factory/env'
|
|
3
|
+
|
|
4
|
+
// puppeteer 모듈 동적 로드 (에러 처리 포함)
|
|
5
|
+
let puppeteer
|
|
6
|
+
try {
|
|
7
|
+
puppeteer = require('puppeteer')
|
|
8
|
+
} catch (err) {
|
|
9
|
+
console.error('Puppeteer 로드 실패:', err)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 브라우저 풀 인스턴스
|
|
13
|
+
let browserPool
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 헤드리스 브라우저 풀 가져오기 (싱글톤 패턴)
|
|
17
|
+
*/
|
|
18
|
+
export function getBrowserPool() {
|
|
19
|
+
if (!browserPool) {
|
|
20
|
+
browserPool = genericPool.createPool(
|
|
21
|
+
{
|
|
22
|
+
// 브라우저 인스턴스 생성
|
|
23
|
+
create() {
|
|
24
|
+
console.log('날씨 서비스용 headless 브라우저 인스턴스 생성')
|
|
25
|
+
return initializeChromium()
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// 브라우저 인스턴스 유효성 검사
|
|
29
|
+
validate(browser) {
|
|
30
|
+
return Promise.race([
|
|
31
|
+
new Promise(res => setTimeout(() => res(false), 1500)),
|
|
32
|
+
browser
|
|
33
|
+
.version()
|
|
34
|
+
.then(_ => true)
|
|
35
|
+
.catch(_ => false)
|
|
36
|
+
])
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// 브라우저 인스턴스 파괴
|
|
40
|
+
destroy(browser) {
|
|
41
|
+
return browser.close()
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
// 풀 설정
|
|
46
|
+
min: 1, // 최소 인스턴스 수
|
|
47
|
+
max: 5, // 최대 인스턴스 수
|
|
48
|
+
testOnBorrow: true,
|
|
49
|
+
acquireTimeoutMillis: 10000,
|
|
50
|
+
idleTimeoutMillis: 30000 // 30초 후 유휴 인스턴스 제거
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// 애플리케이션 종료 시 정리
|
|
55
|
+
process.on('exit', async () => {
|
|
56
|
+
if (browserPool) {
|
|
57
|
+
await browserPool.drain()
|
|
58
|
+
await browserPool.clear()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return browserPool
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Chromium 초기화
|
|
68
|
+
*/
|
|
69
|
+
async function initializeChromium() {
|
|
70
|
+
// puppeteer가 없으면 오류
|
|
71
|
+
if (!puppeteer) {
|
|
72
|
+
throw new Error('Puppeteer is not available')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 크로미움 경로 (환경설정에서 가져옴)
|
|
76
|
+
const CHROMIUM_PATH = config.get('CHROMIUM_PATH')
|
|
77
|
+
|
|
78
|
+
// 실행 설정
|
|
79
|
+
const launchSettings = {
|
|
80
|
+
args: ['--hide-scrollbars', '--mute-audio', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
81
|
+
headless: 'new' // 'new' = 최신 헤드리스 모드
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 크로미움 경로가 설정되어 있으면 사용
|
|
85
|
+
if (CHROMIUM_PATH) {
|
|
86
|
+
launchSettings['executablePath'] = CHROMIUM_PATH
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// 브라우저 실행
|
|
91
|
+
const browser = await puppeteer.launch(launchSettings)
|
|
92
|
+
return browser
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('크로미움 초기화 실패:', err)
|
|
95
|
+
throw err
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG 생성 유틸리티 함수
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SVG 시작 태그 생성
|
|
7
|
+
*/
|
|
8
|
+
export function createSvgStart(width: number = 256, height: number = 256): string {
|
|
9
|
+
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* SVG 종료 태그 생성
|
|
14
|
+
*/
|
|
15
|
+
export function createSvgEnd(): string {
|
|
16
|
+
return '</svg>'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SVG 그라데이션 정의 생성
|
|
21
|
+
*/
|
|
22
|
+
export function createSvgDefs(gradients: Array<{ id: string; color: string; opacity?: number }>): string {
|
|
23
|
+
let defs = '<defs>'
|
|
24
|
+
|
|
25
|
+
gradients.forEach(gradient => {
|
|
26
|
+
const opacity = gradient.opacity !== undefined ? gradient.opacity : 0.8
|
|
27
|
+
defs += `
|
|
28
|
+
<radialGradient id="${gradient.id}" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
|
|
29
|
+
<stop offset="0%" style="stop-color:${gradient.color}; stop-opacity:${opacity}" />
|
|
30
|
+
<stop offset="100%" style="stop-color:${gradient.color}; stop-opacity:0" />
|
|
31
|
+
</radialGradient>`
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
defs += '</defs>'
|
|
35
|
+
return defs
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SVG 원 요소 생성
|
|
40
|
+
*/
|
|
41
|
+
export function createSvgCircle(cx: number, cy: number, radius: number, fill: string, opacity?: number): string {
|
|
42
|
+
const opacityAttr = opacity !== undefined ? ` opacity="${opacity}"` : ''
|
|
43
|
+
return `<circle cx="${cx}" cy="${cy}" r="${radius}" fill="${fill}"${opacityAttr} />`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* SVG 라인 요소 생성
|
|
48
|
+
*/
|
|
49
|
+
export function createSvgLine(
|
|
50
|
+
x1: number,
|
|
51
|
+
y1: number,
|
|
52
|
+
x2: number,
|
|
53
|
+
y2: number,
|
|
54
|
+
stroke: string,
|
|
55
|
+
strokeWidth: number = 1,
|
|
56
|
+
opacity?: number
|
|
57
|
+
): string {
|
|
58
|
+
const opacityAttr = opacity !== undefined ? ` opacity="${opacity}"` : ''
|
|
59
|
+
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${stroke}" stroke-width="${strokeWidth}"${opacityAttr} />`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* SVG 화살표 생성 (바람 방향 표시용)
|
|
64
|
+
*/
|
|
65
|
+
export function createSvgArrow(
|
|
66
|
+
x: number,
|
|
67
|
+
y: number,
|
|
68
|
+
angle: number,
|
|
69
|
+
length: number,
|
|
70
|
+
color: string,
|
|
71
|
+
strokeWidth: number = 1,
|
|
72
|
+
opacity?: number
|
|
73
|
+
): string {
|
|
74
|
+
const opacityAttr = opacity !== undefined ? ` opacity="${opacity}"` : ''
|
|
75
|
+
|
|
76
|
+
// 라디안으로 변환
|
|
77
|
+
const rad = (angle * Math.PI) / 180
|
|
78
|
+
|
|
79
|
+
// 화살표 끝점 계산
|
|
80
|
+
const endX = x + length * Math.cos(rad)
|
|
81
|
+
const endY = y + length * Math.sin(rad)
|
|
82
|
+
|
|
83
|
+
// 화살표 머리 계산 (각도에서 30도 틀어진 두 지점)
|
|
84
|
+
const headSize = length * 0.25
|
|
85
|
+
const head1X = endX - headSize * Math.cos(rad + Math.PI / 6)
|
|
86
|
+
const head1Y = endY - headSize * Math.sin(rad + Math.PI / 6)
|
|
87
|
+
const head2X = endX - headSize * Math.cos(rad - Math.PI / 6)
|
|
88
|
+
const head2Y = endY - headSize * Math.sin(rad - Math.PI / 6)
|
|
89
|
+
|
|
90
|
+
return `
|
|
91
|
+
<g${opacityAttr}>
|
|
92
|
+
<line x1="${x}" y1="${y}" x2="${endX}" y2="${endY}" stroke="${color}" stroke-width="${strokeWidth}" />
|
|
93
|
+
<line x1="${endX}" y1="${endY}" x2="${head1X}" y2="${head1Y}" stroke="${color}" stroke-width="${strokeWidth}" />
|
|
94
|
+
<line x1="${endX}" y1="${endY}" x2="${head2X}" y2="${head2Y}" stroke="${color}" stroke-width="${strokeWidth}" />
|
|
95
|
+
</g>
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 날씨 타일용 SVG 생성
|
|
101
|
+
*/
|
|
102
|
+
export function createWeatherTileSvg(width: number, height: number, elements: string[]): string {
|
|
103
|
+
return `
|
|
104
|
+
${createSvgStart(width, height)}
|
|
105
|
+
${elements.join('\n')}
|
|
106
|
+
${createSvgEnd()}
|
|
107
|
+
`
|
|
108
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 타일 좌표 관련 유틸리티 함수
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface TileBounds {
|
|
6
|
+
west: number
|
|
7
|
+
south: number
|
|
8
|
+
east: number
|
|
9
|
+
north: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 타일 좌표를 위경도 범위로 변환
|
|
14
|
+
*/
|
|
15
|
+
export function getTileBounds(z: number, x: number, y: number): TileBounds {
|
|
16
|
+
const n = Math.pow(2, z)
|
|
17
|
+
const west = (x / n) * 360 - 180
|
|
18
|
+
const east = ((x + 1) / n) * 360 - 180
|
|
19
|
+
const north = Math.atan(Math.sinh(Math.PI * (1 - (2 * y) / n))) * (180 / Math.PI)
|
|
20
|
+
const south = Math.atan(Math.sinh(Math.PI * (1 - (2 * (y + 1)) / n))) * (180 / Math.PI)
|
|
21
|
+
|
|
22
|
+
return { west, south, east, north }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 위경도를 타일 좌표로 변환
|
|
27
|
+
*/
|
|
28
|
+
export function latLonToTile(lat: number, lon: number, zoom: number): { x: number; y: number } {
|
|
29
|
+
const latRad = (lat * Math.PI) / 180
|
|
30
|
+
const n = Math.pow(2, zoom)
|
|
31
|
+
const x = Math.floor(((lon + 180) / 360) * n)
|
|
32
|
+
const y = Math.floor(((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n)
|
|
33
|
+
|
|
34
|
+
return { x, y }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 픽셀 좌표를 위경도로 변환
|
|
39
|
+
*/
|
|
40
|
+
export function pixelToLatLon(x: number, y: number, z: number): { lat: number; lon: number } {
|
|
41
|
+
const size = Math.pow(2, z) * 256
|
|
42
|
+
const lon = (x / size) * 360 - 180
|
|
43
|
+
|
|
44
|
+
const n = Math.PI - (2 * Math.PI * y) / size
|
|
45
|
+
const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))
|
|
46
|
+
|
|
47
|
+
return { lat, lon }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 위경도를 픽셀 좌표로 변환
|
|
52
|
+
*/
|
|
53
|
+
export function latLonToPixel(lat: number, lon: number, z: number): { x: number; y: number } {
|
|
54
|
+
const size = Math.pow(2, z) * 256
|
|
55
|
+
const x = ((lon + 180) / 360) * size
|
|
56
|
+
|
|
57
|
+
const latRad = (lat * Math.PI) / 180
|
|
58
|
+
const y = ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * size
|
|
59
|
+
|
|
60
|
+
return { x, y }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 두 위경도 사이의 거리 계산 (Haversine 공식)
|
|
65
|
+
*/
|
|
66
|
+
export function getDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
|
67
|
+
const R = 6371 // 지구 반경 (km)
|
|
68
|
+
const dLat = (lat2 - lat1) * (Math.PI / 180)
|
|
69
|
+
const dLon = (lon2 - lon1) * (Math.PI / 180)
|
|
70
|
+
|
|
71
|
+
const a =
|
|
72
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
73
|
+
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) * Math.sin(dLon / 2) * Math.sin(dLon / 2)
|
|
74
|
+
|
|
75
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
|
76
|
+
return R * c
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|