|
|
@@ -0,0 +1,114 @@
|
|
|
+const { createCanvas, loadImage } = require('canvas');
|
|
|
+
|
|
|
+// 温度转颜色(蓝 -> 绿 -> 红)
|
|
|
+function tempToColor(temp) {
|
|
|
+ let r, g, b;
|
|
|
+ if (temp < 15) {
|
|
|
+ r = 0;
|
|
|
+ g = Math.floor((temp - 10) / 5 * 255); // 10~15: 黑->绿
|
|
|
+ b = 255;
|
|
|
+ } else if (temp < 25) {
|
|
|
+ r = Math.floor((temp - 15) / 10 * 255);
|
|
|
+ g = 255;
|
|
|
+ b = 255 - Math.floor((temp - 15) / 10 * 255);
|
|
|
+ } else {
|
|
|
+ r = 255;
|
|
|
+ g = Math.floor((30 - temp) / 5 * 255);
|
|
|
+ b = 0;
|
|
|
+ }
|
|
|
+ console.log(`Temp: ${temp}, Color: [${r}, ${g}, ${b}]`);
|
|
|
+ return [r, g, b];
|
|
|
+}
|
|
|
+
|
|
|
+// 模拟数据集,实际应用中应替换为真实数据
|
|
|
+const data = Array(256).fill().map((_, i) =>
|
|
|
+ Array(256).fill().map((_, j) => {
|
|
|
+ // 创建一个渐变效果,方便测试
|
|
|
+ return 10 + 10 * Math.sin(i / 20) * Math.cos(j / 20);
|
|
|
+ })
|
|
|
+);
|
|
|
+
|
|
|
+function getTileData(data, z, x, y) {
|
|
|
+ const maxTiles = Math.pow(2, z);
|
|
|
+ const totalWidth = data[0].length;
|
|
|
+ const totalHeight = data.length;
|
|
|
+
|
|
|
+ // 每个瓦片应覆盖的像素数(向下取整)
|
|
|
+ const tilePixelWidth = Math.floor(totalWidth / maxTiles);
|
|
|
+ const tilePixelHeight = Math.floor(totalHeight / maxTiles);
|
|
|
+
|
|
|
+ // 计算当前瓦片的像素范围
|
|
|
+ const startX = x * tilePixelWidth;
|
|
|
+ const startY = y * tilePixelHeight;
|
|
|
+ const endX = Math.min(startX + tilePixelWidth, totalWidth);
|
|
|
+ const endY = Math.min(startY + tilePixelHeight, totalHeight);
|
|
|
+
|
|
|
+ // 边界检查
|
|
|
+ if (startY >= totalHeight || startX >= totalWidth) {
|
|
|
+ return Array(256).fill().map(() => Array(256).fill(0)); // 返回空白
|
|
|
+ }
|
|
|
+
|
|
|
+ const tileData = [];
|
|
|
+ for (let i = startY; i < endY; i++) {
|
|
|
+ const row = [];
|
|
|
+ for (let j = startX; j < endX; j++) {
|
|
|
+ row.push(data[i][j]);
|
|
|
+ }
|
|
|
+ tileData.push(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ return tileData;
|
|
|
+}
|
|
|
+
|
|
|
+function renderTile(tileData) {
|
|
|
+ const canvas = createCanvas(256, 256);
|
|
|
+ const ctx = canvas.getContext('2d');
|
|
|
+ const imageData = ctx.createImageData(256, 256);
|
|
|
+ const pixels = imageData.data;
|
|
|
+
|
|
|
+ // 如果 tileData 为空,返回灰色图
|
|
|
+ if (tileData.length === 0 || tileData[0].length === 0) {
|
|
|
+ pixels.fill(128); // 灰色
|
|
|
+ ctx.putImageData(imageData, 0, 0);
|
|
|
+ return canvas.toBuffer('image/png');
|
|
|
+ }
|
|
|
+
|
|
|
+ const dataHeight = tileData.length;
|
|
|
+ const dataWidth = tileData[0].length;
|
|
|
+
|
|
|
+ for (let i = 0; i < 256; i++) {
|
|
|
+ for (let j = 0; j < 256; j++) {
|
|
|
+ // 将 256x256 的像素坐标映射回 tileData 的数据坐标
|
|
|
+ const sourceI = Math.floor((i / 256) * dataHeight);
|
|
|
+ const sourceJ = Math.floor((j / 256) * dataWidth);
|
|
|
+
|
|
|
+ // 边界保护
|
|
|
+ const value = tileData[sourceI]?.[sourceJ] ?? 0; // 空值 fallback 为 0
|
|
|
+
|
|
|
+ const color = tempToColor(value);
|
|
|
+
|
|
|
+ const pixelIdx = (j * 256 + i) * 4;
|
|
|
+ pixels[pixelIdx] = color[0];
|
|
|
+ pixels[pixelIdx + 1] = color[1];
|
|
|
+ pixels[pixelIdx + 2] = color[2];
|
|
|
+ pixels[pixelIdx + 3] = 255;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.putImageData(imageData, 0, 0);
|
|
|
+ return canvas.toBuffer('image/png');
|
|
|
+}
|
|
|
+
|
|
|
+exports.tempapixyz = async (req, res) => {
|
|
|
+ const { z, x, y } = req.params;
|
|
|
+ const zoom = parseInt(z);
|
|
|
+ const tileX = parseInt(x);
|
|
|
+ const tileY = parseInt(y);
|
|
|
+
|
|
|
+ const tileData = await getTileData(data, zoom, tileX, tileY);
|
|
|
+ const buffer = renderTile(tileData);
|
|
|
+
|
|
|
+ res.set('Content-Type', 'image/png');
|
|
|
+ res.set('Cache-Control', 'no-cache'); // 实时数据不缓存
|
|
|
+ res.send(buffer);
|
|
|
+}
|