You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何将matplotlib contourf生成的等值线多边形直接映射到3D球面表面(VPython渲染)

如何将matplotlib contourf生成的等值线多边形直接映射到3D球面表面(VPython渲染)

嘿,我来帮你搞定这个问题!你当前的代码主要是没处理好matplotlib contourf输出的独立闭合等值线区域,还错误地把所有线段的点混在一起生成多边形,导致球面渲染出现一堆缝隙和奇怪的形状。咱们一步步来修正:

先明确你的需求

我尝试用matplotlib的contourf生成温度数据的等值线图,然后想把这些数据映射到3D球面上,用VPython渲染。但现在的问题是多边形没有正确覆盖球面,而且数据有很多缝隙。请告诉我怎么解决?
注意:我不想通过生成纹理再贴到球面上的方式解决,我希望直接把contourf生成的多边形路径转换到球面上。

原代码的核心问题

  • 错误合并等值线区域contours.allsegs里每个温度层级都包含多个独立的闭合线段(比如每个温度区间的闭合区域),但原代码把同一个层级的所有线段点都混在一起,生成了完全错误的多边形,这就是缝隙的主要原因。
  • 颜色映射不准确:原代码用层级索引clev直接取颜色,没有对应到实际的温度范围,导致颜色和温度不匹配。
  • 球面中心计算错误:原代码用平面坐标的中心来三角化,这个中心不在球面上,导致多边形无法贴合球面。

修正后的完整代码

from vpython import vector, triangle, vertex, vec, mag
import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import griddata

def spherical_to_cartesian(lat, lon, radius=3):
    lons = np.radians(lon)
    lats = np.radians(lat)
    x = radius * np.cos(lats) * np.cos(lons)
    y = radius * np.cos(lats) * np.sin(lons)
    z = radius * np.sin(lats)
    return np.array([x, y, z])

# 生成温度数据
shape = (721, 1440)
lats = np.linspace(-90, 90, shape[0])
lons = np.linspace(-180, 180, shape[1])
min_temp = -30
max_temp = 50
temps = np.random.uniform(min_temp, max_temp, size=shape)

# 降采样数据,减少计算量
new_lons = np.linspace(lons.min(), lons.max(), 72)
new_lats = np.linspace(lats.min(), lats.max(), 36)
new_lons_grid, new_lats_grid = np.meshgrid(new_lons, new_lats)

lats_flat = lats.flatten()
lons_flat = lons.flatten()
temps_flat = temps.flatten()

coarse_temps = griddata(
    (lons_flat, lats_flat),
    temps_flat,
    (new_lons_grid, new_lats_grid),
    method='linear'
)

# 设置颜色映射和等值线层级
norm = plt.Normalize(coarse_temps.min(), coarse_temps.max())
cmap = plt.get_cmap("inferno", 10)  # 减少层级数量,避免过多多边形拖慢性能
levels = np.linspace(coarse_temps.min(), coarse_temps.max(), 11)  # 10个温度区间对应11个分界点

# 生成contourf等值线
fig, ax = plt.subplots()
contours = ax.contourf(new_lons_grid, new_lats_grid, coarse_temps, levels=levels)
plt.close(fig)

def create_spherical_polygon(closed_lon_lat_points, temp_color, radius=3):
    # 将经纬度点转换为球面笛卡尔坐标
    sphere_points = []
    for lon, lat in closed_lon_lat_points:
        x, y, z = spherical_to_cartesian(lat, lon, radius)
        sphere_points.append(vec(x, y, z))
    
    # 确保线段是闭合的(contourf的seg理论上已经闭合,这里做个保险)
    if not np.allclose(sphere_points[0].xyz, sphere_points[-1].xyz):
        sphere_points.append(sphere_points[0])
    
    # 点数不足无法生成多边形,直接跳过
    if len(sphere_points) < 3:
        return
    
    # 计算球面区域的中心:取所有点的平均向量,再归一化到球面上
    centroid = vec(0, 0, 0)
    for p in sphere_points:
        centroid += p
    # 归一化到球面半径
    centroid = (centroid / mag(centroid)) * radius
    
    # 用中心辐射法三角化闭合区域,生成贴合球面的三角形
    for i in range(len(sphere_points) - 1):
        v0 = sphere_points[i]
        v1 = sphere_points[i+1]
        triangle(
            v0=vertex(pos=v0, color=temp_color),
            v1=vertex(pos=v1, color=temp_color),
            v2=vertex(pos=centroid, color=temp_color)
        )

# 遍历每个温度层级的等值线
for clev in range(len(contours.allsegs)):
    # 获取当前层级的所有独立闭合线段
    segs = contours.allsegs[clev]
    # 计算当前温度区间的中点,获取对应颜色
    temp_mid = (levels[clev] + levels[clev+1]) / 2
    rgb = cmap(norm(temp_mid))[:3]
    temp_color = vector(*rgb)
    
    # 为每个闭合线段生成球面多边形
    for seg in segs:
        if len(seg) < 3:
            continue
        create_spherical_polygon(seg, temp_color)

# 保持VPython窗口持续渲染
import time
while True:
    time.sleep(0.03)

关键修改说明

  1. 独立处理每个闭合区域:现在会遍历每个温度层级下的每一条独立闭合线段(seg),单独为每个区域生成球面多边形,彻底解决了原代码合并点导致的缝隙问题。
  2. 精准颜色映射:通过计算每个温度区间的中点温度来获取对应颜色,确保颜色和实际温度范围完全匹配,不再用层级索引瞎凑颜色。
  3. 球面中心修正:把原代码的平面中心改成了球面中心——先计算所有点的平均向量,再归一化到球面半径上,这样三角化后的所有三角形都能完美贴合球面。
  4. 优化性能:把等值线层级从100降到10,避免生成过多多边形导致VPython卡顿,同时也让颜色区块更清晰易辨。
  5. 闭合检查:添加了线段闭合的校验,确保每个多边形的起点和终点一致,避免生成不完整的三角形。

你原有的渲染效果(问题示例)

原渲染效果:球面存在大量缝隙,多边形未正确覆盖

备注:内容来源于stack exchange,提问作者John

火山引擎 最新活动