问题
Please forgive me as this is my first go at a Python 'Project'.
A quick overview: I am trying to produce maps (MatPlotLib figures) by iterating through a GeoPandas dataframe of a GeoJSON file containing the boundaries of active subdivision phases in order to show the progress of construction of the individual lots in each subdivision phase. We normally did this manually in GIS, but I figured I'd take a stab at automating some, or all, of the process. I am also attempting to do this without using ESRI's python functionality and would prefer to keep it that way for stability for future use as ESRI can move around quite a bit.
I am able to iterate through the geodataframe and produce a map (figure) that is zoomed to the extent of the subdivision phase boundary, however, it is clipping the background aerial imagery basemap to the minimum binding box of the subdivision phase that I have been using to set the 'zoom' of the figure.
Example of what I am aiming to produce. This was made in ArcGIS Pro:
What I am able to make in Python:
I am unable to get the basemap to be a constant size that fills a standard landscape letter page whilst still being correctly zoomed in to the extent of the subdivision phase. My output jpeg is sized correctly, but the aerial imagery basemap is continuously croppped to the extent of the subdivision phase boundary leaving large borders around the figure.
import geopandas as gpd # extension to Pandas to work with geodata
import urllib.request, json # download from the web
import os.path # work with the local file system
from shapely.geometry import Point # basic functions to work with vector geometries
import matplotlib as mpl # plotting
from matplotlib import pyplot as plt # some matplotlib convenience functions
import contextily as ctx # simple free basemaps
import csv # read .csv files
import time # time tracking for processes
from matplotlib.patches import Patch # build legend items
from matplotlib.lines import Line2D # build legend items
from matplotlib import figure # change size of figures
start_time = time.time() # Keep track of execution time
# Build Legend Elements
legend_elements = [Patch(facecolor='green', edgecolor='r', alpha=0.5,
label='Completed Lots'),
Patch(facecolor='red', edgecolor='black', alpha=0.5,
label='Lots Under Construction'),
Patch(facecolor='none', edgecolor='r',
label='Subdivision Boundary')]
for index, row in phases.iterrows():
name = row['NAME']
print(name)
lots_complete_select = lots_complete[(lots_complete['SUBDIVISION'] == row['NAME'])]
print('Completed Lots: ' + str(len(lots_complete_select)))
lots_uc_select = lots_uc[(lots_uc['SUBDIVISION'] == row['NAME'])]
print('Lots Under Construction: ' + str(len(lots_uc_select)))
phase_select = phases[(phases['NAME'] == row['NAME'])]
lots_select = lots[(lots['SUBDIVISION'] == row['NAME'])]
print('Lots in subdivision: ' + str(len(lots_select)))
minx, miny, maxx, maxy = (lots_select).total_bounds # Set zoom to lots for area of interest
map_time = time.time()
print('Building map...')
fig, ax = plt.subplots() # Create a figure with multiple plots within it
ax.set_aspect('equal')
ax.axis('off') # Turn off axes
ax.set_xlim(minx, maxx) # Apply x zoom level
ax.set_ylim(miny, maxy) # Apply y zoom level
streets.plot(ax=ax, linewidth=1.2, color='black') # Plot Streets
lots_complete_select.plot(ax=ax, linewidth=0.5, color='green', edgecolor='red', alpha=0.5) # Plot completed lots
lots_uc_select.plot(ax=ax, linewidth=0.5, color='red', edgecolor='black', alpha=0.5) # Plot U.C. lots
lots.plot(ax=ax, linewidth=0.7, color='none', edgecolor='black') # Plot lot lines
phase_select.plot(ax=ax, linewidth=4, color='none', edgecolor='black') # Plot Active Subdivision Phases
phase_select.plot(ax=ax, linewidth=2, color='none', edgecolor='red') # Plot Active Subdivision Phases
phase_select.plot(ax=ax, linewidth=1, color='none', edgecolor='white') # Plot Active Subdivision Phases
ctx.add_basemap(ax, crs=streets.crs.to_string(), source=ctx.providers.Esri.WorldImagery,
attribution='City of Pflugerville GIS Services') # Change basemap
ax.set_title((name + ' Residential Construction'), fontsize=10, fontweight ="bold") # Set title
ax.legend(handles=legend_elements, prop={'size': 6}, title='Legend', framealpha=1, fancybox=False,
edgecolor='black', loc='upper left') # Add legend
plt.savefig(save_path + name + '.jpg', edgecolor='black', orientation='landscape', papertype='letter',
dpi=300) # Save map
print('Map saved.')
print('Done building map.')
print("--- %s Seconds ---" % (time.time() - map_time) + '\n')
print('Done configuring maps.')
print("--- %s Minutes ---" % ((time.time() - start_time)/60))
I have tried using figsize=(11, 8.5)
in various places around the code to no effect.
Any input would be greatly appreciated. Please let me know if there is anything that needs to be changed/clarified.
Also, if anyone is familiar with labelling line features, such as streets, in Python, is there a way of labelling streets in Python in a manner similar to what is seen in the first image?
回答1:
If you want to have a fixed area which, say, extends the bounding box by 10m, you have to set xlim
and ylim
extended. You are explicitly specifying both to bounding box.
margin = 10
ax.set_xlim(minx - margin, maxx + margin)
ax.set_ylim(miny - margin, maxy + margin)
Furthermore, to remove white boundaries around your plot, you can set bbox_inches
to tight
in saving.
plt.savefig(save_path + name + '.jpg', edgecolor='black', orientation='landscape', papertype='letter',
dpi=300, bbox_inches='tight') # Save map
来源:https://stackoverflow.com/questions/64431494/how-to-set-fixed-size-for-basemap-subplot-when-iterating-through-rows