@things-factory/weather 9.0.0-beta.50 → 9.0.0-beta.52
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/assets/images/wind.png +0 -0
- package/assets/images/wind2.png +0 -0
- package/client/ol-components/webgl-image-static.ts +75 -5
- package/client/pages/weather-page.ts +39 -9
- package/dist-client/ol-components/webgl-image-static.d.ts +12 -0
- package/dist-client/ol-components/webgl-image-static.js +66 -5
- package/dist-client/ol-components/webgl-image-static.js.map +1 -1
- package/dist-client/pages/weather-page.js +37 -9
- package/dist-client/pages/weather-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/open-meteo-adapter.js +3 -1
- package/dist-server/controllers/open-meteo-adapter.js.map +1 -1
- package/dist-server/controllers/weather-controller.js +26 -11
- package/dist-server/controllers/weather-controller.js.map +1 -1
- package/dist-server/services/chrome-renderer.js +51 -26
- package/dist-server/services/chrome-renderer.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/controllers/open-meteo-adapter.ts +4 -1
- package/server/controllers/weather-controller.ts +23 -7
- package/server/services/chrome-renderer.ts +58 -27
package/assets/images/wind.png
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -10,6 +10,8 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
10
10
|
private program: WebGLProgram | null = null
|
|
11
11
|
private texture: WebGLTexture | null = null
|
|
12
12
|
private buffer: WebGLBuffer | null = null
|
|
13
|
+
private opacity: number = 0.8
|
|
14
|
+
private blendMode: string = 'normal'
|
|
13
15
|
|
|
14
16
|
constructor(option: {
|
|
15
17
|
url: string
|
|
@@ -18,12 +20,18 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
18
20
|
imageExtent: Extent
|
|
19
21
|
crossOrigin?: string
|
|
20
22
|
attributions?: AttributionLike
|
|
23
|
+
opacity?: number
|
|
24
|
+
blendMode?: string
|
|
21
25
|
}) {
|
|
22
26
|
super({
|
|
23
27
|
...option,
|
|
24
28
|
imageLoadFunction: (image, src) => this._loadImageWithWebGL(image, src)
|
|
25
29
|
})
|
|
26
30
|
|
|
31
|
+
// 옵션에서 값 설정 (기본값 제공)
|
|
32
|
+
this.opacity = option.opacity !== undefined ? option.opacity : 0.8
|
|
33
|
+
this.blendMode = option.blendMode || 'normal'
|
|
34
|
+
|
|
27
35
|
// WebGL 컨텍스트 설정
|
|
28
36
|
this.canvas = document.createElement('canvas')
|
|
29
37
|
this.gl = this.canvas.getContext('webgl', {
|
|
@@ -39,6 +47,30 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
39
47
|
this._initializeWebGL()
|
|
40
48
|
}
|
|
41
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
|
+
|
|
42
74
|
/**
|
|
43
75
|
* WebGL 초기화
|
|
44
76
|
*/
|
|
@@ -76,7 +108,27 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
76
108
|
this.gl.activeTexture(this.gl.TEXTURE0)
|
|
77
109
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)
|
|
78
110
|
this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0)
|
|
79
|
-
|
|
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)
|
|
80
132
|
|
|
81
133
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer)
|
|
82
134
|
const a_position = this.gl.getAttribLocation(this.program, 'a_position')
|
|
@@ -98,6 +150,11 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
98
150
|
|
|
99
151
|
img.onload = () => {
|
|
100
152
|
if (!this.gl) return
|
|
153
|
+
|
|
154
|
+
// 캔버스 크기 설정
|
|
155
|
+
this.canvas!.width = img.width
|
|
156
|
+
this.canvas!.height = img.height
|
|
157
|
+
|
|
101
158
|
this.texture = this.gl.createTexture()
|
|
102
159
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)
|
|
103
160
|
|
|
@@ -136,16 +193,29 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
136
193
|
precision highp float;
|
|
137
194
|
uniform sampler2D u_texture;
|
|
138
195
|
uniform float u_opacity;
|
|
196
|
+
uniform int u_blendMode;
|
|
139
197
|
varying vec2 v_texCoord;
|
|
198
|
+
|
|
140
199
|
void main() {
|
|
141
200
|
vec4 texColor = texture2D(u_texture, v_texCoord);
|
|
201
|
+
|
|
202
|
+
// 완전 투명한 픽셀은 렌더링하지 않음
|
|
142
203
|
if (texColor.a < 0.01) {
|
|
143
204
|
discard;
|
|
144
205
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
vec3
|
|
148
|
-
|
|
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);
|
|
149
219
|
}`
|
|
150
220
|
|
|
151
221
|
const gl = this.gl!
|
|
@@ -178,7 +178,7 @@ export class WeatherPage extends PageView {
|
|
|
178
178
|
// attributions: '© MapTiler'
|
|
179
179
|
attributions: '© OpenStreetMap contributors, © CartoDB'
|
|
180
180
|
}),
|
|
181
|
-
background: '
|
|
181
|
+
background: 'transparent',
|
|
182
182
|
opacity: 1
|
|
183
183
|
})
|
|
184
184
|
|
|
@@ -187,7 +187,7 @@ export class WeatherPage extends PageView {
|
|
|
187
187
|
url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'
|
|
188
188
|
// url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'
|
|
189
189
|
}),
|
|
190
|
-
background: '
|
|
190
|
+
background: 'transparent',
|
|
191
191
|
opacity: 1
|
|
192
192
|
})
|
|
193
193
|
|
|
@@ -206,6 +206,7 @@ export class WeatherPage extends PageView {
|
|
|
206
206
|
style: new Style({
|
|
207
207
|
stroke: new Stroke({
|
|
208
208
|
color: '#858585',
|
|
209
|
+
// color: '#484848',
|
|
209
210
|
width: 1
|
|
210
211
|
})
|
|
211
212
|
})
|
|
@@ -249,15 +250,15 @@ export class WeatherPage extends PageView {
|
|
|
249
250
|
this.map = new Map({
|
|
250
251
|
target: this.renderRoot.querySelector('#map') as HTMLElement,
|
|
251
252
|
pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),
|
|
252
|
-
layers: [lightOSM, heatmapLayer, outlineLayer, labelOSM],
|
|
253
|
+
layers: [lightOSM, /*heatmapLayer, */ outlineLayer, labelOSM],
|
|
253
254
|
view: new View({
|
|
254
255
|
center: fromLonLat([127.5, 36.5]),
|
|
255
256
|
zoom: 7.5
|
|
256
257
|
})
|
|
257
258
|
})
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
this._addWindLayer()
|
|
260
|
+
this._addWeatherLayer()
|
|
261
|
+
// this._addWindLayer()
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
private _addWindLayer() {
|
|
@@ -292,16 +293,45 @@ export class WeatherPage extends PageView {
|
|
|
292
293
|
console.log('🚨 기존 날씨 레이어 제거')
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
// 날씨 타입별 최적 설정
|
|
297
|
+
let opacity, blendMode
|
|
298
|
+
|
|
299
|
+
switch (this.activeType) {
|
|
300
|
+
case 'temperature':
|
|
301
|
+
opacity = 0.75
|
|
302
|
+
blendMode = 'normal'
|
|
303
|
+
break
|
|
304
|
+
case 'humidity':
|
|
305
|
+
opacity = 0.65
|
|
306
|
+
blendMode = 'multiply'
|
|
307
|
+
break
|
|
308
|
+
case 'wind':
|
|
309
|
+
opacity = 0.8
|
|
310
|
+
blendMode = 'screen'
|
|
311
|
+
break
|
|
312
|
+
case 'precipitation':
|
|
313
|
+
opacity = 0.7
|
|
314
|
+
blendMode = 'color-dodge'
|
|
315
|
+
break
|
|
316
|
+
default:
|
|
317
|
+
opacity = 0.7
|
|
318
|
+
blendMode = 'normal'
|
|
319
|
+
}
|
|
320
|
+
|
|
295
321
|
this.weatherLayer = new ImageLayer({
|
|
296
322
|
source: new WebGLImageStatic({
|
|
297
|
-
url: `/weather/map/${this.activeType}.png`,
|
|
323
|
+
url: `/weather/map/${this.activeType}.png?refresh=${Date.now()}`, // 캐시 방지
|
|
324
|
+
// 올바른 지구 투영 비율 사용
|
|
298
325
|
imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],
|
|
299
326
|
interpolation: true,
|
|
300
|
-
projection: 'EPSG:3857'
|
|
301
|
-
|
|
327
|
+
projection: 'EPSG:3857'
|
|
328
|
+
// opacity: opacity,
|
|
329
|
+
// blendMode: blendMode
|
|
330
|
+
}),
|
|
331
|
+
opacity // WebGLImageStatic에서 투명도 처리
|
|
302
332
|
})
|
|
303
333
|
|
|
304
|
-
this.map.
|
|
334
|
+
this.map.getLayers().insertAt(1, this.weatherLayer)
|
|
305
335
|
|
|
306
336
|
console.log('🚨 날씨 레이어 추가', this.map.getLayers())
|
|
307
337
|
}
|
|
@@ -8,6 +8,8 @@ declare class WebGLImageStatic extends ImageStatic {
|
|
|
8
8
|
private program;
|
|
9
9
|
private texture;
|
|
10
10
|
private buffer;
|
|
11
|
+
private opacity;
|
|
12
|
+
private blendMode;
|
|
11
13
|
constructor(option: {
|
|
12
14
|
url: string;
|
|
13
15
|
projection: ProjectionLike;
|
|
@@ -15,7 +17,17 @@ declare class WebGLImageStatic extends ImageStatic {
|
|
|
15
17
|
imageExtent: Extent;
|
|
16
18
|
crossOrigin?: string;
|
|
17
19
|
attributions?: AttributionLike;
|
|
20
|
+
opacity?: number;
|
|
21
|
+
blendMode?: string;
|
|
18
22
|
});
|
|
23
|
+
/**
|
|
24
|
+
* 투명도 설정
|
|
25
|
+
*/
|
|
26
|
+
setOpacity(opacity: number): void;
|
|
27
|
+
/**
|
|
28
|
+
* 블렌드 모드 설정
|
|
29
|
+
*/
|
|
30
|
+
setBlendMode(blendMode: string): void;
|
|
19
31
|
/**
|
|
20
32
|
* WebGL 초기화
|
|
21
33
|
*/
|
|
@@ -7,6 +7,11 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
7
7
|
this.program = null;
|
|
8
8
|
this.texture = null;
|
|
9
9
|
this.buffer = null;
|
|
10
|
+
this.opacity = 0.8;
|
|
11
|
+
this.blendMode = 'normal';
|
|
12
|
+
// 옵션에서 값 설정 (기본값 제공)
|
|
13
|
+
this.opacity = option.opacity !== undefined ? option.opacity : 0.8;
|
|
14
|
+
this.blendMode = option.blendMode || 'normal';
|
|
10
15
|
// WebGL 컨텍스트 설정
|
|
11
16
|
this.canvas = document.createElement('canvas');
|
|
12
17
|
this.gl = this.canvas.getContext('webgl', {
|
|
@@ -19,6 +24,28 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
19
24
|
}
|
|
20
25
|
this._initializeWebGL();
|
|
21
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* 투명도 설정
|
|
29
|
+
*/
|
|
30
|
+
setOpacity(opacity) {
|
|
31
|
+
if (opacity !== this.opacity) {
|
|
32
|
+
this.opacity = opacity;
|
|
33
|
+
if (this.texture) {
|
|
34
|
+
this._renderTexture();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 블렌드 모드 설정
|
|
40
|
+
*/
|
|
41
|
+
setBlendMode(blendMode) {
|
|
42
|
+
if (blendMode !== this.blendMode) {
|
|
43
|
+
this.blendMode = blendMode;
|
|
44
|
+
if (this.texture) {
|
|
45
|
+
this._renderTexture();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
22
49
|
/**
|
|
23
50
|
* WebGL 초기화
|
|
24
51
|
*/
|
|
@@ -51,7 +78,25 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
51
78
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
52
79
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
53
80
|
this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0);
|
|
54
|
-
|
|
81
|
+
// 사용자 지정 투명도 사용
|
|
82
|
+
this.gl.uniform1f(this.gl.getUniformLocation(this.program, 'u_opacity'), this.opacity);
|
|
83
|
+
// 블렌드 모드 설정
|
|
84
|
+
let blendModeValue = 0; // normal
|
|
85
|
+
switch (this.blendMode) {
|
|
86
|
+
case 'multiply':
|
|
87
|
+
blendModeValue = 1;
|
|
88
|
+
break;
|
|
89
|
+
case 'screen':
|
|
90
|
+
blendModeValue = 2;
|
|
91
|
+
break;
|
|
92
|
+
case 'color-dodge':
|
|
93
|
+
blendModeValue = 3;
|
|
94
|
+
break;
|
|
95
|
+
case 'invert':
|
|
96
|
+
blendModeValue = 4;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_blendMode'), blendModeValue);
|
|
55
100
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
56
101
|
const a_position = this.gl.getAttribLocation(this.program, 'a_position');
|
|
57
102
|
const a_texCoord = this.gl.getAttribLocation(this.program, 'a_texCoord');
|
|
@@ -70,6 +115,9 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
70
115
|
img.onload = () => {
|
|
71
116
|
if (!this.gl)
|
|
72
117
|
return;
|
|
118
|
+
// 캔버스 크기 설정
|
|
119
|
+
this.canvas.width = img.width;
|
|
120
|
+
this.canvas.height = img.height;
|
|
73
121
|
this.texture = this.gl.createTexture();
|
|
74
122
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
75
123
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
@@ -101,16 +149,29 @@ class WebGLImageStatic extends ImageStatic {
|
|
|
101
149
|
precision highp float;
|
|
102
150
|
uniform sampler2D u_texture;
|
|
103
151
|
uniform float u_opacity;
|
|
152
|
+
uniform int u_blendMode;
|
|
104
153
|
varying vec2 v_texCoord;
|
|
154
|
+
|
|
105
155
|
void main() {
|
|
106
156
|
vec4 texColor = texture2D(u_texture, v_texCoord);
|
|
157
|
+
|
|
158
|
+
// 완전 투명한 픽셀은 렌더링하지 않음
|
|
107
159
|
if (texColor.a < 0.01) {
|
|
108
160
|
discard;
|
|
109
161
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
vec3
|
|
113
|
-
|
|
162
|
+
|
|
163
|
+
float temperature = texColor.r; // 예: 빨간 채널에 온도 정보가 있다고 가정
|
|
164
|
+
vec3 color;
|
|
165
|
+
|
|
166
|
+
if (temperature < 0.3) {
|
|
167
|
+
color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), temperature / 0.3); // 파란색에서 청록색으로
|
|
168
|
+
} else if (temperature < 0.6) {
|
|
169
|
+
color = mix(vec3(0.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0), (temperature - 0.3) / 0.3); // 청록색에서 녹색으로
|
|
170
|
+
} else {
|
|
171
|
+
color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (temperature - 0.6) / 0.4); // 녹색에서 빨간색으로
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
gl_FragColor = vec4(color, texColor.a * u_opacity);
|
|
114
175
|
}`;
|
|
115
176
|
const gl = this.gl;
|
|
116
177
|
const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexShaderSource);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webgl-image-static.js","sourceRoot":"","sources":["../../client/ol-components/webgl-image-static.ts"],"names":[],"mappings":"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","sourcesContent":["import ImageStatic from 'ol/source/ImageStatic'\nimport ImageWrapper from 'ol/Image'\nimport { Extent } from 'ol/extent'\nimport { ProjectionLike } from 'ol/proj'\nimport { AttributionLike } from 'ol/source/Source'\n\nclass WebGLImageStatic extends ImageStatic {\n private canvas: HTMLCanvasElement | null = null\n private gl: WebGLRenderingContext | null = null\n private program: WebGLProgram | null = null\n private texture: WebGLTexture | null = null\n private buffer: WebGLBuffer | null = null\n\n constructor(option: {\n url: string\n projection: ProjectionLike\n interpolation?: boolean\n imageExtent: Extent\n crossOrigin?: string\n attributions?: AttributionLike\n }) {\n super({\n ...option,\n imageLoadFunction: (image, src) => this._loadImageWithWebGL(image, src)\n })\n\n // WebGL 컨텍스트 설정\n this.canvas = document.createElement('canvas')\n this.gl = this.canvas.getContext('webgl', {\n alpha: true,\n antialias: false,\n depth: false\n }) as WebGLRenderingContext\n\n if (!this.gl) {\n throw new Error('WebGL을 초기화할 수 없습니다.')\n }\n\n this._initializeWebGL()\n }\n\n /**\n * WebGL 초기화\n */\n private _initializeWebGL() {\n this.program = this._createShaderProgram()\n\n if (!this.program) return\n\n this._initializeBuffers()\n }\n\n /**\n * WebGL에서 사용하는 버퍼 설정\n */\n private _initializeBuffers() {\n const vertices = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0])\n const gl = this.gl!\n\n this.buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer)\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)\n }\n\n /**\n * WebGL을 사용하여 렌더링\n */\n private _renderTexture() {\n if (!this.gl || !this.program || !this.texture || !this.buffer) return\n\n this.gl.viewport(0, 0, this.canvas!.width, this.canvas!.height)\n this.gl.clearColor(0, 0, 0, 0)\n this.gl.clear(this.gl.COLOR_BUFFER_BIT)\n this.gl.useProgram(this.program)\n\n this.gl.activeTexture(this.gl.TEXTURE0)\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)\n this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0)\n this.gl.uniform1f(this.gl.getUniformLocation(this.program, 'u_opacity'), 0.8)\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer)\n const a_position = this.gl.getAttribLocation(this.program, 'a_position')\n const a_texCoord = this.gl.getAttribLocation(this.program, 'a_texCoord')\n\n this.gl.enableVertexAttribArray(a_position)\n this.gl.enableVertexAttribArray(a_texCoord)\n this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 16, 0)\n this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 16, 8)\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4)\n }\n\n /**\n * WebGL에서 기후 데이터 이미지를 로드\n */\n private _loadImageWithWebGL(image: ImageWrapper, src: string) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n\n img.onload = () => {\n if (!this.gl) return\n this.texture = this.gl.createTexture()\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)\n\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)\n\n this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img)\n\n this._renderTexture()\n ;(image.getImage() as HTMLImageElement | HTMLVideoElement).src = this.canvas!.toDataURL('image/png')\n\n this.gl = null\n this.canvas = null\n }\n\n img.src = src\n }\n\n /**\n * 셰이더 프로그램 생성\n */\n private _createShaderProgram(): WebGLProgram | null {\n const vertexShaderSource = `\n precision highp float;\n attribute vec4 a_position;\n attribute vec2 a_texCoord;\n varying vec2 v_texCoord;\n void main() {\n gl_Position = a_position;\n v_texCoord = a_texCoord;\n }`\n\n const fragmentShaderSource = `\n precision highp float;\n uniform sampler2D u_texture;\n uniform float u_opacity;\n varying vec2 v_texCoord;\n void main() {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n if (texColor.a < 0.01) {\n discard;\n }\n // gl_FragColor = vec4(texColor.rgb, texColor.a * u_opacity);\n \n vec3 invertedColor = vec3(1.0) - texColor.rgb;\n gl_FragColor = vec4(invertedColor, texColor.a * u_opacity);\n }`\n\n const gl = this.gl!\n const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexShaderSource)\n const fragmentShader = this._compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource)\n if (!vertexShader || !fragmentShader) return null\n\n const program = gl.createProgram()!\n gl.attachShader(program, vertexShader)\n gl.attachShader(program, fragmentShader)\n gl.linkProgram(program)\n\n return gl.getProgramParameter(program, gl.LINK_STATUS) ? program : null\n }\n\n private _compileShader(type: number, source: string): WebGLShader | null {\n const gl = this.gl!\n\n const shader = gl.createShader(type)!\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null\n }\n}\n\nexport default WebGLImageStatic\n"]}
|
|
1
|
+
{"version":3,"file":"webgl-image-static.js","sourceRoot":"","sources":["../../client/ol-components/webgl-image-static.ts"],"names":[],"mappings":"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","sourcesContent":["import ImageStatic from 'ol/source/ImageStatic'\nimport ImageWrapper from 'ol/Image'\nimport { Extent } from 'ol/extent'\nimport { ProjectionLike } from 'ol/proj'\nimport { AttributionLike } from 'ol/source/Source'\n\nclass WebGLImageStatic extends ImageStatic {\n private canvas: HTMLCanvasElement | null = null\n private gl: WebGLRenderingContext | null = null\n private program: WebGLProgram | null = null\n private texture: WebGLTexture | null = null\n private buffer: WebGLBuffer | null = null\n private opacity: number = 0.8\n private blendMode: string = 'normal'\n\n constructor(option: {\n url: string\n projection: ProjectionLike\n interpolation?: boolean\n imageExtent: Extent\n crossOrigin?: string\n attributions?: AttributionLike\n opacity?: number\n blendMode?: string\n }) {\n super({\n ...option,\n imageLoadFunction: (image, src) => this._loadImageWithWebGL(image, src)\n })\n\n // 옵션에서 값 설정 (기본값 제공)\n this.opacity = option.opacity !== undefined ? option.opacity : 0.8\n this.blendMode = option.blendMode || 'normal'\n\n // WebGL 컨텍스트 설정\n this.canvas = document.createElement('canvas')\n this.gl = this.canvas.getContext('webgl', {\n alpha: true,\n antialias: false,\n depth: false\n }) as WebGLRenderingContext\n\n if (!this.gl) {\n throw new Error('WebGL을 초기화할 수 없습니다.')\n }\n\n this._initializeWebGL()\n }\n\n /**\n * 투명도 설정\n */\n public setOpacity(opacity: number): void {\n if (opacity !== this.opacity) {\n this.opacity = opacity\n if (this.texture) {\n this._renderTexture()\n }\n }\n }\n\n /**\n * 블렌드 모드 설정\n */\n public setBlendMode(blendMode: string): void {\n if (blendMode !== this.blendMode) {\n this.blendMode = blendMode\n if (this.texture) {\n this._renderTexture()\n }\n }\n }\n\n /**\n * WebGL 초기화\n */\n private _initializeWebGL() {\n this.program = this._createShaderProgram()\n\n if (!this.program) return\n\n this._initializeBuffers()\n }\n\n /**\n * WebGL에서 사용하는 버퍼 설정\n */\n private _initializeBuffers() {\n const vertices = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0])\n const gl = this.gl!\n\n this.buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer)\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)\n }\n\n /**\n * WebGL을 사용하여 렌더링\n */\n private _renderTexture() {\n if (!this.gl || !this.program || !this.texture || !this.buffer) return\n\n this.gl.viewport(0, 0, this.canvas!.width, this.canvas!.height)\n this.gl.clearColor(0, 0, 0, 0)\n this.gl.clear(this.gl.COLOR_BUFFER_BIT)\n this.gl.useProgram(this.program)\n\n this.gl.activeTexture(this.gl.TEXTURE0)\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)\n this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0)\n\n // 사용자 지정 투명도 사용\n this.gl.uniform1f(this.gl.getUniformLocation(this.program, 'u_opacity'), this.opacity)\n\n // 블렌드 모드 설정\n let blendModeValue = 0 // normal\n switch (this.blendMode) {\n case 'multiply':\n blendModeValue = 1\n break\n case 'screen':\n blendModeValue = 2\n break\n case 'color-dodge':\n blendModeValue = 3\n break\n case 'invert':\n blendModeValue = 4\n break\n }\n this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_blendMode'), blendModeValue)\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer)\n const a_position = this.gl.getAttribLocation(this.program, 'a_position')\n const a_texCoord = this.gl.getAttribLocation(this.program, 'a_texCoord')\n\n this.gl.enableVertexAttribArray(a_position)\n this.gl.enableVertexAttribArray(a_texCoord)\n this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 16, 0)\n this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 16, 8)\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4)\n }\n\n /**\n * WebGL에서 기후 데이터 이미지를 로드\n */\n private _loadImageWithWebGL(image: ImageWrapper, src: string) {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n\n img.onload = () => {\n if (!this.gl) return\n\n // 캔버스 크기 설정\n this.canvas!.width = img.width\n this.canvas!.height = img.height\n\n this.texture = this.gl.createTexture()\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture)\n\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR)\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR)\n\n this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img)\n\n this._renderTexture()\n ;(image.getImage() as HTMLImageElement | HTMLVideoElement).src = this.canvas!.toDataURL('image/png')\n\n this.gl = null\n this.canvas = null\n }\n\n img.src = src\n }\n\n /**\n * 셰이더 프로그램 생성\n */\n private _createShaderProgram(): WebGLProgram | null {\n const vertexShaderSource = `\n precision highp float;\n attribute vec4 a_position;\n attribute vec2 a_texCoord;\n varying vec2 v_texCoord;\n void main() {\n gl_Position = a_position;\n v_texCoord = a_texCoord;\n }`\n\n const fragmentShaderSource = `\n precision highp float;\n uniform sampler2D u_texture;\n uniform float u_opacity;\n uniform int u_blendMode;\n varying vec2 v_texCoord;\n \n void main() {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n \n // 완전 투명한 픽셀은 렌더링하지 않음\n if (texColor.a < 0.01) {\n discard;\n }\n \n float temperature = texColor.r; // 예: 빨간 채널에 온도 정보가 있다고 가정\n vec3 color;\n\n if (temperature < 0.3) {\n color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), temperature / 0.3); // 파란색에서 청록색으로\n } else if (temperature < 0.6) {\n color = mix(vec3(0.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0), (temperature - 0.3) / 0.3); // 청록색에서 녹색으로\n } else {\n color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (temperature - 0.6) / 0.4); // 녹색에서 빨간색으로\n }\n\n gl_FragColor = vec4(color, texColor.a * u_opacity);\n }`\n\n const gl = this.gl!\n const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexShaderSource)\n const fragmentShader = this._compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource)\n if (!vertexShader || !fragmentShader) return null\n\n const program = gl.createProgram()!\n gl.attachShader(program, vertexShader)\n gl.attachShader(program, fragmentShader)\n gl.linkProgram(program)\n\n return gl.getProgramParameter(program, gl.LINK_STATUS) ? program : null\n }\n\n private _compileShader(type: number, source: string): WebGLShader | null {\n const gl = this.gl!\n\n const shader = gl.createShader(type)!\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null\n }\n}\n\nexport default WebGLImageStatic\n"]}
|
|
@@ -160,7 +160,7 @@ let WeatherPage = class WeatherPage extends PageView {
|
|
|
160
160
|
// attributions: '© MapTiler'
|
|
161
161
|
attributions: '© OpenStreetMap contributors, © CartoDB'
|
|
162
162
|
}),
|
|
163
|
-
background: '
|
|
163
|
+
background: 'transparent',
|
|
164
164
|
opacity: 1
|
|
165
165
|
});
|
|
166
166
|
const labelOSM = new TileLayer({
|
|
@@ -168,7 +168,7 @@ let WeatherPage = class WeatherPage extends PageView {
|
|
|
168
168
|
url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'
|
|
169
169
|
// url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'
|
|
170
170
|
}),
|
|
171
|
-
background: '
|
|
171
|
+
background: 'transparent',
|
|
172
172
|
opacity: 1
|
|
173
173
|
});
|
|
174
174
|
const vectorTile = new VectorTile({
|
|
@@ -185,6 +185,7 @@ let WeatherPage = class WeatherPage extends PageView {
|
|
|
185
185
|
style: new Style({
|
|
186
186
|
stroke: new Stroke({
|
|
187
187
|
color: '#858585',
|
|
188
|
+
// color: '#484848',
|
|
188
189
|
width: 1
|
|
189
190
|
})
|
|
190
191
|
})
|
|
@@ -220,14 +221,14 @@ let WeatherPage = class WeatherPage extends PageView {
|
|
|
220
221
|
this.map = new Map({
|
|
221
222
|
target: this.renderRoot.querySelector('#map'),
|
|
222
223
|
pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),
|
|
223
|
-
layers: [lightOSM, heatmapLayer, outlineLayer, labelOSM],
|
|
224
|
+
layers: [lightOSM, /*heatmapLayer, */ outlineLayer, labelOSM],
|
|
224
225
|
view: new View({
|
|
225
226
|
center: fromLonLat([127.5, 36.5]),
|
|
226
227
|
zoom: 7.5
|
|
227
228
|
})
|
|
228
229
|
});
|
|
229
|
-
|
|
230
|
-
this._addWindLayer()
|
|
230
|
+
this._addWeatherLayer();
|
|
231
|
+
// this._addWindLayer()
|
|
231
232
|
}
|
|
232
233
|
_addWindLayer() {
|
|
233
234
|
if (this.windLayer) {
|
|
@@ -255,15 +256,42 @@ let WeatherPage = class WeatherPage extends PageView {
|
|
|
255
256
|
this.map.removeLayer(this.weatherLayer);
|
|
256
257
|
console.log('🚨 기존 날씨 레이어 제거');
|
|
257
258
|
}
|
|
259
|
+
// 날씨 타입별 최적 설정
|
|
260
|
+
let opacity, blendMode;
|
|
261
|
+
switch (this.activeType) {
|
|
262
|
+
case 'temperature':
|
|
263
|
+
opacity = 0.75;
|
|
264
|
+
blendMode = 'normal';
|
|
265
|
+
break;
|
|
266
|
+
case 'humidity':
|
|
267
|
+
opacity = 0.65;
|
|
268
|
+
blendMode = 'multiply';
|
|
269
|
+
break;
|
|
270
|
+
case 'wind':
|
|
271
|
+
opacity = 0.8;
|
|
272
|
+
blendMode = 'screen';
|
|
273
|
+
break;
|
|
274
|
+
case 'precipitation':
|
|
275
|
+
opacity = 0.7;
|
|
276
|
+
blendMode = 'color-dodge';
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
opacity = 0.7;
|
|
280
|
+
blendMode = 'normal';
|
|
281
|
+
}
|
|
258
282
|
this.weatherLayer = new ImageLayer({
|
|
259
283
|
source: new WebGLImageStatic({
|
|
260
|
-
url: `/weather/map/${this.activeType}.png`,
|
|
284
|
+
url: `/weather/map/${this.activeType}.png?refresh=${Date.now()}`, // 캐시 방지
|
|
285
|
+
// 올바른 지구 투영 비율 사용
|
|
261
286
|
imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],
|
|
262
287
|
interpolation: true,
|
|
263
|
-
projection: 'EPSG:3857'
|
|
264
|
-
|
|
288
|
+
projection: 'EPSG:3857'
|
|
289
|
+
// opacity: opacity,
|
|
290
|
+
// blendMode: blendMode
|
|
291
|
+
}),
|
|
292
|
+
opacity // WebGLImageStatic에서 투명도 처리
|
|
265
293
|
});
|
|
266
|
-
this.map.
|
|
294
|
+
this.map.getLayers().insertAt(1, this.weatherLayer);
|
|
267
295
|
console.log('🚨 날씨 레이어 추가', this.map.getLayers());
|
|
268
296
|
}
|
|
269
297
|
_changeWeatherType(type) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"weather-page.js","sourceRoot":"","sources":["../../client/pages/weather-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\n\nimport Map from 'ol/Map.js'\nimport View from 'ol/View.js'\nimport TileLayer from 'ol/layer/Tile.js'\nimport { fromLonLat } from 'ol/proj.js'\nimport 'ol/ol.css'\nimport { MVT } from 'ol/format'\nimport { DEVICE_PIXEL_RATIO } from 'ol/has.js'\nimport Flow from 'ol/layer/Flow.js'\n\nimport ImageLayer from 'ol/layer/Image'\nimport WebGLImageStatic from '../ol-components/webgl-image-static'\nimport { VectorTile, XYZ } from 'ol/source'\nimport VectorSource from 'ol/source/Vector'\nimport colormap from 'colormap'\n\nimport Heatmap from 'ol/layer/Heatmap.js'\nimport Feature from 'ol/Feature.js'\nimport Point from 'ol/geom/Point.js'\nimport { Stroke, Style } from 'ol/style.js'\n\nimport wind from '../ol-components/wind.js'\nimport VectorTileLayer from 'ol/layer/VectorTile'\n\nconst colors = colormap({\n colormap: 'jet',\n nshades: 10,\n alpha: 0.3,\n format: 'rgba'\n})\n\n@customElement('weather-page')\nexport class WeatherPage extends PageView {\n static get styles() {\n return css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n\n .map-container {\n flex: 1;\n display: flex;\n position: relative;\n background-color: #f9f9f9;\n overflow: hidden;\n }\n\n #map {\n flex: 1;\n }\n\n .controls {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: rgba(255, 255, 255, 0.8);\n padding: 15px;\n border-radius: 4px;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n z-index: 10;\n }\n\n .weather-btn {\n cursor: pointer;\n padding: 8px 12px;\n background-color: #f0f0f0;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-weight: bold;\n transition: all 0.3s;\n }\n\n .weather-btn.active {\n background-color: #4281f5;\n color: white;\n border-color: #2d5ed9;\n }\n\n .refresh-btn {\n margin-top: 10px;\n cursor: pointer;\n padding: 8px 12px;\n background-color: #00c851;\n color: white;\n border: none;\n border-radius: 4px;\n font-weight: bold;\n transition: all 0.3s;\n }\n\n .refresh-btn:hover {\n background-color: #00a651;\n }\n `\n }\n\n @property({ type: String }) private activeType = 'temperature'\n @state() private map: any\n @state() private weatherLayer: any\n @state() private windLayer: any\n\n constructor() {\n super()\n }\n\n render() {\n return html`\n <div class=\"map-container\">\n <div id=\"map\"></div>\n\n <div class=\"controls\">\n <button\n class=\"weather-btn ${this.activeType === 'temperature' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('temperature')}\n >\n 온도\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'humidity' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('humidity')}\n >\n 습도\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'wind' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('wind')}\n >\n 바람\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'precipitation' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('precipitation')}\n >\n 강수량\n </button>\n <button class=\"refresh-btn\" @click=${this._refreshWeatherMap}>새로고침</button>\n </div>\n </div>\n `\n }\n\n connectedCallback(): void {\n super.connectedCallback()\n\n requestAnimationFrame(() => {\n this._initMap()\n })\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback()\n\n this.map.removeLayer(this.weatherLayer)\n this.weatherLayer.dispose()\n this.map.dispose()\n\n this.map = null\n this.weatherLayer = null\n }\n\n private _initMap() {\n const lightOSM = new TileLayer({\n source: new XYZ({\n // url: 'https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png',\n url: 'https://{a-c}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'\n // url: 'https://api.maptiler.com/maps/toner-lines/{z}/{x}/{y}.png?key=uHikv5CybbRm7cDLjRca'\n // attributions: '© MapTiler'\n attributions: '© OpenStreetMap contributors, © CartoDB'\n }),\n background: 'tranparent',\n opacity: 1\n })\n\n const labelOSM = new TileLayer({\n source: new XYZ({\n url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'\n }),\n background: 'tranparent',\n opacity: 1\n })\n\n const vectorTile = new VectorTile({\n format: new MVT({\n layerName: 'countries',\n layers: ['countries']\n }),\n url: `/geo/tiles/{z}/{x}/{y}.mvt`,\n maxZoom: 10,\n projection: 'EPSG:3857'\n })\n\n const outlineLayer = new VectorTileLayer({\n source: vectorTile,\n style: new Style({\n stroke: new Stroke({\n color: '#858585',\n width: 1\n })\n })\n })\n\n console.log('🛠 벡터 타일 레이어:', outlineLayer, outlineLayer.getSource())\n\n /* heatmap */\n\n const generateHeatmapData = () => {\n const features: Feature[] = []\n for (let i = 0; i < 20000; i++) {\n // 더 많은 데이터 포인트 생성\n const lon = Math.random() * 360 - 180 // 전 세계 경도 (-180 ~ 180)\n const lat = Math.random() * 170 - 85 // 전 세계 위도 (-85 ~ 85)\n\n const weight = Math.random() * 1 // 가중치 (0 ~ 2)\n\n const feature = new Feature({\n geometry: new Point(fromLonLat([lon, lat])),\n weight: weight\n })\n features.push(feature)\n }\n return features\n }\n\n const vectorSource = new VectorSource({\n features: generateHeatmapData()\n })\n\n const heatmapLayer = new Heatmap({\n source: vectorSource,\n blur: 60, // 흐림 정도 증가\n radius: 60, // 점의 반경 증가\n weight: feature => feature.get('weight'), // 가중치 적용\n gradient: colors.map(c => `rgba(${c.join(',')})`), // 색상 적용\n opacity: 0.8\n })\n\n this.map = new Map({\n target: this.renderRoot.querySelector('#map') as HTMLElement,\n pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),\n layers: [lightOSM, heatmapLayer, outlineLayer, labelOSM],\n view: new View({\n center: fromLonLat([127.5, 36.5]),\n zoom: 7.5\n })\n })\n\n // this._addWeatherLayer()\n this._addWindLayer()\n }\n\n private _addWindLayer() {\n if (this.windLayer) {\n this.map.removeLayer(this.windLayer)\n console.log('🚨 기존 날씨 레이어 제거')\n }\n\n const maxSpeed = 20\n\n const colorStops: (number | [number, number, number, number])[] = []\n for (let i = 0; i < colors.length; ++i) {\n colorStops.push((i * maxSpeed) / (colors.length - 1))\n colorStops.push(colors[i])\n }\n\n this.windLayer = new Flow({\n source: wind,\n maxSpeed,\n particles: 20000,\n style: {\n color: ['interpolate', ['linear'], ['get', 'speed'], ...colorStops]\n }\n })\n\n this.map.addLayer(this.windLayer)\n }\n\n private _addWeatherLayer() {\n if (this.weatherLayer) {\n this.map.removeLayer(this.weatherLayer)\n console.log('🚨 기존 날씨 레이어 제거')\n }\n\n this.weatherLayer = new ImageLayer({\n source: new WebGLImageStatic({\n url: `/weather/map/${this.activeType}.png`,\n imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],\n interpolation: true,\n projection: 'EPSG:3857' // 좌표 시스템 설정\n })\n })\n\n this.map.addLayer(this.weatherLayer)\n\n console.log('🚨 날씨 레이어 추가', this.map.getLayers())\n }\n\n private _changeWeatherType(type: string) {\n if (this.activeType === type) return\n this.activeType = type\n this._addWeatherLayer()\n }\n\n private _refreshWeatherMap() {\n this._addWeatherLayer()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"weather-page.js","sourceRoot":"","sources":["../../client/pages/weather-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\n\nimport Map from 'ol/Map.js'\nimport View from 'ol/View.js'\nimport TileLayer from 'ol/layer/Tile.js'\nimport { fromLonLat } from 'ol/proj.js'\nimport 'ol/ol.css'\nimport { MVT } from 'ol/format'\nimport { DEVICE_PIXEL_RATIO } from 'ol/has.js'\nimport Flow from 'ol/layer/Flow.js'\n\nimport ImageLayer from 'ol/layer/Image'\nimport WebGLImageStatic from '../ol-components/webgl-image-static'\nimport { VectorTile, XYZ } from 'ol/source'\nimport VectorSource from 'ol/source/Vector'\nimport colormap from 'colormap'\n\nimport Heatmap from 'ol/layer/Heatmap.js'\nimport Feature from 'ol/Feature.js'\nimport Point from 'ol/geom/Point.js'\nimport { Stroke, Style } from 'ol/style.js'\n\nimport wind from '../ol-components/wind.js'\nimport VectorTileLayer from 'ol/layer/VectorTile'\n\nconst colors = colormap({\n colormap: 'jet',\n nshades: 10,\n alpha: 0.3,\n format: 'rgba'\n})\n\n@customElement('weather-page')\nexport class WeatherPage extends PageView {\n static get styles() {\n return css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n\n .map-container {\n flex: 1;\n display: flex;\n position: relative;\n background-color: #f9f9f9;\n overflow: hidden;\n }\n\n #map {\n flex: 1;\n }\n\n .controls {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: rgba(255, 255, 255, 0.8);\n padding: 15px;\n border-radius: 4px;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n z-index: 10;\n }\n\n .weather-btn {\n cursor: pointer;\n padding: 8px 12px;\n background-color: #f0f0f0;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-weight: bold;\n transition: all 0.3s;\n }\n\n .weather-btn.active {\n background-color: #4281f5;\n color: white;\n border-color: #2d5ed9;\n }\n\n .refresh-btn {\n margin-top: 10px;\n cursor: pointer;\n padding: 8px 12px;\n background-color: #00c851;\n color: white;\n border: none;\n border-radius: 4px;\n font-weight: bold;\n transition: all 0.3s;\n }\n\n .refresh-btn:hover {\n background-color: #00a651;\n }\n `\n }\n\n @property({ type: String }) private activeType = 'temperature'\n @state() private map: any\n @state() private weatherLayer: any\n @state() private windLayer: any\n\n constructor() {\n super()\n }\n\n render() {\n return html`\n <div class=\"map-container\">\n <div id=\"map\"></div>\n\n <div class=\"controls\">\n <button\n class=\"weather-btn ${this.activeType === 'temperature' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('temperature')}\n >\n 온도\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'humidity' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('humidity')}\n >\n 습도\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'wind' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('wind')}\n >\n 바람\n </button>\n <button\n class=\"weather-btn ${this.activeType === 'precipitation' ? 'active' : ''}\"\n @click=${() => this._changeWeatherType('precipitation')}\n >\n 강수량\n </button>\n <button class=\"refresh-btn\" @click=${this._refreshWeatherMap}>새로고침</button>\n </div>\n </div>\n `\n }\n\n connectedCallback(): void {\n super.connectedCallback()\n\n requestAnimationFrame(() => {\n this._initMap()\n })\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback()\n\n this.map.removeLayer(this.weatherLayer)\n this.weatherLayer.dispose()\n this.map.dispose()\n\n this.map = null\n this.weatherLayer = null\n }\n\n private _initMap() {\n const lightOSM = new TileLayer({\n source: new XYZ({\n // url: 'https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png',\n url: 'https://{a-c}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'\n // url: 'https://api.maptiler.com/maps/toner-lines/{z}/{x}/{y}.png?key=uHikv5CybbRm7cDLjRca'\n // attributions: '© MapTiler'\n attributions: '© OpenStreetMap contributors, © CartoDB'\n }),\n background: 'transparent',\n opacity: 1\n })\n\n const labelOSM = new TileLayer({\n source: new XYZ({\n url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'\n // url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'\n }),\n background: 'transparent',\n opacity: 1\n })\n\n const vectorTile = new VectorTile({\n format: new MVT({\n layerName: 'countries',\n layers: ['countries']\n }),\n url: `/geo/tiles/{z}/{x}/{y}.mvt`,\n maxZoom: 10,\n projection: 'EPSG:3857'\n })\n\n const outlineLayer = new VectorTileLayer({\n source: vectorTile,\n style: new Style({\n stroke: new Stroke({\n color: '#858585',\n // color: '#484848',\n width: 1\n })\n })\n })\n\n console.log('🛠 벡터 타일 레이어:', outlineLayer, outlineLayer.getSource())\n\n /* heatmap */\n\n const generateHeatmapData = () => {\n const features: Feature[] = []\n for (let i = 0; i < 20000; i++) {\n // 더 많은 데이터 포인트 생성\n const lon = Math.random() * 360 - 180 // 전 세계 경도 (-180 ~ 180)\n const lat = Math.random() * 170 - 85 // 전 세계 위도 (-85 ~ 85)\n\n const weight = Math.random() * 1 // 가중치 (0 ~ 2)\n\n const feature = new Feature({\n geometry: new Point(fromLonLat([lon, lat])),\n weight: weight\n })\n features.push(feature)\n }\n return features\n }\n\n const vectorSource = new VectorSource({\n features: generateHeatmapData()\n })\n\n const heatmapLayer = new Heatmap({\n source: vectorSource,\n blur: 60, // 흐림 정도 증가\n radius: 60, // 점의 반경 증가\n weight: feature => feature.get('weight'), // 가중치 적용\n gradient: colors.map(c => `rgba(${c.join(',')})`), // 색상 적용\n opacity: 0.8\n })\n\n this.map = new Map({\n target: this.renderRoot.querySelector('#map') as HTMLElement,\n pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),\n layers: [lightOSM, /*heatmapLayer, */ outlineLayer, labelOSM],\n view: new View({\n center: fromLonLat([127.5, 36.5]),\n zoom: 7.5\n })\n })\n\n this._addWeatherLayer()\n // this._addWindLayer()\n }\n\n private _addWindLayer() {\n if (this.windLayer) {\n this.map.removeLayer(this.windLayer)\n console.log('🚨 기존 날씨 레이어 제거')\n }\n\n const maxSpeed = 20\n\n const colorStops: (number | [number, number, number, number])[] = []\n for (let i = 0; i < colors.length; ++i) {\n colorStops.push((i * maxSpeed) / (colors.length - 1))\n colorStops.push(colors[i])\n }\n\n this.windLayer = new Flow({\n source: wind,\n maxSpeed,\n particles: 20000,\n style: {\n color: ['interpolate', ['linear'], ['get', 'speed'], ...colorStops]\n }\n })\n\n this.map.addLayer(this.windLayer)\n }\n\n private _addWeatherLayer() {\n if (this.weatherLayer) {\n this.map.removeLayer(this.weatherLayer)\n console.log('🚨 기존 날씨 레이어 제거')\n }\n\n // 날씨 타입별 최적 설정\n let opacity, blendMode\n\n switch (this.activeType) {\n case 'temperature':\n opacity = 0.75\n blendMode = 'normal'\n break\n case 'humidity':\n opacity = 0.65\n blendMode = 'multiply'\n break\n case 'wind':\n opacity = 0.8\n blendMode = 'screen'\n break\n case 'precipitation':\n opacity = 0.7\n blendMode = 'color-dodge'\n break\n default:\n opacity = 0.7\n blendMode = 'normal'\n }\n\n this.weatherLayer = new ImageLayer({\n source: new WebGLImageStatic({\n url: `/weather/map/${this.activeType}.png?refresh=${Date.now()}`, // 캐시 방지\n // 올바른 지구 투영 비율 사용\n imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],\n interpolation: true,\n projection: 'EPSG:3857'\n // opacity: opacity,\n // blendMode: blendMode\n }),\n opacity // WebGLImageStatic에서 투명도 처리\n })\n\n this.map.getLayers().insertAt(1, this.weatherLayer)\n\n console.log('🚨 날씨 레이어 추가', this.map.getLayers())\n }\n\n private _changeWeatherType(type: string) {\n if (this.activeType === type) return\n this.activeType = type\n this._addWeatherLayer()\n }\n\n private _refreshWeatherMap() {\n this._addWeatherLayer()\n }\n}\n"]}
|