Search code examples
cartopy

How to determine visible map extent for any cartopy projection?


When setting the map extent for a cartopy map that has a projection other than Plate Carree, there are areas visible on the map that are outside of the specified extent. An example of this can be seen in the last image of this jupyter notebook: https://gist.github.com/ajdawson/e75715203148c9b22e436c6017d3c513

With the Lambert Conformal projection, there are areas inside the figure window (not shaded) that fall outside of the specified extent. While map extent in this example was set to [-140, -60, 20, 70], the minimum longitude visible is close to -180 in the top left corner. Essentially, the extent set with set_extent() is not the true lat/lon extent seen in the image.

I'd like a way to find what this true extent is for every image. This would be the max and min lat and lon visible anywhere in the image. For most projections, this could be done by finding the lat/lon coordinates of the corners of the map, but that method would fail if the poles are visible in the figure (and for any polar projection).


Solution

  • Update 9/23/21
    I found that that the cartopy axes has a get_extent() method that does exactly this!

    Example:

    import matplotlib.pyplot as plt
    import cartopy.crs as ccrs, cartopy.feature as cfeature
    
    fig = plt.figure()
    ax = fig.add_subplot(111, projection=ccrs.LambertConformal())
    ax.set_extent([-130, -60, 20, 70], ccrs.PlateCarree())
    ax.add_feature(cfeature.COASTLINE)
    print(ax.get_extent(crs=ccrs.PlateCarree())) # this is the visible map extent, not the same as what was set using ax.set_extent()
    

    Previous answer - a solution that also works
    I ended up coming up with a solution that works better for my application than the original question. If I'm plotting gridded data on the map, I convert from data coordinates to map coordinates, then from map coordinates to figure coordinates, and finally from figure coordinates to axes coordinates. If the grid point is within (0:1, 0:1), it will fall within the visible map extent.

    import numpy as np
    import cartopy.crs as ccrs
    import matplotlib.pyplot as plt
    
    lat = np.arange(0,91,1.0)
    lon = np.arange(-180,1,1.0)
    lons,lats = np.meshgrid(lon,lat)
    
    mapproj = ccrs.LambertConformal() # the map projection
    dataproj = ccrs.PlateCarree() # the data projection, PlateCarree for lat/lon data
    fig = plt.figure()
    ax = fig.add_subplot(111, projection=mapproj)
    
    points = mapproj.transform_points(dataproj, lons, lats)
    xpts = points[:,:,0].flatten().tolist()
    ypts = points[:,:,1].flatten().tolist()
    crs_pts = list(zip(xpts,ypts))
    fig_pts = ax.transData.transform(crs_pts)
    ax_pts = ax.transAxes.inverted().transform(fig_pts)
    x = ax_pts[:,0].reshape(lats.shape)
    y = ax_pts[:,1].reshape(lats.shape)
    
    mask = (x>=-0.02) & (x<=1.02) & (y>=-0.02) & (y<=1.02) # add padding for large grid spacings