Search code examples
pythonmatplotlibaxis-labelscartopy

Axis labels for LambertConformal in cartopy at wrong location


I want to plot some data in a LambertConformal projection and add labels to the axes. See the example code below. However, now the x-labels show up twice, and both times in the middle of the plot, instead of at its bottom. When instead I set gl.xlabels_bottom = False and gl.xlabels_top = True, no x-labels are plotted at all. With the y-labels, I do not get this problem; they are just nicely plotted either along the left or right boundary of the plot. How can I get the x-labels at the right location (at the bottom of the figure)?

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
 
bounds_lon = [-45,-25]
bounds_lat = [55,65]
lon = np.arange(bounds_lon[0],bounds_lon[1]+0.1,0.1)
lat = np.arange(bounds_lat[0],bounds_lat[1]+0.1,0.1)
Lon, Lat = np.meshgrid(lon,lat)
data = np.ones(np.shape(Lon))

data_crs = ccrs.PlateCarree()
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon),central_latitude=np.mean(bounds_lat),cutoff=bounds_lat[0])

plt.figure(figsize=(4,4))
ax = plt.axes(projection=projection)
ax.coastlines()
ax.contourf(Lon, Lat, data, transform=data_crs)

gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.xlabels_bottom = True

image


Solution

  • Manual repositioning of tick-labels are needed. To do that successfully, requires some other adjustments of the plot settings. Here is the code you can try.

    import numpy as np
    import matplotlib.pyplot as plt
    import cartopy.crs as ccrs
    
    bounds_lon = [-45,-25]
    bounds_lat = [55,65]
    
    # make-up data to plot on the map
    inc = 0.5
    lon = np.arange(bounds_lon[0],bounds_lon[1]+inc, inc)
    lat = np.arange(bounds_lat[0],bounds_lat[1]+inc, inc)
    Lon, Lat = np.meshgrid(lon,lat)
    
    #data = np.ones(np.shape(Lon))  # original `boring` data
    data = np.sin(Lon)+np.cos(Lat)  # better data to use instead
    
    data_crs = ccrs.PlateCarree()
    projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
                                       central_latitude=np.mean(bounds_lat), \
                                       #cutoff=bounds_lat[0]
                                      )
    
    # Note: `cutoff` causes horizontal cut at lower edge
    
    # init plot figure
    plt.figure(figsize=(15,9))
    ax = plt.axes(projection=projection)
    ax.coastlines(lw=0.2)
    ax.contourf(Lon, Lat, data, transform=data_crs, alpha=0.5)
    
    # set gridlines specs
    gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='gray', alpha=0.5, linestyle='--')
    
    gl.top_labels=True
    gl.bottom_labels=True
    gl.left_labels=True
    gl.right_labels=True
    
    plt.draw()  #enable access to lables' positions
    xs_ys = ax.get_extent()  #(x0,x1, y0,y1)
    #dx = xs_ys[1]-xs_ys[0]
    dy = xs_ys[3]-xs_ys[2]
    
    # The extent of `ax` must be adjusted
    # Extents' below and above are increased
    new_ext = [xs_ys[0], xs_ys[1], xs_ys[2]-dy/15., xs_ys[3]+dy/12.] 
    ax.set_extent(new_ext, crs=projection)
    
    # find locations of the labels and reposition them as needed
    xs, ys = [], []
    for ix,ea in enumerate(gl.label_artists):
        xy = ea[2].get_position()
        xs.append(xy[0])
        ys.append(xy[1])
    
        # Targeted labels to manipulate has "W" in them
        if "W" in ea[2].get_text():
            x_y = ea[2].get_position()
    
            # to check which are above/below mid latitude of the plot
            # use 60 (valid only this special case)
            if x_y[1]<60:
                # labels at lower latitudes
                curpos = ea[2].get_position()
                newpos = (curpos[0], 54.7)        # <- from inspection: 54.7
                ea[2].set_position(newpos)
            else:
                curpos = ea[2].get_position()
                newpos = (curpos[0], 65.3)        # <- from inspection: 65.3
                ea[2].set_position(newpos)
    
    plt.show()
    

    repositionlabels

    Edit1

    If you want to move all the lat/long labels to the outside edges, try this code. It is much more concise than the above.

    import numpy as np
    import matplotlib.pyplot as plt
    import cartopy.crs as ccrs
    
    bounds_lon = [-45,-25]
    bounds_lat = [55,65]
    
    inc = 0.5
    lon = np.arange(bounds_lon[0],bounds_lon[1]+inc, inc)
    lat = np.arange(bounds_lat[0],bounds_lat[1]+inc, inc)
    Lon, Lat = np.meshgrid(lon,lat)
    #data = np.ones(np.shape(Lon))  # boring data
    data = np.sin(Lon)+np.cos(Lat)  # more interesting
    
    data_crs = ccrs.PlateCarree()
    projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
                                       central_latitude=np.mean(bounds_lat), \
                                       cutoff=bounds_lat[0]
                                      )
    
    # init plot
    plt.figure(figsize=(15,9))
    ax = plt.axes(projection=projection)
    ax.coastlines(lw=0.2)
    ax.contourf(Lon, Lat, data, transform=data_crs, alpha=0.3)
    
    gl = ax.gridlines(draw_labels=True, x_inline=False, y_inline=False,
                  color='k', linestyle='dashed', linewidth=0.5)
    
    gl.top_labels=True
    gl.bottom_labels=True
    gl.left_labels=True
    gl.right_labels=True
    
    plt.show()
    

    new_plots

    If you want to get bottom edge as a straight line, you can achieve that by dropping the option cutoff=bounds_lat[0] from this line of code:-

    projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
                                   central_latitude=np.mean(bounds_lat), \
                                   cutoff=bounds_lat[0]
                                  )
    

    so that it becomes

    projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon),
                   central_latitude=np.mean(bounds_lat))
    

    and you will get the plot like this:-

    3rd-plot