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

设置matplotlib Axes框宽高比后如何确保Cartopy要素正常渲染?

问题

我有一个函数,可根据包含经纬度坐标的DataFrame生成地图。无论坐标对和时间戳绘制的轨迹形状如何,我希望通过axes.set_box_aspect()确保Axes框的宽高比保持不变。示例代码如下:

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plot
import pandas as pd

MAP_FIXED_WIDTH = 8
MAP_FIXED_HEIGHT = 6

df = pd.DataFrame(
    {
        "timestamp": 
        [
            pd.Timestamp('2025-01-01 00:00:00 UTC'), 
            pd.Timestamp('2025-01-01 00:30:00 UTC'), 
            pd.Timestamp('2025-01-01 01:00:00 UTC'), 
            pd.Timestamp('2025-01-01 01:30:00 UTC'), 
            pd.Timestamp('2025-01-01 02:00:00 UTC'),
         ],
        "latitude": [20,22,24,26,28],
        "longitude": [-45,-40,-35,-30,-25],
    },
)

def create_map_with_scatter_points(df: pd.DataFrame) -> None:
    plot.figure(figsize=(MAP_FIXED_WIDTH, MAP_FIXED_HEIGHT))
    axes = plot.axes(
        projection=ccrs.PlateCarree(central_longitude=df["longitude"].median())
    )

    axes.set_box_aspect(MAP_FIXED_HEIGHT / MAP_FIXED_WIDTH)

    axes.scatter(df["longitude"], df["latitude"], color='green')

    axes.set_facecolor(cfeature.COLORS["water"])
    axes.add_feature(cfeature.LAND, color="lightgray")
    axes.add_feature(cfeature.LAKES)
    axes.add_feature(cfeature.RIVERS)
    axes.add_feature(cfeature.COASTLINE)
    axes.add_feature(cfeature.BORDERS, linestyle=":")

    axes.gridlines(
        draw_labels=True, linewidth=1, color="gray", alpha=0.5, linestyle="--"
    )

    plot.show()

create_map_with_scatter_points(df)

依赖版本:

  • matplotlib==3.10.7
  • pandas==2.3.3
  • cartopy==0.25.0

但我发现set_box_aspect仅控制Axes框的物理尺寸,对轴内的数据范围无影响,导致仅存在于数据范围外的Cartopy要素无法渲染(如牙买加和波多黎各的边界未显示)。使用axes.set_aspect(aspect='equal', adjustable='datalim')也得到相同结果,因为set_adjustable会忽略显式数据范围。

我希望避免通过axes.set_extent手动编写逻辑来强制框宽高比,尤其是在数据跨180度子午线、经度在±180间切换时,确保正确中心经度难度较大。请问是否有可行的解决方法?


解决方法

可以通过「先自动适配数据范围,再调整范围匹配目标宽高比」的思路实现需求,同时处理跨180度子午线的场景,无需手动计算复杂的边界逻辑:

修改后的代码示例

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plot
import pandas as pd

MAP_FIXED_WIDTH = 8
MAP_FIXED_HEIGHT = 6
TARGET_ASPECT = MAP_FIXED_HEIGHT / MAP_FIXED_WIDTH

df = pd.DataFrame(
    {
        "timestamp": 
        [
            pd.Timestamp('2025-01-01 00:00:00 UTC'), 
            pd.Timestamp('2025-01-01 00:30:00 UTC'), 
            pd.Timestamp('2025-01-01 01:00:00 UTC'), 
            pd.Timestamp('2025-01-01 01:30:00 UTC'), 
            pd.Timestamp('2025-01-01 02:00:00 UTC'),
         ],
        "latitude": [20,22,24,26,28],
        "longitude": [-45,-40,-35,-30,-25],
    },
)

def create_map_with_scatter_points(df: pd.DataFrame) -> None:
    fig = plot.figure(figsize=(MAP_FIXED_WIDTH, MAP_FIXED_HEIGHT))
    central_lon = df["longitude"].median()
    axes = plot.axes(projection=ccrs.PlateCarree(central_longitude=central_lon))

    # 先绘制数据,让Cartopy自动计算包含数据的初始范围
    axes.scatter(df["longitude"], df["latitude"], color='green')
    
    # 获取初始数据范围(转换为PlateCarree投影下的坐标)
    x0, x1, y0, y1 = axes.get_extent(ccrs.PlateCarree())
    
    # 计算当前范围的宽高比,对比目标值调整范围
    current_width = x1 - x0
    current_height = y1 - y0
    current_aspect = current_height / current_width

    if current_aspect < TARGET_ASPECT:
        # 当前纬度范围过窄,扩展纬度边界以匹配目标比例
        delta_y = (TARGET_ASPECT * current_width - current_height) / 2
        y0 -= delta_y
        y1 += delta_y
    else:
        # 当前经度范围过窄,扩展经度边界以匹配目标比例
        delta_x = (current_height / TARGET_ASPECT - current_width) / 2
        x0 -= delta_x
        x1 += delta_x

    # 处理跨180度子午线的显示异常
    if abs(central_lon) > 90:
        if x0 < -180:
            x0 += 360
        if x1 > 180:
            x1 -= 360

    # 设置调整后的范围,确保覆盖周边要素
    axes.set_extent([x0, x1, y0, y1], crs=ccrs.PlateCarree())
    
    # 固定Axes框的物理宽高比
    axes.set_box_aspect(TARGET_ASPECT)

    # 添加地图要素
    axes.set_facecolor(cfeature.COLORS["water"])
    axes.add_feature(cfeature.LAND, color="lightgray")
    axes.add_feature(cfeature.LAKES)
    axes.add_feature(cfeature.RIVERS)
    axes.add_feature(cfeature.COASTLINE)
    axes.add_feature(cfeature.BORDERS, linestyle=":")

    axes.gridlines(
        draw_labels=True, linewidth=1, color="gray", alpha=0.5, linestyle="--"
    )

    plot.show()

create_map_with_scatter_points(df)

关键逻辑说明

  • 先绘制数据获取初始范围,避免手动计算数据边界的繁琐操作。
  • 通过对比当前范围宽高比和目标值,动态扩展纬度或经度范围,保证最终地图符合设定的物理框比例。
  • 针对跨180度子午线的场景,调整经度范围的计算逻辑,避免出现地图显示断裂的问题。
  • 最后设置set_box_aspect固定Axes框的物理比例,确保无论数据范围如何,地图的显示比例一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 01:40:56