I saw this post and a couple other similar ones in search for the same answer and came up with this as a way to take the user out of the loop in J.P.'s accepted answer.
This bit of code takes the desired figure size height and width, compares the orientation of your figure to the orientation of your gdf, and scales one of the axes "outward" to match the extent just as described in JP's answer above, just without having to adjust the scaling number manually.
There is definitely some room for improvement (like defining a function or two), but I am looking forward to hearing about any holes you all find in this. It'll be a daily driver for me no doubt.
Best,
-Matt
import geopandas as gpd
import contextily as cx
import matplotlib.pyplot as plt
#Import Primary Geo Data
df = gpd.read_file(r'Primary.shp')
#import Secondary Layer
adf = gpd.read_file('Secondary.shp')
# - Reproject secondary layer if needed to match primary layer
if adf.crs != df.crs:
adf = adf.to_crs(df.crs)
print("Reprojected ADF")
#Plot Geodata
fig, ax = plt.subplots ()
width = 10
height = 15
if width > height:
framedim = "wide"
else:
framedim = "tall"
print(framedim)
plt.gcf().set_size_inches(width, height)
df.plot(ax = ax, alpha=0.5, edgecolor='silver', column = 'hour', cmap = "plasma", scheme=None, legend=False, zorder=3)
# - Set mapping extent to primary layer and user-defined figsize
lon_min, lat_min, lon_max, lat_max = df.total_bounds
dataWt = lon_max-lon_min
dataHt = lat_max-lat_min
print("dataWt: "+str(dataWt))
print("dataHt: "+str(dataHt))
if dataWt > dataHt:
datadim = "wide"
else:
datadim = "tall"
print(datadim)
#scaling
if (framedim == "tall") and (datadim == "tall"):
#scale height
print("Match Height")
# - Get datawt unit for 1 unit of frame width
one_data_unit = dataWt/width
print("one_data_unit: "+str(one_data_unit))
properheight = one_data_unit*height
print("properheight: "+str(properheight))
height_bookends = abs(properheight-dataHt)/2
print("height_bookends: "+str(height_bookends))
print("lon_min: "+str(lon_min))
print("lon_max: "+str(lon_max))
print("lat_min: "+str(lat_min))
print("lat_max: "+str(lat_max))
print("lat_min-height_bookends: "+str(lat_min-height_bookends))
print("lat_max+height_bookends: "+str(lat_max+height_bookends))
ax.set_xlim(lon_min-(one_data_unit*(width/100)), lon_max+(one_data_unit*(width/100)))
ax.set_ylim(lat_min-height_bookends-(one_data_unit*(height/100)), lat_max+height_bookends+(one_data_unit*(height/100)))
elif (framedim == "wide") and (datadim == "wide"):
#scale width
print("Match Width")
# - Get dataht unit for 1 unit of frame height
one_data_unit = dataHt/height
print("one_data_unit: "+str(one_data_unit))
properwidth = one_data_unit*width
print("properwidth: "+str(properwidth))
width_bookends = abs(properwidth-dataWt)/2
print("width_bookends: "+str(width_bookends))
print("lon_min: "+str(lon_min))
print("lon_max: "+str(lon_max))
print("lon_min-width_bookends: "+str(lon_min-width_bookends))
print("lon_max+width_bookends: "+str(lon_max+width_bookends))
print("lat_min: "+str(lat_min))
print("lat_max: "+str(lat_max))
ax.set_xlim((lon_min-width_bookends)-(one_data_unit*(width/100)), (lon_max+width_bookends)+(one_data_unit*(width/100)))
ax.set_ylim(lat_min-(one_data_unit*(height/100)), lat_max+(one_data_unit*(height/100)))
elif (framedim == "tall") and (datadim == "wide"):
#scale height
print("Fit Wide in Tall")
# - Get datawt unit for 1 unit of frame width
one_data_unit = dataWt/width
print("one_data_unit: "+str(one_data_unit))
properheight = one_data_unit*height
print("properheight: "+str(properheight))
height_bookends = abs(properheight-dataHt)/2
print("height_bookends: "+str(height_bookends))
print("lon_min: "+str(lon_min))
print("lon_max: "+str(lon_max))
print("lat_min: "+str(lat_min))
print("lat_max: "+str(lat_max))
print("lat_min-height_bookends: "+str(lat_min-height_bookends))
print("lat_max+height_bookends: "+str(lat_max+height_bookends))
ax.set_xlim(lon_min-(one_data_unit*(width/100)), lon_max+(one_data_unit*(width/100)))
ax.set_ylim(lat_min-height_bookends-(one_data_unit*(height/100)), lat_max+height_bookends+(one_data_unit*(height/100)))
else:
#scale width
print("Fit Tall in Wide")
# - Get dataht unit for 1 unit of frame height
one_data_unit = dataHt/height
print("one_data_unit: "+str(one_data_unit))
properwidth = one_data_unit*width
print("properwidth: "+str(properwidth))
width_bookends = abs(properwidth-dataWt)/2
print("width_bookends: "+str(width_bookends))
print("lon_min: "+str(lon_min))
print("lon_max: "+str(lon_max))
print("lon_min-width_bookends: "+str(lon_min-width_bookends))
print("lon_max+width_bookends: "+str(lon_max+width_bookends))
print("lat_min: "+str(lat_min))
print("lat_max: "+str(lat_max))
ax.set_xlim((lon_min-width_bookends)-(one_data_unit*(width/100)), (lon_max+width_bookends)+(one_data_unit*(width/100)))
ax.set_ylim(lat_min-(one_data_unit*(height/100)), lat_max+(one_data_unit*(height/100)))
# - Plot Secondary Layer now so that the pri layer is the focus of the extent
adf.plot(ax = ax, alpha=0.5, edgecolor='k', color = "r", zorder=2)
#Add in Basemap based on user definition
cx.add_basemap(ax,
crs=df.crs,
source=cx.providers.CartoDB.DarkMatterOnlyLabels,
attribution = False,
reset_extent=True
)
cx.add_basemap(ax,
crs=df.crs,
source=cx.providers.CartoDB.DarkMatterNoLabels,
attribution = False,
alpha = .8,
reset_extent=True
)
ax.set_axis_off()
#Add in Marginalia
plt.text(.01, .03, str(df.crs).upper(), fontsize=10, color = "k", backgroundcolor = "gray", ha='left', va='top', transform=ax.transAxes, zorder=4)
plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0)
plt.margins(0,0)
fig.savefig("contextily.png")