You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JavaScript和SVG实现Python版的气压等值线及高低压中心标注图?

用JavaScript + SVG复现MSLP与1000-500hPa厚度高低压符号图

以下是完整实现方案,包含等压线绘制、厚度场可视化、局部极值识别与标注三个核心部分:

1. 基础SVG画布搭建

先创建适配气象图展示的SVG容器:

<svg id="weatherChart" width="800" height="600" viewBox="0 0 800 600"></svg>

2. 模拟气象数据(实际场景替换为真实数据)

这里用网格数据模拟海平面气压(MSLP)和1000-500hPa厚度场,实际使用时可替换为API接口或本地文件导出的真实气象数据:

// 网格参数:30x30覆盖整个SVG画布
const gridSize = 30;
const width = 800;
const height = 600;
const xStep = width / (gridSize - 1);
const yStep = height / (gridSize - 1);

// 生成模拟MSLP数据(单位hPa)
function generateMSLPData() {
  const data = [];
  for (let y = 0; y < gridSize; y++) {
    const row = [];
    for (let x = 0; x < gridSize; x++) {
      // 正弦函数模拟高低压系统+随机噪声
      const base = 1010 + Math.sin(x/5) * 15 + Math.cos(y/7) * 10;
      const noise = (Math.random() - 0.5) * 2;
      row.push(base + noise);
    }
    data.push(row);
  }
  return data;
}

// 生成模拟厚度数据(单位dam)
function generateThicknessData() {
  const data = [];
  for (let y = 0; y < gridSize; y++) {
    const row = [];
    for (let x = 0; x < gridSize; x++) {
      const base = 550 + Math.sin(x/6 + y/8) * 20;
      const noise = (Math.random() - 0.5) * 1.5;
      row.push(base + noise);
    }
    data.push(row);
  }
  return data;
}

const mslpData = generateMSLPData();
const thicknessData = generateThicknessData();

3. 绘制等值线(等压线+厚度线)

采用气象领域常用的移动正方形法生成等值线,实现原生JS的等值线绘制:

// 移动正方形法计算等值线交点
function getContourPoints(data, targetValue) {
  const points = [];
  for (let y = 0; y < gridSize - 1; y++) {
    for (let x = 0; x < gridSize - 1; x++) {
      const vals = [
        data[y][x], data[y][x+1],
        data[y+1][x+1], data[y+1][x]
      ];
      const squareIndex = vals.reduce((idx, val, i) => idx + (val >= targetValue ? (1 << i) : 0), 0);
      
      const x0 = x * xStep;
      const y0 = y * yStep;
      const x1 = (x+1) * xStep;
      const y1 = (y+1) * yStep;
      
      // 简化版等值线交点计算(覆盖常见情况)
      switch(squareIndex) {
        case 1: case 14:
          points.push([x0, interpolateY(vals[3], vals[0], targetValue, y0, y1)]);
          points.push([interpolateX(vals[3], vals[2], targetValue, x0, x1), y1]);
          break;
        case 2: case 13:
          points.push([interpolateX(vals[0], vals[1], targetValue, x0, x1), y0]);
          points.push([x1, interpolateY(vals[1], vals[2], targetValue, y0, y1)]);
          break;
        case 3: case 12:
          points.push([x0, interpolateY(vals[3], vals[0], targetValue, y0, y1)]);
          points.push([x1, interpolateY(vals[1], vals[2], targetValue, y0, y1)]);
          break;
        case 4: case 11:
          points.push([interpolateX(vals[2], vals[3], targetValue, x0, x1), y1]);
          points.push([x1, interpolateY(vals[1], vals[2], targetValue, y0, y1)]);
          break;
        case 5:
          points.push([x0, interpolateY(vals[3], vals[0], targetValue, y0, y1)]);
          points.push([x0, y1]);
          points.push([x1, interpolateY(vals[1], vals[2], targetValue, y0, y1)]);
          break;
        case 6: case 9:
          points.push([interpolateX(vals[0], vals[1], targetValue, x0, x1), y0]);
          points.push([interpolateX(vals[2], vals[3], targetValue, x0, x1), y1]);
          break;
        case 7: case 8:
          points.push([x0, interpolateY(vals[3], vals[0], targetValue, y0, y1)]);
          points.push([interpolateX(vals[0], vals[1], targetValue, x0, x1), y0]);
          break;
      }
    }
  }
  return points;
}

// 线性插值计算X坐标
function interpolateX(v1, v2, target, x0, x1) {
  return x0 + (x1 - x0) * (target - v1) / (v2 - v1);
}

// 线性插值计算Y坐标
function interpolateY(v1, v2, target, y0, y1) {
  return y0 + (y1 - y0) * (target - v1) / (v2 - v1);
}

// 绘制等值线及数值标签
function drawContour(svg, points, color, width, labelValue) {
  if (points.length < 2) return;
  
  let pathStr = `M ${points[0][0]},${points[0][1]}`;
  for (let i = 1; i < points.length; i++) {
    pathStr += ` L ${points[i][0]},${points[i][1]}`;
  }
  
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", pathStr);
  path.setAttribute("stroke", color);
  path.setAttribute("stroke-width", width);
  path.setAttribute("fill", "none");
  svg.appendChild(path);
  
  // 等值线中间位置添加数值标签
  const midIdx = Math.floor(points.length / 2);
  const midPoint = points[midIdx];
  const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
  text.setAttribute("x", midPoint[0]);
  text.setAttribute("y", midPoint[1] - 5);
  text.setAttribute("fill", color);
  text.setAttribute("font-size", "12");
  text.textContent = labelValue;
  svg.appendChild(text);
}

// 绘制MSLP等压线(990-1030hPa,间隔5hPa)
const svg = document.getElementById("weatherChart");
for (let val = 990; val <= 1030; val += 5) {
  const points = getContourPoints(mslpData, val);
  drawContour(svg, points, "#2c3e50", 1.5, val);
}

// 绘制厚度线(530-570dam,间隔10dam)
for (let val = 530; val <= 570; val += 10) {
  const points = getContourPoints(thicknessData, val);
  drawContour(svg, points, "#e74c3c", 1, val);
}

4. 识别高低压中心并标注

通过对比网格点与8邻域点的数值,判断局部极值并添加可视化标注:

// 识别MSLP场的局部高低压极值
function findExtremes(data) {
  const extremes = [];
  const offset = [-1, 0, 1];
  
  for (let y = 1; y < gridSize - 1; y++) {
    for (let x = 1; x < gridSize - 1; x++) {
      const current = data[y][x];
      let isHigh = true;
      let isLow = true;
      
      // 遍历8邻域验证极值
      for (const dy of offset) {
        for (const dx of offset) {
          if (dx === 0 && dy === 0) continue;
          const neighbor = data[y+dy][x+dx];
          if (neighbor > current) isHigh = false;
          if (neighbor < current) isLow = false;
        }
      }
      
      if (isHigh) {
        extremes.push({ type: "high", x: x*xStep, y: y*yStep, value: current });
      } else if (isLow) {
        extremes.push({ type: "low", x: x*xStep, y: y*yStep, value: current });
      }
    }
  }
  return extremes;
}

// 绘制高低压符号及数值标注
function drawExtremes(svg, extremes) {
  extremes.forEach(extreme => {
    const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
    
    // 圆形背景
    const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    circle.setAttribute("cx", extreme.x);
    circle.setAttribute("cy", extreme.y);
    circle.setAttribute("r", 15);
    circle.setAttribute("fill", extreme.type === "high" ? "#f39c12" : "#3498db");
    circle.setAttribute("opacity", 0.8);
    group.appendChild(circle);
    
    // H/L符号
    const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
    text.setAttribute("x", extreme.x);
    text.setAttribute("y", extreme.y + 5);
    text.setAttribute("text-anchor", "middle");
    text.setAttribute("fill", "white");
    text.setAttribute("font-size", "18");
    text.setAttribute("font-weight", "bold");
    text.textContent = extreme.type === "high" ? "H" : "L";
    group.appendChild(text);
    
    // 气压数值标注
    const valueText = document.createElementNS("http://www.w3.org/2000/svg", "text");
    valueText.setAttribute("x", extreme.x);
    valueText.setAttribute("y", extreme.y + 25);
    valueText.setAttribute("text-anchor", "middle");
    valueText.setAttribute("fill", "#2c3e50");
    valueText.setAttribute("font-size", "10");
    valueText.textContent = extreme.value.toFixed(1);
    group.appendChild(valueText);
    
    svg.appendChild(group);
  });
}

// 执行极值识别与标注
const extremes = findExtremes(mslpData);
drawExtremes(svg, extremes);

关键注意事项

  • 真实场景中需替换模拟数据为标准化气象数据(如NetCDF转JSON、气象API返回数据)
  • 移动正方形法为简化实现,如需更精准的等值线,可引入d3-contour等专业库
  • 极值识别的邻域范围可根据数据分辨率调整,避免识别过多伪极值

内容的提问来源于stack exchange,提问作者ATMYDN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 04:03:19