Matplotlib绘制3D散点图z=0平面图像时无法消除全部空白
问题描述
需要在3D散点图的z=0平面上绘制Cartopy地图作为基底。已通过2D图生成了范围匹配的地图,但将其转为numpy数组贴到3D平面时,地图范围正确但侧边存在空白,无法完全覆盖z=0平面。
原因分析
- 2D画布的宽高比与地理范围(经纬度跨度)的纵横比不匹配,导致生成的地图图像本身存在留白
- 2D图的布局设置未彻底消除所有边距
- 3D表面绘制时的纹理映射未完全对齐地理范围
解决方案
1. 匹配画布与地理范围的纵横比
计算经纬度范围的宽高比,以此设置2D画布的尺寸,确保地图完全填充画布,无额外留白。
2. 彻底清除2D图的边距与边框
强化布局设置,确保2D图的轴完全填充画布,无任何边距。
3. 优化3D表面的纹理绘制
确保plot_surface使用的网格与地图图像的像素尺寸严格对应,避免纹理拉伸或错位。
修改后的完整代码
import matplotlib.pyplot as plt import numpy as np import cartopy.crs as ccrs import cartopy.io.img_tiles as cimgt import os import pandas as pd # 假设data_ts、cmin、cmax、output_path、flight已提前定义 # Create a new figure fig = plt.figure(figsize=(12, 8), layout='tight') ax = fig.add_subplot(111, projection='3d', computed_zorder=False) # Define the map boundaries min_lat, max_lat = np.round(data_ts['Lat'].min() - 0.1, 5), np.round(data_ts['Lat'].max() + 0.1, 5) min_lon, max_lon = np.round(data_ts['Lon'].min() - 0.1, 5), np.round(data_ts['Lon'].max() + 0.1, 5) print(min_lat, max_lat, min_lon, max_lon) # Set up the map tiles tiles = cimgt.GoogleTiles() tiler = tiles.crs # 匹配画布与地理范围的纵横比 lon_span = max_lon - min_lon lat_span = max_lat - min_lat aspect_ratio = lon_span / lat_span fig_height = 10 fig_width = fig_height * aspect_ratio fig_2d = plt.figure(figsize=(fig_width, fig_height), frameon=False) ax_2d = fig_2d.add_subplot(111, projection=tiler) ax_2d.set_axis_off() # 彻底清除边距 plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) ax_2d.margins(0) ax_2d.set_extent([min_lon, max_lon, min_lat, max_lat], crs=ccrs.PlateCarree()) ax_2d.add_image(tiles, 12) # Zoom level # 无边距保存图像 fig_2d.savefig(os.path.join(output_path,f"{flight}.2d3d.tif"), format='tif',dpi=300, bbox_inches='tight', pad_inches=0) # Extract the map image from the 2D plot fig_2d.canvas.draw() map_img = np.array(fig_2d.canvas.renderer.buffer_rgba()) map_img = np.flipud(map_img) # Convert the map image to a texture x = np.linspace(min_lon, max_lon, map_img.shape[1]) y = np.linspace(min_lat, max_lat, map_img.shape[0]) print(y.min(), y.max(), x.min(),x.max()) x, y = np.meshgrid(x, y) z = np.zeros_like(x) # Plot the texture on the z=0 plane ax.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=map_img / 255, zorder=1) # Plot the 3D scatter plot sc = ax.scatter(data_ts['Lon'], data_ts['Lat'], data_ts['Alt'], c=data_ts['Alt'], vmin=cmin, vmax=cmax, cmap='turbo', marker='o', alpha=0.5, zorder=2) ax.scatter([min_lon, min_lon, max_lon, max_lon], [min_lat, max_lat, min_lat, max_lat]) ax.set_xlim(min_lon, max_lon) ax.set_ylim(min_lat, max_lat) ax.set_zlim(0, data_ts['Alt'].max()) # Add color bar cb = plt.colorbar(sc, ax=ax, shrink=0.5, aspect=5) cb.set_label('Altitude (m)') # Label axes ax.set_xlabel('Longitude') ax.set_ylabel('Latitude') ax.set_zlabel('Altitude (m)') # Set the view angle for better visualization ax.view_init(elev=30, azim=-60) plt.show()
关键修改说明
- 计算地理范围的纵横比,设置2D画布尺寸与之匹配,避免画布比例不当导致的留白
- 移除
plt.tight_layout(),改用bbox_inches='tight', pad_inches=0保存图像,确保无额外边距 - 简化3D表面绘制,只调用一次
plot_surface,避免重复绘制可能的层级问题
内容的提问来源于stack exchange,提问作者Charbel Abdallah
相关产品推荐
相关产品推荐

