I need to plot a square map using Cartopy. I currently use the following code for my map:
plt.figure(figsize = (15, 15))
img = cimgt.GoogleTiles()
ax = plt.axes(projection = img.crs)
ax.set_extent((d['longitude'].min() - 0.05, d['longitude'].max() + 0.05,
d['latitude'].min() - 0.05, d['latitude'].max() + 0.05))
ax.add_image(img, 10, interpolation = 'bicubic')
plt.scatter(d['longitude'], d['latitude'], transform = ccrs.PlateCarree(),
c = '#E8175D', s = 14)
This works fine, except for the fact that the map isn't square. Instead, it's just fitted into the (15, 15)
square of the plot.
I would like to add a bit more map to the left and to the right to make the plot perfectly square without distorting it. Simply setting the extent to the same difference on latitude and longitude doesn't do the job, because latitude and longitude cover different distances in Google's (and most other) map projections. I also found this post, but from what I get, the intent here is to distort the map.
I hope someone has an idea how to do this. It seems that Cartopy is not very intuitive in this regard.
To get square extent you need to specify it in map projection coordinates. That involves some coordinate transformation. Here is the code snippet that you need.
# crs of your choice
crg = cimgt.StamenTerrain().crs # or cimgt.GoogleTiles().crs
# set map limits, in degrees
lonmin, lonmax = -22, -15
latmin, latmax = 63, 65
# do coordinate transformation
LL = crg.transform_point(lonmin, latmin, ccrs.Geodetic())
UR = crg.transform_point(lonmax, latmax, ccrs.Geodetic())
EW = UR[0] - LL[0]
SN = UR[1] - LL[1]
# get side of the square extent (in map units, usually meters)
side = max(EW, SN) # larger value is in effect
mid_x, mid_y = LL[0]+EW/2.0, LL[1]+SN/2.0 # center location
# the extent preserves the center location
extent = [mid_x-side/2.0, mid_x+side/2.0, mid_y-side/2.0, mid_y+side/2.0]
# this sets square extent
# crs=crg signifies that projection coordinates is used in extent
ax.set_extent(extent, crs=crg)
Hope it helps.
Edit
Here is a complete working code and its resulting map.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.io.img_tiles as cimgt
def make_map(projection=ccrs.PlateCarree()):
fig, ax = plt.subplots(figsize=(10, 10),
subplot_kw=dict(projection=projection))
gl = ax.gridlines(draw_labels=True)
gl.xlabels_top = gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
return fig, ax
request = cimgt.StamenTerrain() # very responsive
crg = request.crs #crs of the projection
fig, ax = make_map(projection = crg)
# specify map extent here
lonmin, lonmax = -22, -15
latmin, latmax = 63, 65
LL = crg.transform_point(lonmin, latmin, ccrs.Geodetic())
UR = crg.transform_point(lonmax, latmax, ccrs.Geodetic())
EW = UR[0] - LL[0]
SN = UR[1] - LL[1]
side = max(EW,SN)
mid_x, mid_y = LL[0]+EW/2.0, LL[1]+SN/2.0 #center location
extent = [mid_x-side/2.0, mid_x+side/2.0, mid_y-side/2.0, mid_y+side/2.0] # map coordinates, meters
ax.set_extent(extent, crs=crg)
ax.add_image(request, 8)
# add a marker at center of the map
plt.plot(mid_x, mid_y, marker='o', \
color='red', markersize=10, \
alpha=0.7, transform = crg)
plt.show()