@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,135 @@
1
+ import ImageStatic from 'ol/source/ImageStatic';
2
+ class WebGLImageStatic extends ImageStatic {
3
+ constructor(option) {
4
+ super(Object.assign(Object.assign({}, option), { imageLoadFunction: (image, src) => this._loadImageWithWebGL(image, src) }));
5
+ this.canvas = null;
6
+ this.gl = null;
7
+ this.program = null;
8
+ this.texture = null;
9
+ this.buffer = null;
10
+ // WebGL 컨텍스트 설정
11
+ this.canvas = document.createElement('canvas');
12
+ this.gl = this.canvas.getContext('webgl', {
13
+ alpha: true,
14
+ antialias: false,
15
+ depth: false
16
+ });
17
+ if (!this.gl) {
18
+ throw new Error('WebGL을 초기화할 수 없습니다.');
19
+ }
20
+ this._initializeWebGL();
21
+ }
22
+ /**
23
+ * WebGL 초기화
24
+ */
25
+ _initializeWebGL() {
26
+ this.program = this._createShaderProgram();
27
+ if (!this.program)
28
+ return;
29
+ this._initializeBuffers();
30
+ }
31
+ /**
32
+ * WebGL에서 사용하는 버퍼 설정
33
+ */
34
+ _initializeBuffers() {
35
+ const vertices = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0]);
36
+ const gl = this.gl;
37
+ this.buffer = gl.createBuffer();
38
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
39
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
40
+ }
41
+ /**
42
+ * WebGL을 사용하여 렌더링
43
+ */
44
+ _renderTexture() {
45
+ if (!this.gl || !this.program || !this.texture || !this.buffer)
46
+ return;
47
+ this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
48
+ this.gl.clearColor(0, 0, 0, 0);
49
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
50
+ this.gl.useProgram(this.program);
51
+ this.gl.activeTexture(this.gl.TEXTURE0);
52
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
53
+ this.gl.uniform1i(this.gl.getUniformLocation(this.program, 'u_texture'), 0);
54
+ this.gl.uniform1f(this.gl.getUniformLocation(this.program, 'u_opacity'), 0.8);
55
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
56
+ const a_position = this.gl.getAttribLocation(this.program, 'a_position');
57
+ const a_texCoord = this.gl.getAttribLocation(this.program, 'a_texCoord');
58
+ this.gl.enableVertexAttribArray(a_position);
59
+ this.gl.enableVertexAttribArray(a_texCoord);
60
+ this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 16, 0);
61
+ this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 16, 8);
62
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
63
+ }
64
+ /**
65
+ * WebGL에서 기후 데이터 이미지를 로드
66
+ */
67
+ _loadImageWithWebGL(image, src) {
68
+ const img = new Image();
69
+ img.crossOrigin = 'anonymous';
70
+ img.onload = () => {
71
+ if (!this.gl)
72
+ return;
73
+ this.texture = this.gl.createTexture();
74
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
75
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
76
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
77
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
78
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
79
+ this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img);
80
+ this._renderTexture();
81
+ image.getImage().src = this.canvas.toDataURL('image/png');
82
+ this.gl = null;
83
+ this.canvas = null;
84
+ };
85
+ img.src = src;
86
+ }
87
+ /**
88
+ * 셰이더 프로그램 생성
89
+ */
90
+ _createShaderProgram() {
91
+ const vertexShaderSource = `
92
+ precision highp float;
93
+ attribute vec4 a_position;
94
+ attribute vec2 a_texCoord;
95
+ varying vec2 v_texCoord;
96
+ void main() {
97
+ gl_Position = a_position;
98
+ v_texCoord = a_texCoord;
99
+ }`;
100
+ const fragmentShaderSource = `
101
+ precision highp float;
102
+ uniform sampler2D u_texture;
103
+ uniform float u_opacity;
104
+ varying vec2 v_texCoord;
105
+ void main() {
106
+ vec4 texColor = texture2D(u_texture, v_texCoord);
107
+ if (texColor.a < 0.01) {
108
+ discard;
109
+ }
110
+ // gl_FragColor = vec4(texColor.rgb, texColor.a * u_opacity);
111
+
112
+ vec3 invertedColor = vec3(1.0) - texColor.rgb;
113
+ gl_FragColor = vec4(invertedColor, texColor.a * u_opacity);
114
+ }`;
115
+ const gl = this.gl;
116
+ const vertexShader = this._compileShader(gl.VERTEX_SHADER, vertexShaderSource);
117
+ const fragmentShader = this._compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
118
+ if (!vertexShader || !fragmentShader)
119
+ return null;
120
+ const program = gl.createProgram();
121
+ gl.attachShader(program, vertexShader);
122
+ gl.attachShader(program, fragmentShader);
123
+ gl.linkProgram(program);
124
+ return gl.getProgramParameter(program, gl.LINK_STATUS) ? program : null;
125
+ }
126
+ _compileShader(type, source) {
127
+ const gl = this.gl;
128
+ const shader = gl.createShader(type);
129
+ gl.shaderSource(shader, source);
130
+ gl.compileShader(shader);
131
+ return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null;
132
+ }
133
+ }
134
+ export default WebGLImageStatic;
135
+ //# sourceMappingURL=webgl-image-static.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,3 @@
1
+ import DataTileSource from 'ol/source/DataTile.js';
2
+ declare const wind: DataTileSource<import("ol/DataTile").default>;
3
+ export default wind;
@@ -0,0 +1,104 @@
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
+ const windData = new Promise((resolve, reject) => {
5
+ const image = new Image();
6
+ image.onload = () => {
7
+ const canvas = document.createElement('canvas');
8
+ const width = image.width;
9
+ const height = image.height;
10
+ canvas.width = width;
11
+ canvas.height = height;
12
+ const context = canvas.getContext('2d');
13
+ context.drawImage(image, 0, 0);
14
+ const data = context.getImageData(0, 0, width, height).data;
15
+ resolve({ data, width, height });
16
+ };
17
+ image.onerror = () => {
18
+ reject(new Error('failed to load'));
19
+ };
20
+ image.src = '/assets/images/wind.png';
21
+ });
22
+ function bilinearInterpolation(xAlong, yAlong, v11, v21, v12, v22) {
23
+ const q11 = (1 - xAlong) * (1 - yAlong) * v11;
24
+ const q21 = xAlong * (1 - yAlong) * v21;
25
+ const q12 = (1 - xAlong) * yAlong * v12;
26
+ const q22 = xAlong * yAlong * v22;
27
+ return q11 + q21 + q12 + q22;
28
+ }
29
+ function interpolatePixels(xAlong, yAlong, p11, p21, p12, p22) {
30
+ return p11.map((_, i) => bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]));
31
+ }
32
+ const dataTileGrid = createXYZ();
33
+ const dataTileSize = 256;
34
+ const inputImageProjection = getProjection('EPSG:4326');
35
+ const dataTileProjection = getProjection('EPSG:3857');
36
+ const inputBands = 4;
37
+ const dataBands = 3;
38
+ // range of wind velocities
39
+ // these values are stretched between 0 and 255 in the png
40
+ const minU = -21.32;
41
+ const maxU = 26.8;
42
+ const deltaU = maxU - minU;
43
+ const minV = -21.57;
44
+ const maxV = 21.42;
45
+ const deltaV = maxV - minV;
46
+ const wind = new DataTileSource({
47
+ // transition must be 0, see https://github.com/openlayers/openlayers/issues/16119
48
+ transition: 0,
49
+ wrapX: true,
50
+ async loader(z, x, y) {
51
+ const { data: inputData, width: inputWidth, height: inputHeight } = await windData;
52
+ const tileCoord = wrapX(dataTileGrid, [z, x, y], dataTileProjection);
53
+ const extent = dataTileGrid.getTileCoordExtent(tileCoord);
54
+ const resolution = dataTileGrid.getResolution(z);
55
+ const data = new Float32Array(dataTileSize * dataTileSize * dataBands);
56
+ for (let row = 0; row < dataTileSize; ++row) {
57
+ let offset = row * dataTileSize * dataBands;
58
+ const mapY = extent[3] - row * resolution;
59
+ for (let col = 0; col < dataTileSize; ++col) {
60
+ const mapX = extent[0] + col * resolution;
61
+ const [lon, lat] = transform([mapX, mapY], dataTileProjection, inputImageProjection);
62
+ const x = (inputWidth * (lon + 180)) / 360;
63
+ let x1 = Math.floor(x);
64
+ let x2 = Math.ceil(x);
65
+ const xAlong = x - x1;
66
+ if (x1 < 0) {
67
+ x1 += inputWidth;
68
+ }
69
+ if (x2 >= inputWidth) {
70
+ x2 -= inputWidth;
71
+ }
72
+ const y = (inputHeight * (90 - lat)) / 180;
73
+ let y1 = Math.floor(y);
74
+ let y2 = Math.ceil(y);
75
+ const yAlong = y - y1;
76
+ if (y1 < 0) {
77
+ y1 = 0;
78
+ }
79
+ if (y2 >= inputHeight) {
80
+ y2 = inputHeight - 1;
81
+ }
82
+ const corners = [
83
+ [x1, y1],
84
+ [x2, y1],
85
+ [x1, y2],
86
+ [x2, y2]
87
+ ];
88
+ const pixels = corners.map(([cx, cy]) => {
89
+ const inputOffset = (cy * 360 + cx) * inputBands;
90
+ return [inputData[inputOffset], inputData[inputOffset + 1]];
91
+ });
92
+ const interpolated = interpolatePixels(xAlong, yAlong, ...pixels);
93
+ const u = minU + (deltaU * interpolated[0]) / 255;
94
+ const v = minV + (deltaV * interpolated[1]) / 255;
95
+ data[offset] = u;
96
+ data[offset + 1] = v;
97
+ offset += dataBands;
98
+ }
99
+ }
100
+ return data;
101
+ }
102
+ });
103
+ export default wind;
104
+ //# sourceMappingURL=wind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wind.js","sourceRoot":"","sources":["../../client/ol-components/wind.ts"],"names":[],"mappings":"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","sourcesContent":["import { get as getProjection, transform } from 'ol/proj.js'\nimport DataTileSource from 'ol/source/DataTile.js'\nimport { createXYZ, wrapX } from 'ol/tilegrid.js'\n\nconst windData: Promise<{ data: ArrayBuffer; width: number; height: number }> = new Promise((resolve, reject) => {\n const image = new Image()\n image.onload = () => {\n const canvas = document.createElement('canvas')\n const width = image.width\n const height = image.height\n canvas.width = width\n canvas.height = height\n\n const context = canvas.getContext('2d')!\n context.drawImage(image, 0, 0)\n const data = context.getImageData(0, 0, width, height).data\n resolve({ data, width, height })\n }\n image.onerror = () => {\n reject(new Error('failed to load'))\n }\n image.src = '/assets/images/wind.png'\n})\n\nfunction bilinearInterpolation(xAlong, yAlong, v11, v21, v12, v22) {\n const q11 = (1 - xAlong) * (1 - yAlong) * v11\n const q21 = xAlong * (1 - yAlong) * v21\n const q12 = (1 - xAlong) * yAlong * v12\n const q22 = xAlong * yAlong * v22\n return q11 + q21 + q12 + q22\n}\n\nfunction interpolatePixels(xAlong, yAlong, p11, p21, p12, p22) {\n return p11.map((_, i) => bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]))\n}\n\nconst dataTileGrid = createXYZ()\nconst dataTileSize = 256\n\nconst inputImageProjection = getProjection('EPSG:4326')!\nconst dataTileProjection = getProjection('EPSG:3857')!\n\nconst inputBands = 4\nconst dataBands = 3\n\n// range of wind velocities\n// these values are stretched between 0 and 255 in the png\nconst minU = -21.32\nconst maxU = 26.8\nconst deltaU = maxU - minU\nconst minV = -21.57\nconst maxV = 21.42\nconst deltaV = maxV - minV\n\nconst wind = new DataTileSource({\n // transition must be 0, see https://github.com/openlayers/openlayers/issues/16119\n transition: 0,\n wrapX: true,\n async loader(z, x, y) {\n const { data: inputData, width: inputWidth, height: inputHeight } = await windData\n\n const tileCoord = wrapX(dataTileGrid, [z, x, y], dataTileProjection)\n const extent = dataTileGrid.getTileCoordExtent(tileCoord)\n const resolution = dataTileGrid.getResolution(z)\n const data = new Float32Array(dataTileSize * dataTileSize * dataBands)\n for (let row = 0; row < dataTileSize; ++row) {\n let offset = row * dataTileSize * dataBands\n const mapY = extent[3] - row * resolution\n for (let col = 0; col < dataTileSize; ++col) {\n const mapX = extent[0] + col * resolution\n const [lon, lat] = transform([mapX, mapY], dataTileProjection, inputImageProjection)\n\n const x = (inputWidth * (lon + 180)) / 360\n let x1 = Math.floor(x)\n let x2 = Math.ceil(x)\n const xAlong = x - x1\n if (x1 < 0) {\n x1 += inputWidth\n }\n if (x2 >= inputWidth) {\n x2 -= inputWidth\n }\n\n const y = (inputHeight * (90 - lat)) / 180\n let y1 = Math.floor(y)\n let y2 = Math.ceil(y)\n const yAlong = y - y1\n if (y1 < 0) {\n y1 = 0\n }\n if (y2 >= inputHeight) {\n y2 = inputHeight - 1\n }\n\n const corners = [\n [x1, y1],\n [x2, y1],\n [x1, y2],\n [x2, y2]\n ]\n\n const pixels = corners.map(([cx, cy]) => {\n const inputOffset = (cy * 360 + cx) * inputBands\n return [inputData[inputOffset], inputData[inputOffset + 1]]\n })\n\n const interpolated = interpolatePixels(xAlong, yAlong, ...(pixels as [any, any, any, any]))\n const u = minU + (deltaU * interpolated[0]) / 255\n const v = minV + (deltaV * interpolated[1]) / 255\n\n data[offset] = u\n data[offset + 1] = v\n offset += dataBands\n }\n }\n return data\n }\n})\n\nexport default wind\n"]}
@@ -0,0 +1,18 @@
1
+ import { PageView } from '@operato/shell';
2
+ import 'ol/ol.css';
3
+ export declare class WeatherPage extends PageView {
4
+ static get styles(): import("lit").CSSResult;
5
+ private activeType;
6
+ private map;
7
+ private weatherLayer;
8
+ private windLayer;
9
+ constructor();
10
+ render(): import("lit-html").TemplateResult<1>;
11
+ connectedCallback(): void;
12
+ disconnectedCallback(): void;
13
+ private _initMap;
14
+ private _addWindLayer;
15
+ private _addWeatherLayer;
16
+ private _changeWeatherType;
17
+ private _refreshWeatherMap;
18
+ }
@@ -0,0 +1,300 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { css, html } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { PageView } from '@operato/shell';
5
+ import Map from 'ol/Map.js';
6
+ import View from 'ol/View.js';
7
+ import TileLayer from 'ol/layer/Tile.js';
8
+ import { fromLonLat } from 'ol/proj.js';
9
+ import 'ol/ol.css';
10
+ import { MVT } from 'ol/format';
11
+ import { DEVICE_PIXEL_RATIO } from 'ol/has.js';
12
+ import Flow from 'ol/layer/Flow.js';
13
+ import ImageLayer from 'ol/layer/Image';
14
+ import WebGLImageStatic from '../ol-components/webgl-image-static';
15
+ import { VectorTile, XYZ } from 'ol/source';
16
+ import VectorSource from 'ol/source/Vector';
17
+ import colormap from 'colormap';
18
+ import Heatmap from 'ol/layer/Heatmap.js';
19
+ import Feature from 'ol/Feature.js';
20
+ import Point from 'ol/geom/Point.js';
21
+ import { Stroke, Style } from 'ol/style.js';
22
+ import wind from '../ol-components/wind.js';
23
+ import VectorTileLayer from 'ol/layer/VectorTile';
24
+ const colors = colormap({
25
+ colormap: 'jet',
26
+ nshades: 10,
27
+ alpha: 0.3,
28
+ format: 'rgba'
29
+ });
30
+ let WeatherPage = class WeatherPage extends PageView {
31
+ static get styles() {
32
+ return css `
33
+ :host {
34
+ display: flex;
35
+ flex-direction: column;
36
+ height: 100%;
37
+ }
38
+
39
+ .map-container {
40
+ flex: 1;
41
+ display: flex;
42
+ position: relative;
43
+ background-color: #f9f9f9;
44
+ overflow: hidden;
45
+ }
46
+
47
+ #map {
48
+ flex: 1;
49
+ }
50
+
51
+ .controls {
52
+ position: absolute;
53
+ top: 10px;
54
+ right: 10px;
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 10px;
58
+ background: rgba(255, 255, 255, 0.8);
59
+ padding: 15px;
60
+ border-radius: 4px;
61
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
62
+ z-index: 10;
63
+ }
64
+
65
+ .weather-btn {
66
+ cursor: pointer;
67
+ padding: 8px 12px;
68
+ background-color: #f0f0f0;
69
+ border: 1px solid #ddd;
70
+ border-radius: 4px;
71
+ font-weight: bold;
72
+ transition: all 0.3s;
73
+ }
74
+
75
+ .weather-btn.active {
76
+ background-color: #4281f5;
77
+ color: white;
78
+ border-color: #2d5ed9;
79
+ }
80
+
81
+ .refresh-btn {
82
+ margin-top: 10px;
83
+ cursor: pointer;
84
+ padding: 8px 12px;
85
+ background-color: #00c851;
86
+ color: white;
87
+ border: none;
88
+ border-radius: 4px;
89
+ font-weight: bold;
90
+ transition: all 0.3s;
91
+ }
92
+
93
+ .refresh-btn:hover {
94
+ background-color: #00a651;
95
+ }
96
+ `;
97
+ }
98
+ constructor() {
99
+ super();
100
+ this.activeType = 'temperature';
101
+ }
102
+ render() {
103
+ return html `
104
+ <div class="map-container">
105
+ <div id="map"></div>
106
+
107
+ <div class="controls">
108
+ <button
109
+ class="weather-btn ${this.activeType === 'temperature' ? 'active' : ''}"
110
+ @click=${() => this._changeWeatherType('temperature')}
111
+ >
112
+ 온도
113
+ </button>
114
+ <button
115
+ class="weather-btn ${this.activeType === 'humidity' ? 'active' : ''}"
116
+ @click=${() => this._changeWeatherType('humidity')}
117
+ >
118
+ 습도
119
+ </button>
120
+ <button
121
+ class="weather-btn ${this.activeType === 'wind' ? 'active' : ''}"
122
+ @click=${() => this._changeWeatherType('wind')}
123
+ >
124
+ 바람
125
+ </button>
126
+ <button
127
+ class="weather-btn ${this.activeType === 'precipitation' ? 'active' : ''}"
128
+ @click=${() => this._changeWeatherType('precipitation')}
129
+ >
130
+ 강수량
131
+ </button>
132
+ <button class="refresh-btn" @click=${this._refreshWeatherMap}>새로고침</button>
133
+ </div>
134
+ </div>
135
+ `;
136
+ }
137
+ connectedCallback() {
138
+ super.connectedCallback();
139
+ requestAnimationFrame(() => {
140
+ this._initMap();
141
+ });
142
+ }
143
+ disconnectedCallback() {
144
+ super.disconnectedCallback();
145
+ this.map.removeLayer(this.weatherLayer);
146
+ this.weatherLayer.dispose();
147
+ this.map.dispose();
148
+ this.map = null;
149
+ this.weatherLayer = null;
150
+ }
151
+ _initMap() {
152
+ const lightOSM = new TileLayer({
153
+ source: new XYZ({
154
+ // url: 'https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png',
155
+ // url: 'https://{a-c}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png',
156
+ url: 'https://{a-c}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
157
+ // url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
158
+ // url: 'https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
159
+ // url: 'https://api.maptiler.com/maps/toner-lines/{z}/{x}/{y}.png?key=uHikv5CybbRm7cDLjRca'
160
+ // attributions: '© MapTiler'
161
+ attributions: '© OpenStreetMap contributors, © CartoDB'
162
+ }),
163
+ background: 'tranparent',
164
+ opacity: 1
165
+ });
166
+ const labelOSM = new TileLayer({
167
+ source: new XYZ({
168
+ url: 'https://{a-c}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png'
169
+ // url: 'https://{a-c}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png'
170
+ }),
171
+ background: 'tranparent',
172
+ opacity: 1
173
+ });
174
+ const vectorTile = new VectorTile({
175
+ format: new MVT({
176
+ layerName: 'countries',
177
+ layers: ['countries']
178
+ }),
179
+ url: `/geo/tiles/{z}/{x}/{y}.mvt`,
180
+ maxZoom: 10,
181
+ projection: 'EPSG:3857'
182
+ });
183
+ const outlineLayer = new VectorTileLayer({
184
+ source: vectorTile,
185
+ style: new Style({
186
+ stroke: new Stroke({
187
+ color: '#858585',
188
+ width: 1
189
+ })
190
+ })
191
+ });
192
+ console.log('🛠 벡터 타일 레이어:', outlineLayer, outlineLayer.getSource());
193
+ /* heatmap */
194
+ const generateHeatmapData = () => {
195
+ const features = [];
196
+ for (let i = 0; i < 20000; i++) {
197
+ // 더 많은 데이터 포인트 생성
198
+ const lon = Math.random() * 360 - 180; // 전 세계 경도 (-180 ~ 180)
199
+ const lat = Math.random() * 170 - 85; // 전 세계 위도 (-85 ~ 85)
200
+ const weight = Math.random() * 1; // 가중치 (0 ~ 2)
201
+ const feature = new Feature({
202
+ geometry: new Point(fromLonLat([lon, lat])),
203
+ weight: weight
204
+ });
205
+ features.push(feature);
206
+ }
207
+ return features;
208
+ };
209
+ const vectorSource = new VectorSource({
210
+ features: generateHeatmapData()
211
+ });
212
+ const heatmapLayer = new Heatmap({
213
+ source: vectorSource,
214
+ blur: 60, // 흐림 정도 증가
215
+ radius: 60, // 점의 반경 증가
216
+ weight: feature => feature.get('weight'), // 가중치 적용
217
+ gradient: colors.map(c => `rgba(${c.join(',')})`), // 색상 적용
218
+ opacity: 0.8
219
+ });
220
+ this.map = new Map({
221
+ target: this.renderRoot.querySelector('#map'),
222
+ pixelRatio: Math.min(DEVICE_PIXEL_RATIO, 2),
223
+ layers: [lightOSM, heatmapLayer, outlineLayer, labelOSM],
224
+ view: new View({
225
+ center: fromLonLat([127.5, 36.5]),
226
+ zoom: 7.5
227
+ })
228
+ });
229
+ // this._addWeatherLayer()
230
+ this._addWindLayer();
231
+ }
232
+ _addWindLayer() {
233
+ if (this.windLayer) {
234
+ this.map.removeLayer(this.windLayer);
235
+ console.log('🚨 기존 날씨 레이어 제거');
236
+ }
237
+ const maxSpeed = 20;
238
+ const colorStops = [];
239
+ for (let i = 0; i < colors.length; ++i) {
240
+ colorStops.push((i * maxSpeed) / (colors.length - 1));
241
+ colorStops.push(colors[i]);
242
+ }
243
+ this.windLayer = new Flow({
244
+ source: wind,
245
+ maxSpeed,
246
+ particles: 20000,
247
+ style: {
248
+ color: ['interpolate', ['linear'], ['get', 'speed'], ...colorStops]
249
+ }
250
+ });
251
+ this.map.addLayer(this.windLayer);
252
+ }
253
+ _addWeatherLayer() {
254
+ if (this.weatherLayer) {
255
+ this.map.removeLayer(this.weatherLayer);
256
+ console.log('🚨 기존 날씨 레이어 제거');
257
+ }
258
+ this.weatherLayer = new ImageLayer({
259
+ source: new WebGLImageStatic({
260
+ url: `/weather/map/${this.activeType}.png`,
261
+ imageExtent: [-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],
262
+ interpolation: true,
263
+ projection: 'EPSG:3857' // 좌표 시스템 설정
264
+ })
265
+ });
266
+ this.map.addLayer(this.weatherLayer);
267
+ console.log('🚨 날씨 레이어 추가', this.map.getLayers());
268
+ }
269
+ _changeWeatherType(type) {
270
+ if (this.activeType === type)
271
+ return;
272
+ this.activeType = type;
273
+ this._addWeatherLayer();
274
+ }
275
+ _refreshWeatherMap() {
276
+ this._addWeatherLayer();
277
+ }
278
+ };
279
+ __decorate([
280
+ property({ type: String }),
281
+ __metadata("design:type", Object)
282
+ ], WeatherPage.prototype, "activeType", void 0);
283
+ __decorate([
284
+ state(),
285
+ __metadata("design:type", Object)
286
+ ], WeatherPage.prototype, "map", void 0);
287
+ __decorate([
288
+ state(),
289
+ __metadata("design:type", Object)
290
+ ], WeatherPage.prototype, "weatherLayer", void 0);
291
+ __decorate([
292
+ state(),
293
+ __metadata("design:type", Object)
294
+ ], WeatherPage.prototype, "windLayer", void 0);
295
+ WeatherPage = __decorate([
296
+ customElement('weather-page'),
297
+ __metadata("design:paramtypes", [])
298
+ ], WeatherPage);
299
+ export { WeatherPage };
300
+ //# sourceMappingURL=weather-page.js.map
@@ -0,0 +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"]}
@@ -0,0 +1 @@
1
+ export default function route(page: string): "weather-page" | undefined;
@@ -0,0 +1,8 @@
1
+ export default function route(page) {
2
+ switch (page) {
3
+ case 'weather-page':
4
+ import('./pages/weather-page.js');
5
+ return page;
6
+ }
7
+ }
8
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,cAAc;YACjB,MAAM,CAAC,yBAAyB,CAAC,CAAA;YACjC,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'weather-page':\n import('./pages/weather-page.js')\n return page\n }\n}\n"]}