如何转换contourf值并绘制到3d球体的表面

问题描述 投票:0回答:1

我正在尝试使用 matplotlibcontourf 生成温度数据的等高线图。然后我想将这些数据映射到 3d 球体上。我正在使用 vpython 来渲染图像。我遇到的问题是多边形不是在球体表面生成的,而且数据中也存在很多间隙。有人可以解释一下如何实现这一点吗?

请注意,我不希望通过从轮廓生成纹理然后用纹理包裹球体来解决这个问题。我希望平移由contourf生成的多边形路径,并将这些路径直接平移到球体上。

from vpython import vector

import numpy as np
from matplotlib import pyplot as plt

from vpython import triangle, vertex, vec


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)



lons_grid, lats_grid = np.meshgrid(lons, lats)

new_lons = np.linspace(lons.min(), lons.max(), 72)  # 72 points in longitude
new_lats = np.linspace(lats.min(), lats.max(), 36)  # 36 points in latitude
new_lons_grid, new_lats_grid = np.meshgrid(new_lons, new_lats)

radius = 3

lats_flat = lats_grid.flatten()
lons_flat = lons_grid.flatten()
temps_flat = temps.flatten()

coarse_temps = griddata(
    (lons_flat, lats_flat),
    temps_flat,
    (new_lons_grid, new_lats_grid),
    method='linear'  # Use 'linear', 'nearest', or 'cubic' as needed
)


norm = plt.Normalize(coarse_temps.min(), vmax=coarse_temps.max())

cmap = plt.get_cmap("inferno", 100)

levels = 100

levels = np.linspace(coarse_temps.min(), coarse_temps.max(), levels + 1)

bucketed_data = np.digitize(coarse_temps, levels) - 1

fig, ax = plt.subplots()
contours = ax.contourf(new_lons_grid, new_lats_grid, bucketed_data, levels=np.arange(len(levels)))
plt.close(fig)



def create_polygon(region, temp_color):
    if len(region) < 3:
        # Can't form a polygon with fewer than 3 points
        return

    # Calculate the centroid of the region
    centroid = vec(sum(p[0] for p in region) / len(region),
                   sum(p[1] for p in region) / len(region),
                   sum(p[2] for p in region) / len(region))

    # Tessellate the region into triangles
    for i in range(len(region) - 1):
        v0 = vec(*region[i])
        v1 = vec(*region[i + 1])
        triangle(
            v0=vertex(pos=v0, color=temp_color),
            v1=vertex(pos=v1, color=temp_color),
            v2=vertex(pos=centroid, color=temp_color)
        )


    v0 = vec(*region[-1])
    v1 = vec(*region[0])
    triangle(
        v0=vertex(pos=v0, color=temp_color),
        v1=vertex(pos=v1, color=temp_color),
        v2=vertex(pos=centroid, color=temp_color)
    )


def split_contours(segs, kinds=None):
    if kinds is None:
        return segs  # nothing to be done
    new_segs = []
    for i, seg in enumerate(segs):
        segkinds = kinds[i]
        boundaries = [0] + list(np.nonzero(segkinds == 79)[0])
        for b in range(len(boundaries) - 1):
            new_segs.append(seg[boundaries[b] + (1 if b > 0 else 0):boundaries[b + 1]])
    return new_segs



allsegs = contours.allsegs
allkinds = contours.allkinds
colors = cmap(norm(coarse_temps))
rgb_colors = [
    tuple(int(c * 255) for c in color[:3])
    for color in colors.reshape(-1, colors.shape[-1])
]
for clev in range(len(contours.allsegs)):
    kinds = None if allkinds is None else allkinds[clev]
    segs = split_contours(allsegs[clev], kinds)
    rgb = cmap(clev)[:3]
    coords = [spherical_to_cartesian(lat, lon, radius=3) for seg in segs for lon, lat in seg]
    temp_color = vector(*rgb)
    create_polygon(coords, temp_color)



import time
while True:
    time.sleep(0.03)

这就是球体的渲染方式。我希望将轮廓渲染在球体的表面上,就像 2d 轮廓投影到 3d 表面上一样。

enter image description here

python matplotlib
1个回答
0
投票

感谢 Reddit 上的

misho88
,他发布了 this 并建议查看使用
matplotlib.tri.Triangulation
tricontourf
以下作品:

import numpy as np
import matplotlib.tri as tri
from vpython import vector, triangle, vertex, vec
import matplotlib.pyplot as plt


# Function to convert spherical to Cartesian coordinates
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)

lons_grid, lats_grid = np.meshgrid(lons, lats)

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)

radius = 3

lats_flat = lats_grid.flatten()
lons_flat = lons_grid.flatten()
temps_flat = temps.flatten()

# Randomly sample a subset of the points
num_samples = 10000  # Adjust this number to control triangle density
indices = np.random.choice(len(lats_flat), size=num_samples, replace=False)
lats_sampled = lats_flat[indices]
lons_sampled = lons_flat[indices]
temps_sampled = temps_flat[indices]

triang = tri.Triangulation(lons_sampled, lats_sampled)


fig, ax = plt.subplots()
contour = ax.tricontourf(triang, temps_sampled, levels=100, cmap='inferno')
plt.colorbar(contour)
plt.close(fig)

for tri_indices in triang.triangles:

    vertices = []
    for idx in tri_indices:
        xi, yi, zi = spherical_to_cartesian(lats_sampled[idx], lons_sampled[idx])
        temp = temps_sampled[idx]
        color_value = plt.cm.inferno((temp - min_temp) / (max_temp - min_temp))
        vertices.append(vertex(pos=vector(xi, yi, zi), color=vec(*color_value[:3])))
    triangle(v0=vertices[0], v1=vertices[1], v2=vertices[2])


import time
while True:
    time.sleep(0.03)

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.