| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- const fs = require('fs');
- const path = require('path');
- const { createCanvas, ImageData } = require('canvas');
- const sharp = require('sharp');
- // 配置参数
- const config = {
- outputDir: 'G:/tiles/cloud', // 输出目录
- emptyValue: 9.999e+20, // 空值标识
- minZoom: 0, // 最小缩放级别
- maxZoom: 5 // 最大缩放级别
- };
- async function createCloudImageDeep(inputFile) {
- try {
- // 确保输出目录存在
- if (!fs.existsSync(inputFile)) {
- fs.mkdirSync(inputFile, { recursive: true });
- }
- // 解析数据文件
- const { grid, width, height } = parseDataFile(inputFile);
- // 生成各级别瓦片
- for (let zoom = config.minZoom; zoom <= config.maxZoom; zoom++) {
- await generateTiles(grid, width, height, zoom);
- }
- // 生成图例
- generateLegend();
- console.log('瓦片生成完成!');
- console.log(`输出目录: ${path.resolve(config.outputDir)}`);
- } catch (error) {
- console.error('处理过程中发生错误:', error);
- }
- }
- // 读取并解析数据文件
- function parseDataFile(filePath) {
- console.log('正在读取数据文件...');
- const data = fs.readFileSync(filePath, 'utf8').split('\n');
- // 解析第一行获取尺寸信息
- const dimensions = data[0].split(' ').map(Number);
- const dataWidth = dimensions[0];
- const dataHeight = dimensions[1];
- console.log(`数据尺寸: ${dataWidth}x${dataHeight}`);
- // 解析数据值
- const grid = [];
- for (let i = 1; i < data.length; i++) {
- if (data[i].trim() === '') continue;
- const value = parseFloat(data[i]);
- // 处理空值
- grid.push(value === config.emptyValue ? NaN : value);
- }
- console.log(`已读取 ${grid.length} 个数据点`);
- return { grid, width: dataWidth, height: dataHeight };
- }
- // 将经纬度转换为瓦片坐标
- function latLonToTileIndex(lat, lon, zoom) {
- const x = Math.floor((lon + 180) / 360 * Math.pow(2, zoom));
- const y = Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) +
- 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
- return { x, y };
- }
- // 将瓦片坐标转换为经纬度范围
- function tileToLatLon(x, y, zoom) {
- const n = Math.pow(2, zoom);
- const lon1 = x / n * 360 - 180;
- const lat1 = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n))) * 180 / Math.PI;
- const lon2 = (x + 1) / n * 360 - 180;
- const lat2 = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1) / n))) * 180 / Math.PI;
- return {
- west: lon1,
- east: lon2,
- south: lat2,
- north: lat1
- };
- }
- // 生成指定缩放级别的瓦片
- async function generateTiles(data, dataWidth, dataHeight, zoom) {
- console.log(`正在生成第 ${zoom} 级瓦片...`);
- const tileSize = 256;
- const totalTiles = Math.pow(2, zoom);
- // 创建进度跟踪
- let processed = 0;
- const totalToProcess = totalTiles * totalTiles;
- for (let x = 0; x < totalTiles; x++) {
- for (let y = 0; y < totalTiles; y++) {
- const canvas = createCanvas(tileSize, tileSize);
- const ctx = canvas.getContext('2d');
- const imageData = ctx.createImageData(tileSize, tileSize);
- // 获取当前瓦片的经纬度范围
- const bounds = tileToLatLon(x, y, zoom);
- // 为瓦片中的每个像素计算数据值
- for (let py = 0; py < tileSize; py++) {
- for (let px = 0; px < tileSize; px++) {
- // 计算当前像素的经纬度
- const pixelLon = bounds.west + (px / tileSize) * (bounds.east - bounds.west);
- const pixelLat = bounds.south + (py / tileSize) * (bounds.north - bounds.south);
- // 将经纬度转换为数据网格索引
- const dataX = Math.floor((pixelLon + 180) / 360 * dataWidth);
- const dataY = Math.floor((90 - pixelLat) / 180 * dataHeight);
- // 确保索引在有效范围内
- const safeX = Math.max(0, Math.min(dataWidth - 1, dataX));
- const safeY = Math.max(0, Math.min(dataHeight - 1, dataY));
- const dataIndex = safeY * dataWidth + safeX;
- // 获取数据值并处理空值
- let value = data[dataIndex];
- if (isNaN(value)) {
- // 空值处理:设置为完全透明
- setPixel(imageData, px, py, 0, 0, 0, 0);
- } else {
- // 将云量值(0-100)映射到颜色(蓝色到白色)
- const intensity = Math.min(255, Math.max(0, Math.floor(value * 2.55)));
- // 使用蓝色渐变表示云量
- setPixel(imageData, px, py, intensity, intensity, 255, 180);
- }
- }
- }
- ctx.putImageData(imageData, 0, 0);
- // 创建目录并保存瓦片
- const tileDir = path.join(config.outputDir, zoom.toString(), x.toString());
- if (!fs.existsSync(tileDir)) {
- fs.mkdirSync(tileDir, { recursive: true });
- }
- const tilePath = path.join(tileDir, `${y}.png`);
- const buffer = canvas.toBuffer('image/png');
- await sharp(buffer).png().toFile(tilePath);
- // 更新进度
- processed++;
- if (processed % 100 === 0) {
- console.log(`进度: ${Math.round(processed / totalToProcess * 100)}%`);
- }
- }
- }
- }
- // 设置像素颜色
- function setPixel(imageData, x, y, r, g, b, a) {
- const index = (y * imageData.width + x) * 4;
- imageData.data[index] = r;
- imageData.data[index + 1] = g;
- imageData.data[index + 2] = b;
- imageData.data[index + 3] = a;
- }
- // 生成图例
- function generateLegend() {
- const legend = `
- <!DOCTYPE html>
- <html>
- <head>
- <title>云量图例</title>
- <style>
- .legend {
- position: absolute;
- bottom: 20px;
- right: 20px;
- background: white;
- padding: 10px;
- border-radius: 5px;
- box-shadow: 0 0 10px rgba(0,0,0,0.2);
- }
- .color-bar {
- height: 20px;
- width: 200px;
- background: linear-gradient(to right, #0000ff, #ffffff);
- margin-bottom: 5px;
- }
- .labels {
- display: flex;
- justify-content: space-between;
- }
- </style>
- </head>
- <body>
- <div class="legend">
- <div>云量百分比</div>
- <div class="color-bar"></div>
- <div class="labels">
- <span>0%</span>
- <span>100%</span>
- </div>
- </div>
- </body>
- </html>
- `;
- fs.writeFileSync(path.join(config.outputDir, 'legend.html'), legend);
- }
- // 主函数
- async function main() {
- try {
- // 确保输出目录存在
- if (!fs.existsSync(config.outputDir)) {
- fs.mkdirSync(config.outputDir, { recursive: true });
- }
- // 解析数据文件
- const { grid, width, height } = parseDataFile(config.inputFile);
- // 生成各级别瓦片
- for (let zoom = config.minZoom; zoom <= config.maxZoom; zoom++) {
- await generateTiles(grid, width, height, zoom);
- }
- // 生成图例
- generateLegend();
- console.log('瓦片生成完成!');
- console.log(`输出目录: ${path.resolve(config.outputDir)}`);
- } catch (error) {
- console.error('处理过程中发生错误:', error);
- }
- }
- module.exports = {
- createCloudImageDeep
- };
|