[Python] How do I control the size of margins around a cartopy map projection?

Discussão em 'Python' iniciado por Stack, Julho 17, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to plot a bunch of data on a map of the sky in various projections, using matplotlib + cartopy, and the margins around the maps are always too large and none of the controls I can find seem to help. Example (annotations added after rendering):

    [​IMG]

    I would like to make the outer margin of the entire image, including the colorbar, be something like 2.5mm, and the gap between the colorbar and the image be something like 5mm (these numbers will need to be tweaked of course), and then the map should fill the rest of the available space.

    Note that I may need to turn this into a 2-subplot figure, two maps sharing a colorbar, each with a label, and possibly also add meridians and parallels with labels, so a solution that works regardless of how much 'furniture' each Axes has is strongly preferred.

    Part of the problem seems to be that each map projection has its own desired aspect ratio, and if that doesn't agree with the aspect ratio of the figure then spacing will be added to preserve said aspect ratio, but since the desired aspect ratio is not documented anywhere and the width of the colorbar is unpredictable, knowing that doesn't actually help me any.

    Here's the code I have now. Please note how each projection gets rendered with different margins.

    import cartopy.crs as ccrs
    import matplotlib.pyplot as plt
    import numpy as np
    from matplotlib.layout_engine import ConstrainedLayoutEngine


    def main():
    cel_sphere = ccrs.Globe(datum=None, ellipse=None,
    semimajor_axis=180/np.pi,
    semiminor_axis=180/np.pi)
    sky_plate = ccrs.PlateCarree(globe=cel_sphere)

    ra, dec = np.mgrid[-179.5:180:1, -89.5:90:1]
    fake_data = generate_perlin_noise_2d(ra.shape, (1, 1))

    for label, proj in [("ee", ccrs.EqualEarth),
    ("mw", ccrs.Mollweide),
    ("lc", ccrs.LambertCylindrical)]:
    try:
    fig, ax = plt.subplots(
    figsize=(20, 10),
    layout=ConstrainedLayoutEngine(
    h_pad=0, w_pad=0, hspace=0, wspace=0
    ),
    subplot_kw={
    "xlim": (-180, 180),
    "ylim": (-90, 90),
    "projection": proj(globe=cel_sphere)
    },
    )
    ctr = ax.contourf(ra, dec, fake_data,
    transform=sky_plate,
    cmap="Greys")
    fig.colorbar(ctr, shrink=0.5, pad=0.02)
    fig.savefig(f"layout_test_{label}.png")
    finally:
    plt.close(fig)

    # stolen from https://pvigier.github.io/2018/06/13/perlin-noise-numpy.html
    def generate_perlin_noise_2d(shape, res):
    def f(t):
    return 6*t**5 - 15*t**4 + 10*t**3

    delta = (res[0] / shape[0], res[1] / shape[1])
    d = (shape[0] // res[0], shape[1] // res[1])
    grid = np.mgrid[0:res[0]:delta[0],0:res[1]:delta[1]].transpose(1, 2, 0) % 1
    # Gradients
    angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
    gradients = np.dstack((np.cos(angles), np.sin(angles)))
    g00 = gradients[0:-1,0:-1].repeat(d[0], 0).repeat(d[1], 1)
    g10 = gradients[1:,0:-1].repeat(d[0], 0).repeat(d[1], 1)
    g01 = gradients[0:-1,1:].repeat(d[0], 0).repeat(d[1], 1)
    g11 = gradients[1:,1:].repeat(d[0], 0).repeat(d[1], 1)
    # Ramps
    n00 = np.sum(grid * g00, 2)
    n10 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1])) * g10, 2)
    n01 = np.sum(np.dstack((grid[:,:,0], grid[:,:,1]-1)) * g01, 2)
    n11 = np.sum(np.dstack((grid[:,:,0]-1, grid[:,:,1]-1)) * g11, 2)
    # Interpolation
    t = f(grid)
    n0 = n00*(1-t[:,:,0]) + t[:,:,0]*n10
    n1 = n01*(1-t[:,:,0]) + t[:,:,0]*n11
    return np.sqrt(2)*((1-t[:,:,1])*n0 + t[:,:,1]*n1)

    main()

    Continue reading...

Compartilhe esta Página