## LSDMap_Subplots.py
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## These functions are tools to deal with creating nice subplots from multiple
## rasters
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## FJC
## 22/12/2016
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
from __future__ import absolute_import, division, print_function, unicode_literals
from glob import glob
import os.path
import numpy as np
import matplotlib.pyplot as pp
import string
import matplotlib.image as mpimg
import matplotlib.cm as cmx
from matplotlib import rcParams
from . import LSDMap_GDALIO as LSDMap_IO
from . import LSDMap_BasicPlotting as LSDMap_BP
from . import labels as lsdlabels
#==============================================================================
# Convert cm to inch for figure sizing
#------------------------------------------------------------------------------
[docs]def cm2inch(value):
"""
Convert cm to inch for figure sizing
"""
return value/2.54
#==============================================================================
# Function to create nice field sites figure from all the hillshades in a folder
# N_HSFiles = number of field sites
# Also reads in a series of map files to show locations of each site. At the moment
# you have to manually indicate on these maps where the field site is - would
# be nice to automate this when I have some time to mess around with it.
# FJC 22/12/16
#------------------------------------------------------------------------------
[docs]def field_sites(DataDirectory, N_HSFiles, NRows, NCols, n_target_ticks):
# Set up fonts
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['arial']
rcParams['font.size'] = 6
# Read in the files for each site
HSFiles = sorted(glob(DataDirectory+'*_HS.bil'), key=str)
MapFiles = sorted(glob(DataDirectory+'*_map.eps'), key=str)
print(MapFiles)
n_files = len(HSFiles)+len(MapFiles)
print("Number of files = ", n_files)
# Now make the subplots
fig, ax = pp.subplots(NRows,NCols, figsize=(cm2inch(12),cm2inch(15)), frameon=False)
ax = ax.ravel()
#get a list to label the subfigures
alphabet = list(string.ascii_lowercase)
file_counter = 0
for i in range (n_files):
if i < N_HSFiles:
# first deal with the hillshade files. If there are more hillshade files simply
# change the value of N_HSFiles
print("The hillshade file name is: ", HSFiles[i])
hillshade_raster = LSDMap_IO.ReadRasterArrayBlocks(HSFiles[i])
hillshade_raster = np.ma.masked_where(hillshade_raster == -9999, hillshade_raster)
# now get the extent
extent_raster = LSDMap_IO.GetRasterExtent(HSFiles[i])
# get DEM info
CellSize,XMin,XMax,YMin,YMax = LSDMap_IO.GetUTMMaxMin(HSFiles[i])
print(YMin, YMax)
#plot the rasters
ax[i].imshow(hillshade_raster, extent = extent_raster, cmap=cmx.gray)
ax[i].text(0.05,0.97, alphabet[i], horizontalalignment='left', verticalalignment='top', bbox=dict(facecolor='white', edgecolor='k', pad=3), fontsize = 8, transform=ax[i].transAxes)
#change ticks
# xlocs = ax[i].xaxis.get_ticklocs()
# ylocs = ax[i].yaxis.get_ticklocs()
# new_xlocs,new_ylocs,new_x_labels,new_y_labels = LSDMap_BP.GetTicksForUTM(HSFiles[i],xlocs.max(),xlocs.min(),ylocs.max(),ylocs.min(),n_target_ticks)
#
# # change the location of the ticks depending on subplot placement
# if i < 2:
# ax[i].xaxis.tick_top()
# if i % 2 != 0:
# ax[i].yaxis.tick_right()
ax[i].set_xticklabels([])
ax[i].set_yticklabels([])
#ax[i].tick_params(axis='x', pad=7)
#ax[i].tick_params(axis='y', pad=7)
if i >= N_HSFiles:
print("The map file name is: ", MapFiles[file_counter])
# add in the location maps for the sites (e.g. USA, UK)
img = mpimg.imread(MapFiles[file_counter])
ax[i].imshow(img)
ax[i].text(0.02,1.00, alphabet[i], horizontalalignment='left', verticalalignment='top', fontsize = 8, transform=ax[i].transAxes)
file_counter=file_counter+1
#remove border
ax[i].axis('off')
# Save figure
# Add a big subplot to get a common x and y label for the subplots
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axes
pp.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
#pp.xlabel('Easting (m)', fontsize=8, labelpad=-122)
#pp.ylabel('Northing (m)', fontsize=8, labelpad=10, position=(0.0,0.67))
pp.tight_layout(pad=0.1, w_pad = 0.1, h_pad = 0.2)
OutputFigureName = "field_sites"
OutputFigureFormat = 'pdf'
pp.savefig(DataDirectory+OutputFigureName + '.' + OutputFigureFormat, format=OutputFigureFormat, dpi=300)
#pp.show()
#==============================================================================
# Function to create comparison plots for floodplain mapping between the published
# flood maps and the geometric method
# FJC 22/12/16
#------------------------------------------------------------------------------
[docs]def multiple_flood_maps(DataDirectory):
"""
Make nice subplots of floodplain rasters for different field sites
"""
import seaborn as sns
# Set up fonts
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['arial']
rcParams['font.size'] = 12
# Read in the files for each site
FPFiles = sorted(glob(DataDirectory+'*_FP*.bil'), key=str)
n_files = len(FPFiles)
print("Number of files = ", n_files)
# Now make the subplots
fig, ax = pp.subplots(2,3, figsize=(cm2inch(15),cm2inch(11)))
ax = ax.ravel()
#use seaborn to get a nice color palette
cmap_oranges = sns.light_palette("#ff8f66", input="hex", as_cmap=True, reverse=True)
cmap_ice = sns.light_palette("#00ffff", input="hex", as_cmap=True, reverse=True)
alphabet = list(string.ascii_lowercase)
for i in range (n_files):
print("The floodplain file name is: ", FPFiles[i])
# get the name of the field site
fname = FPFiles[i].split('/')
print(fname)
split_fname = fname[-1].split('_')
print(split_fname)
HSFile = DataDirectory+split_fname[1]+'_'+split_fname[2]+"_HS.bil"
print(HSFile)
hillshade_raster = LSDMap_IO.ReadRasterArrayBlocks(HSFile)
FP_raster = LSDMap_IO.ReadRasterArrayBlocks(FPFiles[i])
FP_raster = np.ma.masked_where(FP_raster <= 0, FP_raster)
# now get the extent
extent_raster = LSDMap_IO.GetRasterExtent(HSFile)
# get DEM info
CellSize,XMin,XMax,YMin,YMax = LSDMap_IO.GetUTMMaxMin(HSFile)
print(YMin, YMax)
#plot the rasters
ax[i].imshow(hillshade_raster, extent = extent_raster, cmap=cmx.gray)
if i < 3:
ax[i].imshow(FP_raster, extent = extent_raster, cmap=cmap_oranges, alpha=0.8)
else:
ax[i].imshow(FP_raster, extent = extent_raster, cmap=cmap_ice, alpha=0.6)
ax[i].text(0.03,0.97, alphabet[i], bbox=dict(facecolor='white', edgecolor='k', pad=5), horizontalalignment='left', verticalalignment='top', transform=ax[i].transAxes)
#scalebars.add_scalebar(ax[i], matchx=False, sizex=500.0, loc=3, borderpad =1, lw=3, matchy=False, hidex=False, hidey=False)
ax[i].set_xticklabels([])
ax[i].set_yticklabels([])
ax[i].set_xticks([])
ax[i].set_yticks([])
ax[i].tick_params(axis='x', pad=3)
ax[i].tick_params(axis='y', pad=3)
# Save figure
pp.tight_layout(pad=0.5, h_pad=0, w_pad=0.1)
pp.subplots_adjust(wspace=0.05,hspace=0)
OutputFigureName = "Comparison_published_maps"
OutputFigureFormat = 'pdf'
pp.savefig(DataDirectory+OutputFigureName + '.' + OutputFigureFormat, format=OutputFigureFormat, transparent=True, dpi=300)
#==============================================================================
# Make subplots showing the difference between the mapped and predicted
# floodplain initiation points. Uses Fiona (yaaay) to read in the shapefiles
# FJC 05/01/17
#------------------------------------------------------------------------------
[docs]def flood_maps_with_shapefile(DataDirectory):
from fiona import collection
from descartes import PolygonPatch
# Set up fonts
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['arial']
rcParams['font.size'] = 8
# Read in the files for each site
HSFiles = sorted(glob(DataDirectory+'*_HS*.bil'), key=str)
FPFiles = sorted(glob(DataDirectory+'*_FIPs_FP*.shp'), key=str)
PointFiles = sorted(glob(DataDirectory+'*_FIPs_MP*.shp'), key=str)
n_files = len(FPFiles)
print("Number of files = ", n_files)
# Now make the subplots
fig, ax = pp.subplots(1,2, figsize=(cm2inch(12),cm2inch(7)))
ax = ax.ravel()
#get a list with the figure letterings
figure_letter = ["a", "b"]
titles = ["Mid Bailey Run, OH", "Coweeta, NC"]
for i in range (n_files):
print("The hillshade file name is: ", HSFiles[i])
print("The floodplain file name is: ", FPFiles[i])
print("The shapefile name is: ", PointFiles[i])
# get the name of the field site
split_fname = FPFiles[i].split('_FP')
print(split_fname[0])
hillshade_raster = LSDMap_IO.ReadRasterArrayBlocks(HSFiles[i])
# now get the extent
extent_raster = LSDMap_IO.GetRasterExtent(HSFiles[i])
# get DEM info
CellSize,XMin,XMax,YMin,YMax = LSDMap_IO.GetUTMMaxMin(HSFiles[i])
print(YMin, YMax)
# plot the raster
ax[i].imshow(hillshade_raster, extent = extent_raster, cmap=cmx.gray)
# plot the floodplain shapefile using fiona and descartes
with collection(FPFiles[i], 'r') as input:
for f in input:
ax[i].add_patch(PolygonPatch(f['geometry'], fc='blue', ec='blue', lw=0.1, alpha=0.8))
#plot the mapped points
with collection(PointFiles[i],'r') as input:
for point in input:
x = point['geometry']['coordinates'][0]
y = point['geometry']['coordinates'][1]
ax[i].scatter(x,y, c="red", s=15, zorder=100)
#ax[i].text(0.03,0.97, figure_letter[i], bbox=dict(facecolor='white', edgecolor='k', pad=3), horizontalalignment='left', verticalalignment='top', fontsize = 8, transform=ax[i].transAxes)
#change ticks
xlocs = ax[i].xaxis.get_ticklocs()
ylocs = ax[i].yaxis.get_ticklocs()
n_target_tics = 10
new_xlocs,new_ylocs,new_x_labels,new_y_labels = LSDMap_BP.GetTicksForUTM(HSFiles[i],xlocs.max(),xlocs.min(),ylocs.max(),ylocs.min(),n_target_tics)
ax[i].set_xticklabels(new_x_labels, rotation=30)
ax[i].set_yticklabels(new_y_labels)
#set axis limits
ax[i].set_xlim(XMin,XMax)
ax[i].set_ylim(YMin,YMax)
if i == 0:
ax[i].set_xlabel('Easting (m)', position=(1,0))
ax[i].set_ylabel('Northing (m)')
if i > 0:
ax[i].yaxis.tick_right()
ax[i].tick_params(axis='x', pad=2)
ax[i].tick_params(axis='y', pad=2)
ax[i].set_title(titles[i], fontsize=9)
# Save figure
pp.tight_layout(pad=0.5)
OutputFigureName = "Comparison_with_mapped_FIPs"
OutputFigureFormat = 'pdf'
pp.savefig(DataDirectory+OutputFigureName + '.' + OutputFigureFormat, format=OutputFigureFormat, dpi=300)
[docs]def findmaxval_multirasters(FileList):
"""
Loops through a list or array of rasters (np arrays)
and finds the maximum single value in the set of arrays.
"""
overall_max_val = 0
for i in range (len(FileList)):
raster_as_array = LSDMap_IO.ReadRasterArrayBlocks(FileList[i])
this_max_val = np.nanmax(raster_as_array)
if this_max_val > overall_max_val:
overall_max_val = this_max_val
print(overall_max_val)
return overall_max_val
[docs]def findminval_multirasters(FileList):
"""
Loops through a list or array of rasters (np arrays)
and finds the minimum single value in the set of arrays.
"""
overall_min_val = 0
for i in range (len(FileList)):
raster_as_array = LSDMap_IO.ReadRasterArrayBlocks(FileList[i])
this_min_val = np.nanmin(raster_as_array)
if this_min_val > overall_min_val:
overall_min_val = this_min_val
print(overall_min_val)
return overall_min_val
[docs]def MultiDrapeFloodMaps(DataDir, ElevationRaster, DrapeRasterWild, cmap,
drape_min_threshold=None, drape_max=None, cbar_label=None):
"""Creates a figure with multiple drape maps over a hillshade.
Plots flood extents from water depth rasters
draped over the catchment elevation raster
in a series of subplots
Takes a wildcard for the drapes
Expexts a fixed elevation raster, but this could be
modified in future.
Parameters:
DataDir (str): Path to the directory containing the data files
ElevationRaster (str): Name of the elevation raster used to create the hillshade
DrapeRasterWild (str): Wildcard string used to find all the drape files in the
directory.
cmap: Can be the string name of a colourmap, or a Colourmap object
drape_min (float, optional): Minimum value for the drape raster, i.e. values
below this threshold will be masked and not plotted.
drape_max (float, optional): Maximum value for the drape raster, i.e. values
above this value will be masked and not plotted.
cbar_label (str, optional): Label for the colourbar on the figure. This
is the colourbar for the drape colourmap.
Notes:
Consider, if plotting multiple datasets, how you
are going to deal with min a max values in the colur range.
imshow will automatically set vmin and vmax and stretch the colour bar
over this - which can be visually misleading. Ideally, you
want to have the same colour map used for *all* subplots, and
this is not default behaviour.
Note: If `drape_max` is not set, the function searches for the maximum value
in the range of rasters found by expanding the `DrapeRasterWild` argument
and searching for the maximum value out of all rasters found.
Raises:
Exception: If the maximum value in the drape maps could not be found.
"""
f, ax_arr = pp.subplots(2, 2, figsize=(10, 5), sharex=True, sharey=True)
ax_arr = ax_arr.ravel()
FPFiles = sorted(glob(DataDir+DrapeRasterWild), key=str)
n_files = len(FPFiles)
print("Number of files = ", n_files)
elev_raster_file = DataDir + ElevationRaster
hillshade = LSDMap_BP.Hillshade(elev_raster_file)
#hillshade_array = LSDP.ReadRasterArrayBlocks(elev_raster_file)
# now get the extent
extent_raster = LSDMap_IO.GetRasterExtent(elev_raster_file)
x_min = extent_raster[0]
x_max = extent_raster[1]
y_min = extent_raster[2]
y_max = extent_raster[3]
# now get the tick marks
n_target_tics = 5
xlocs,ylocs,new_x_labels,new_y_labels = LSDMap_BP.GetTicksForUTM(elev_raster_file,x_max,x_min,y_max,y_min,n_target_tics)
print("xmax: " + str(x_max))
print("xmin: " + str(x_min))
print("ymax: " + str(y_max))
print("ymin: " + str(y_min))
"""
Find the maximum water depth in all rasters.
You need this to normalize the colourscale accross
all plots when teh imshow is done later.
"""
try:
print("Calculating max drape raster value by scanning rasters...")
max_water_depth = findmaxval_multirasters(FPFiles)
drape_max = max_water_depth
except:
print("Something went wrong trying to obtain the max value in \
your drape raster file list.")
finally:
print("The drape(s) max value is set to: ", drape_max)
#im = mpimg.AxesImage()
for i in range(n_files):
print("The floodplain file name is: ", FPFiles[i])
FP_raster = LSDMap_IO.ReadRasterArrayBlocks(FPFiles[i])
#FP_raster = np.ma.masked_where(FP_raster <= 0, FP_raster)
filename = os.path.basename(FPFiles[i])
title = lsdlabels.make_line_label(filename)
print(title)
low_values_index = FP_raster < drape_min_threshold
FP_raster[low_values_index] = np.nan
im = ax_arr[i].imshow(hillshade, "gray", extent=extent_raster, interpolation="nearest")
"""
Now we can set vmax to be the maximum water depth we calcualted earlier, making our separate
subplots all have the same colourscale
"""
im = ax_arr[i].imshow(FP_raster, cmap, extent=extent_raster,
alpha=1.0, interpolation="nearest",
vmin=drape_min_threshold,
vmax=drape_max)
ax_arr[i].set_title(title)
pp.setp( ax_arr[i].xaxis.get_majorticklabels(), rotation=70 )
f.subplots_adjust(right=0.85)
cax = f.add_axes([0.9, 0.1, 0.03, 0.8])
cbar = f.colorbar(im, cax=cax)
cbar.set_label(cbar_label)
#cbar.set_ticks(np.linspace(0, 8, 8))
#cbar = LSDP.colours.colorbar_index(f, cax, 8, cmap,
# drape_min_threshold, drape_max)
#tick_locator = ticker.MaxNLocator(nbins=8)
#cbar.locator = tick_locator
#cbar.update_ticks()
f.text(0.5, 0.04, 'Easting (m)', ha='center', fontsize=17)
f.text(0.04, 0.5, 'Northing (m)', va='center', rotation='vertical', fontsize=17)
[docs]def MultiDrapeErodeDiffMaps(DataDir, ElevationRaster, DrapeRasterWild, cmap,
drape_min_threshold=None, cbar_label=None,
drape_max_threshold=None,
middle_mask_range=None):
"""Plots multiple drape maps of erosion/deposition (a DEM of difference)
over a hillshade raster of the basin.
Takes a wildcard for the drapes
Expexts a single elevation raster for the background hillshade,
but this could be modified in future.
Parameters:
DataDir (str): Path to the directory containing the data files
ElevationRaster (str): Name of the elevation raster used to create the hillshade
DrapeRasterWild (str): Wildcard string used to find all the drape files in the
directory.
cmap: Can be the string name of a colourmap, or a Colourmap object
drape_min_threshold (float, optional): Minimum value for the drape raster, i.e. values
below this threshold will be masked and not plotted.
drape_max_threshold (float, optional): Maximum value for the drape raster, i.e. values
above this value will be masked and not plotted.
cbar_label (str, optional): Label for the colourbar on the figure. This
is the colourbar for the drape colourmap.
middle_mask_range (tuple, optional): A tuple or list of two values, used
to mask an inner range of values in the drape raster.
e.g. if you pass `(-0.1, 0.1)` then all the values
in the range -0.1 to 0.1 will be masked and not plotted
on the final map. Use for masking very small values
either side of zero.
Notes:
Consider, if plotting multiple datasets, how you
are going to deal with min a max values in the colur range.
imshow will automatically set vmin and vmax and stretch the colour bar
over this - which can be visually misleading. Ideally, you
want to have the same colour map used for *all* subplots, and
this is not default behaviour.
Note: If `drape_max_threshold` is not set, the function searches for the maximum value
in the range of rasters found by expanding the `DrapeRasterWild` argument
and searching for the maximum value out of all rasters found.
Raises:
Exception: If the maximum value in the drape maps could not be found.
Author: DAV & FJC
"""
#import lsdmatplotlibextensions as mplext
f, ax_arr = pp.subplots(2, 2, figsize=(10, 5), sharex=True, sharey=True)
ax_arr = ax_arr.ravel()
FPFiles = sorted(glob(DataDir+DrapeRasterWild), key=str)
n_files = len(FPFiles)
print("Number of files = ", n_files)
elev_raster_file = DataDir + ElevationRaster
hillshade = LSDMap_BP.Hillshade(elev_raster_file)
#hillshade_array = LSDP.ReadRasterArrayBlocks(elev_raster_file)
# now get the extent
extent_raster = LSDMap_IO.GetRasterExtent(elev_raster_file)
x_min = extent_raster[0]
x_max = extent_raster[1]
y_min = extent_raster[2]
y_max = extent_raster[3]
# now get the tick marks
n_target_tics = 5
xlocs, ylocs, new_x_labels, new_y_labels = LSDMap_BP.GetTicksForUTM(
elev_raster_file,
x_max,
x_min,
y_max,
y_min,
n_target_tics)
print("xmax: " + str(x_max))
print("xmin: " + str(x_min))
print("ymax: " + str(y_max))
print("ymin: " + str(y_min))
"""
Find the maximum water depth in all rasters.
You need this to normalize the colourscale accross
all plots when teh imshow is done later.
"""
if drape_max_threshold is None:
try:
print("Calculating max drape raster value by scanning rasters...")
max_water_depth = findmaxval_multirasters(FPFiles)
drape_max_threshold = max_water_depth
except ValueError:
print("Something went wrong trying to obtain the max value in \
your drape raster file list.")
finally:
print("The drape(s) max value is set to: ", drape_max_threshold)
if drape_min_threshold is None:
try:
print("Calculating min drape raster value by scanning rasters...")
min_water_depth = findminval_multirasters(FPFiles)
drape_min_threshold = min_water_depth
except ValueError:
print("Something went wrong trying to obtain the min value in \
your drape raster file list.")
finally:
print("The drape(s) min value is set to: ", drape_min_threshold)
for i in range(n_files):
print("The floodplain file name is: ", FPFiles[i])
FP_raster = LSDMap_IO.ReadRasterArrayBlocks(FPFiles[i])
#FP_raster = np.ma.masked_where(FP_raster <= 0, FP_raster)
filename = os.path.basename(FPFiles[i])
title = lsdlabels.make_line_label(filename)
print(title)
# Mask the extreme high values
hi_values_index = FP_raster > drape_max_threshold
FP_raster[hi_values_index] = np.nan
# Mask the extreme low values
lo_values_index = FP_raster < drape_min_threshold
FP_raster[lo_values_index] = np.nan
# Mask the middle values that are really close to zero (i.e. if you
# have negative and positive values in the raster, such as in a DEM
# of difference with both erosion and deposition.)
if middle_mask_range is not None:
masked_mid_values_index = (np.logical_and(FP_raster > middle_mask_range[0],
FP_raster < middle_mask_range[1]))
FP_raster[masked_mid_values_index] = np.nan
im = ax_arr[i].imshow(hillshade, "gray", extent=extent_raster, interpolation="nearest")
"""
Now we can set vmax to be the maximum water depth we calcualted earlier, making our separate
subplots all have the same colourscale
"""
im = ax_arr[i].imshow(FP_raster, cmap, extent=extent_raster,
alpha=1.0, interpolation="nearest",
vmin=drape_min_threshold,
vmax=drape_max_threshold)
ax_arr[i].set_title(title)
pp.setp( ax_arr[i].xaxis.get_majorticklabels(), rotation=70 )
f.subplots_adjust(right=0.85)
cax = f.add_axes([0.9, 0.1, 0.03, 0.8])
cbar = f.colorbar(im, cax=cax)
cbar.set_label(cbar_label)
#cbar.set_ticks(np.linspace(0, 8, 8))
#cbar = mplext.colours.colorbar_index(f, cax, 8, cmap,
# drape_min_threshold, drape_max)
#tick_locator = ticker.MaxNLocator(nbins=8)
#cbar.locator = tick_locator
#cbar.update_ticks()
f.text(0.5, 0.04, 'Easting (m)', ha='center', fontsize=17)
f.text(0.04, 0.5, 'Northing (m)', va='center', rotation='vertical', fontsize=17)