plierp
plierp

Reputation: 97

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

Upvotes: 1

Views: 874

Answers (1)

swatchai
swatchai

Reputation: 18772

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

Upvotes: 1

Related Questions