@things-factory/weather 9.0.0-beta.55 → 9.0.0-beta.56

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.
@@ -1,244 +0,0 @@
1
- import ImageStatic from 'ol/source/ImageStatic'
2
- import ImageWrapper from 'ol/Image'
3
- import { Extent } from 'ol/extent'
4
- import { ProjectionLike } from 'ol/proj'
5
- import { AttributionLike } from 'ol/source/Source'
6
-
7
- class WebGLImageStatic extends ImageStatic {
8
- private canvas: HTMLCanvasElement | null = null
9
- private gl: WebGLRenderingContext | null = null
10
- private program: WebGLProgram | null = null
11
- private texture: WebGLTexture | null = null
12
- private buffer: WebGLBuffer | null = null
13
- private opacity: number = 0.8
14
- private blendMode: string = 'normal'
15
-
16
- constructor(option: {
17
- url: string
18
- projection: ProjectionLike
19
- interpolation?: boolean
20
- imageExtent: Extent
21
- crossOrigin?: string
22
- attributions?: AttributionLike
23
- opacity?: number
24
- blendMode?: string
25
- }) {
26
- super({
27
- ...option,
28
- imageLoadFunction: (image, src) => this._loadImageWithWebGL(image, src)
29
- })
30
-
31
- // 옵션에서 값 설정 (기본값 제공)
32
- this.opacity = option.opacity !== undefined ? option.opacity : 0.8
33
- this.blendMode = option.blendMode || 'normal'
34
-
35
- // WebGL 컨텍스트 설정
36
- this.canvas = document.createElement('canvas')
37
- this.gl = this.canvas.getContext('webgl', {
38
- alpha: true,
39
- antialias: false,
40
- depth: false
41
- }) as WebGLRenderingContext
42
-
43
- if (!this.gl) {
44
- throw new Error('WebGL을 초기화할 수 없습니다.')
45
- }
46
-
47
- this._initializeWebGL()
48
- }
49
-
50
- /**
51
- * 투명도 설정
52
- */
53
- public setOpacity(opacity: number): void {
54
- if (opacity !== this.opacity) {
55
- this.opacity = opacity
56
- if (this.texture) {
57
- this._renderTexture()
58
- }
59
- }
60
- }
61
-
62
- /**
63
- * 블렌드 모드 설정
64
- */
65
- public setBlendMode(blendMode: string): void {
66
- if (blendMode !== this.blendMode) {
67
- this.blendMode = blendMode
68
- if (this.texture) {
69
- this._renderTexture()
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * WebGL 초기화
76
- */
77
- private _initializeWebGL() {
78
- this.program = this._createShaderProgram()
79
-
80
- if (!this.program) return
81
-
82
- this._initializeBuffers()
83
- }
84
-
85
- /**
86
- * WebGL에서 사용하는 버퍼 설정
87
- */
88
- private _initializeBuffers() {
89
- const vertices = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0])
90
- const gl = this.gl!
91
-
92
- this.buffer = gl.createBuffer()
93
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer)
94
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
95
- }
96
-
97
- /**
98
- * WebGL을 사용하여 렌더링
99
- */
100
- private _renderTexture() {
101
- if (!this.gl || !this.program || !this.texture || !this.buffer) return
102
-
103
- this.gl.viewport(0, 0, this.canvas!.width, this.canvas!.height)
104
- this.gl.clearColor(0, 0, 0, 0)
105
- this.gl.clear(this.gl.COLOR_BUFFER_BIT)
106
- this.gl.useProgram(this.program)
107
-
108
- this.gl.activeTexture(this.gl.TEXTURE0)
109
- this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)
110
- this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0)
111
-
112
- // 사용자 지정 투명도 사용
113
- this.gl.uniform1f(this.gl.getUniformLocation(this.program, 'u_opacity'), this.opacity)
114
-
115
- // 블렌드 모드 설정
116
- let blendModeValue = 0 // normal
117
- switch (this.blendMode) {
118
- case 'multiply':
119
- blendModeValue = 1
120
- break
121
- case 'screen':
122
- blendModeValue = 2
123
- break
124
- case 'color-dodge':
125
- blendModeValue = 3
126
- break
127
- case 'invert':
128
- blendModeValue = 4
129
- break
130
- }
131
- this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_blendMode'), blendModeValue)
132
-
133
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer)
134
- const a_position = this.gl.getAttribLocation(this.program, 'a_position')
135
- const a_texCoord = this.gl.getAttribLocation(this.program, 'a_texCoord')
136
-
137
- this.gl.enableVertexAttribArray(a_position)
138
- this.gl.enableVertexAttribArray(a_texCoord)
139
- this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 16, 0)
140
- this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 16, 8)
141
- this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4)
142
- }
143
-
144
- /**
145
- * WebGL에서 기후 데이터 이미지를 로드
146
- */
147
- private _loadImageWithWebGL(image: ImageWrapper, src: string) {
148
- const img = new Image()
149
- img.crossOrigin = 'anonymous'
150
-
151
- img.onload = () => {
152
- if (!this.gl) return
153
-
154
- // 캔버스 크기 설정
155
- this.canvas!.width = img.width
156
- this.canvas!.height = img.height
157
-
158
- this.texture = this.gl.createTexture()
159
- this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)
160
-
161
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)
162
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)
163
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)
164
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)
165
-
166
- this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img)
167
-
168
- this._renderTexture()
169
- ;(image.getImage() as HTMLImageElement | HTMLVideoElement).src = this.canvas!.toDataURL('image/png')
170
-
171
- this.gl = null
172
- this.canvas = null
173
- }
174
-
175
- img.src = src
176
- }
177
-
178
- /**
179
- * 셰이더 프로그램 생성
180
- */
181
- private _createShaderProgram(): WebGLProgram | null {
182
- const vertexShaderSource = `
183
- precision highp float;
184
- attribute vec4 a_position;
185
- attribute vec2 a_texCoord;
186
- varying vec2 v_texCoord;
187
- void main() {
188
- gl_Position = a_position;
189
- v_texCoord = a_texCoord;
190
- }`
191
-
192
- const fragmentShaderSource = `
193
- precision highp float;
194
- uniform sampler2D u_texture;
195
- uniform float u_opacity;
196
- uniform int u_blendMode;
197
- varying vec2 v_texCoord;
198
-
199
- void main() {
200
- vec4 texColor = texture2D(u_texture, v_texCoord);
201
-
202
- // 완전 투명한 픽셀은 렌더링하지 않음
203
- if (texColor.a < 0.01) {
204
- discard;
205
- }
206
-
207
- float temperature = texColor.r; // 예: 빨간 채널에 온도 정보가 있다고 가정
208
- vec3 color;
209
-
210
- if (temperature < 0.3) {
211
- color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), temperature / 0.3); // 파란색에서 청록색으로
212
- } else if (temperature < 0.6) {
213
- color = mix(vec3(0.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0), (temperature - 0.3) / 0.3); // 청록색에서 녹색으로
214
- } else {
215
- color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (temperature - 0.6) / 0.4); // 녹색에서 빨간색으로
216
- }
217
-
218
- gl_FragColor = vec4(color, texColor.a * u_opacity);
219
- }`
220
-
221
- const gl = this.gl!
222
- const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexShaderSource)
223
- const fragmentShader = this._compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource)
224
- if (!vertexShader || !fragmentShader) return null
225
-
226
- const program = gl.createProgram()!
227
- gl.attachShader(program, vertexShader)
228
- gl.attachShader(program, fragmentShader)
229
- gl.linkProgram(program)
230
-
231
- return gl.getProgramParameter(program, gl.LINK_STATUS) ? program : null
232
- }
233
-
234
- private _compileShader(type: number, source: string): WebGLShader | null {
235
- const gl = this.gl!
236
-
237
- const shader = gl.createShader(type)!
238
- gl.shaderSource(shader, source)
239
- gl.compileShader(shader)
240
- return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null
241
- }
242
- }
243
-
244
- export default WebGLImageStatic
@@ -1,120 +0,0 @@
1
- import { get as getProjection, transform } from 'ol/proj.js'
2
- import DataTileSource from 'ol/source/DataTile.js'
3
- import { createXYZ, wrapX } from 'ol/tilegrid.js'
4
-
5
- const windData: Promise<{ data: ArrayBuffer; width: number; height: number }> = new Promise((resolve, reject) => {
6
- const image = new Image()
7
- image.onload = () => {
8
- const canvas = document.createElement('canvas')
9
- const width = image.width
10
- const height = image.height
11
- canvas.width = width
12
- canvas.height = height
13
-
14
- const context = canvas.getContext('2d')!
15
- context.drawImage(image, 0, 0)
16
- const data = context.getImageData(0, 0, width, height).data
17
- resolve({ data, width, height })
18
- }
19
- image.onerror = () => {
20
- reject(new Error('failed to load'))
21
- }
22
- image.src = '/assets/images/wind.png'
23
- })
24
-
25
- function bilinearInterpolation(xAlong, yAlong, v11, v21, v12, v22) {
26
- const q11 = (1 - xAlong) * (1 - yAlong) * v11
27
- const q21 = xAlong * (1 - yAlong) * v21
28
- const q12 = (1 - xAlong) * yAlong * v12
29
- const q22 = xAlong * yAlong * v22
30
- return q11 + q21 + q12 + q22
31
- }
32
-
33
- function interpolatePixels(xAlong, yAlong, p11, p21, p12, p22) {
34
- return p11.map((_, i) => bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]))
35
- }
36
-
37
- const dataTileGrid = createXYZ()
38
- const dataTileSize = 256
39
-
40
- const inputImageProjection = getProjection('EPSG:4326')!
41
- const dataTileProjection = getProjection('EPSG:3857')!
42
-
43
- const inputBands = 4
44
- const dataBands = 3
45
-
46
- // range of wind velocities
47
- // these values are stretched between 0 and 255 in the png
48
- const minU = -21.32
49
- const maxU = 26.8
50
- const deltaU = maxU - minU
51
- const minV = -21.57
52
- const maxV = 21.42
53
- const deltaV = maxV - minV
54
-
55
- const wind = new DataTileSource({
56
- // transition must be 0, see https://github.com/openlayers/openlayers/issues/16119
57
- transition: 0,
58
- wrapX: true,
59
- async loader(z, x, y) {
60
- const { data: inputData, width: inputWidth, height: inputHeight } = await windData
61
-
62
- const tileCoord = wrapX(dataTileGrid, [z, x, y], dataTileProjection)
63
- const extent = dataTileGrid.getTileCoordExtent(tileCoord)
64
- const resolution = dataTileGrid.getResolution(z)
65
- const data = new Float32Array(dataTileSize * dataTileSize * dataBands)
66
- for (let row = 0; row < dataTileSize; ++row) {
67
- let offset = row * dataTileSize * dataBands
68
- const mapY = extent[3] - row * resolution
69
- for (let col = 0; col < dataTileSize; ++col) {
70
- const mapX = extent[0] + col * resolution
71
- const [lon, lat] = transform([mapX, mapY], dataTileProjection, inputImageProjection)
72
-
73
- const x = (inputWidth * (lon + 180)) / 360
74
- let x1 = Math.floor(x)
75
- let x2 = Math.ceil(x)
76
- const xAlong = x - x1
77
- if (x1 < 0) {
78
- x1 += inputWidth
79
- }
80
- if (x2 >= inputWidth) {
81
- x2 -= inputWidth
82
- }
83
-
84
- const y = (inputHeight * (90 - lat)) / 180
85
- let y1 = Math.floor(y)
86
- let y2 = Math.ceil(y)
87
- const yAlong = y - y1
88
- if (y1 < 0) {
89
- y1 = 0
90
- }
91
- if (y2 >= inputHeight) {
92
- y2 = inputHeight - 1
93
- }
94
-
95
- const corners = [
96
- [x1, y1],
97
- [x2, y1],
98
- [x1, y2],
99
- [x2, y2]
100
- ]
101
-
102
- const pixels = corners.map(([cx, cy]) => {
103
- const inputOffset = (cy * 360 + cx) * inputBands
104
- return [inputData[inputOffset], inputData[inputOffset + 1]]
105
- })
106
-
107
- const interpolated = interpolatePixels(xAlong, yAlong, ...(pixels as [any, any, any, any]))
108
- const u = minU + (deltaU * interpolated[0]) / 255
109
- const v = minV + (deltaV * interpolated[1]) / 255
110
-
111
- data[offset] = u
112
- data[offset + 1] = v
113
- offset += dataBands
114
- }
115
- }
116
- return data
117
- }
118
- })
119
-
120
- export default wind