Source code for mfexport.results

import numpy as np
from flopy.utils import binaryfile as bf
from mfexport.array_export import export_array, export_array_contours
from mfexport.budget_output import get_bc_flux, read_sfr_output, get_flowja_face
from gisutils import shp2df
from mfexport.pdf_export import sfr_baseflow_pdf, sfr_qaquifer_pdf
from mfexport.shapefile_export import export_shapefile
from mfexport.units import (convert_length_units, convert_time_units,
                    get_length_units, get_time_units, get_unit_text)
from mfexport.utils import get_water_table, make_output_folders

# TODO: update docstrings


[docs] def export_cell_budget(cell_budget_file, grid, binary_grid_file=None, kstpkper=None, text=None, idx=0, precision='single', output_path='postproc', suffix=''): """Read a flow component from MODFLOW binary cell budget output; write to raster. Parameters ---------- cell_budget_file : modflow binary cell budget output grid : rOpen.modflow.grid instance text : cell budget record to read (e.g. 'STREAM LEAKAGE') kstpkper : tuple (timestep, stress period) to read idx : index of list returned by cbbobj (usually 0) outfolder : where to write raster """ print('Exporting cell budget info...') print('file: {}'.format(cell_budget_file)) print('binary grid file: {}'.format(binary_grid_file)) cbbobj = bf.CellBudgetFile(cell_budget_file, precision=precision) if kstpkper is None: kstpkper = cbbobj.get_times()[idx] if np.isscalar(kstpkper[0]): kstpkper = [kstpkper] pdfs_dir, rasters_dir, shps_dir = make_output_folders(output_path) if text is not None and not isinstance(text, list): text = [text] # Use the exact record name byte string, # otherwise records for multiple packages (e.g. RCHA and RCH) # can apparently be confused unique_records = cbbobj.get_unique_record_names() names = [r.decode().strip() for r in cbbobj.get_unique_record_names()] if text is not None: names = list(set(text).intersection(names)) if len(names) == 0: print('{} not found in {}'.format(' '.join(text), cell_budget_file)) outfiles = [] for kstp, kper in kstpkper: print('stress period {}, timestep {}'.format(kper, kstp)) for i, variable in enumerate(names): data = None if variable == 'FLOW-JA-FACE': df = get_flowja_face(cbbobj, binary_grid_file=binary_grid_file, kstpkper=(kstp, kper), idx=idx, precision=precision) # export the vertical fluxes as rasters # (in the downward direction; so fluxes between 2 layers # would be represented in the upper layer) if df is not None and 'kn' in df.columns and np.any(df['kn'] < df['km']): vflux = df.loc[(df['kn'] < df['km'])] nlay = vflux['km'].max() _, nrow, ncol = grid.shape vflux_array = np.zeros((nlay, nrow, ncol)) vflux_array[vflux['kn'].values, vflux['in'].values, vflux['jn'].values] = vflux.q.values data = vflux_array else: data = get_bc_flux(cbbobj, unique_records[i], kstpkper=(kstp, kper), idx=idx) if not isinstance(data, np.ndarray): data = get_bc_flux(cbbobj, unique_records[i], kstpkper=(kstp, kper)) # for example, 1-layer models don't have vertical fluxes if data is None or (len(data) == 0): print(f'{variable}, period {kper}, timestep {kstp} not exported.') continue outfile = '{}/{}_per{}_stp{}{}.tif'.format(rasters_dir, variable, kper, kstp, suffix) export_array(outfile, data, grid, nodata=0) outfiles.append(outfile) return outfiles
[docs] def export_drawdown(heads_file, grid, hdry, hnflo, kstpkper0=None, kstpkper1=None, levels=None, interval=None, export_water_table=True, export_layers=False, output_path='postproc', suffix=''): """Export MODFLOW binary head output to rasters and shapefiles. Parameters ---------- modelname : str model base name grid : rOpen.modflow.grid instance hdry : hdry value from UPW package hnflo : hnflo value from BAS package levels : 1D numpy array Values of equal interval contours to write. shps_outfolder : where to write shapefiles rasters_outfolder : where to write rasters Writes ------ * A raster of heads for each layer and a raster of the water table elevation * Shapefiles of head contours for each layer and the water table. """ print('Exporting drawdown...') print('file: {}'.format(heads_file)) if kstpkper0 is not None: print('from stress period {}, timestep {}'.format(*reversed(kstpkper0))) if kstpkper1 is not None: # convert kstpkper1 to a list of tuples or lists if it isn't if np.isscalar(kstpkper1[0]): kstpkper1 = [kstpkper1] pdfs_dir, rasters_dir, shps_dir = make_output_folders(output_path) # Heads output at drawdown period start hdsobj = bf.HeadFile(heads_file) hds0 = hdsobj.get_data(kstpkper=kstpkper0) hds0[(hds0 > 9999) & (hds0 < 0)] = np.nan if export_water_table: wt0 = get_water_table(hds0, nodata=hdry) for kstp, kper in kstpkper1: print(f'to stress period {kper}, timestep {kstp}') print('\n') if (kstp, kper) == (0, 0): print('kstpkper == (0, 0, no drawdown to export') continue # heads output at drawdown period end hds1 = hdsobj.get_data(kstpkper=(kstp, kper)) hds1[(hds1 > 9999) & (hds1 < 0)] = np.nan if export_water_table: wt1 = get_water_table(hds1, nodata=hdry) wt_ddn = wt0 - wt1 outfiles = [] outfile = '{}/wt-ddn_per{}_stp{}{}.tif'.format(rasters_dir, kper, kstp, suffix) ctr_outfile = '{}/wt-ddn_ctr_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix) export_array(outfile, wt_ddn, grid, nodata=hnflo) export_array_contours(ctr_outfile, wt_ddn, grid, levels=levels, interval=interval, ) outfiles += [outfile, ctr_outfile] if export_layers: ddn = hds0 - hds1 for k, d in enumerate(ddn): outfile = '{}/ddn_lay{}_per{}_stp{}{}.tif'.format(rasters_dir, k, kper, kstp, suffix) ctr_outfile = '{}/ddn_ctr_lay{}_per{}_stp{}{}.shp'.format(shps_dir, k, kper, kstp, suffix) export_array(outfile, d, grid, nodata=hnflo) export_array_contours(ctr_outfile, d, grid, levels=levels ) outfiles += [outfile, ctr_outfile] return outfiles
[docs] def export_heads(heads_file, grid, hdry, hnflo, kstpkper=(0, 0), levels=None, interval=None, export_water_table=True, export_depth_to_water=False, export_layers=False, land_surface_elevations=None, output_path='postproc', suffix=''): """Export MODFLOW binary head output to rasters and shapefiles. Parameters ---------- modelname : str model base name grid : rOpen.modflow.grid instance hdry : hdry value from UPW package hnflo : hnflo value from BAS package levels : 1D numpy array Values of equal interval contours to write. shps_outfolder : where to write shapefiles rasters_outfolder : where to write rasters Writes ------ * A raster of heads for each layer and a raster of the water table elevation * Shapefiles of head contours for each layer and the water table. """ if np.isscalar(kstpkper[0]): kstpkper = [kstpkper] print('Exporting heads...') print('file: {}'.format(heads_file)) pdfs_dir, rasters_dir, shps_dir = make_output_folders(output_path) outfiles = [] for kstp, kper in kstpkper: print('stress period {}, timestep {}'.format(kper, kstp)) # Heads output hdsobj = bf.HeadFile(heads_file) hds = hdsobj.get_data(kstpkper=(kstp, kper)) if export_water_table or export_depth_to_water: wt = get_water_table(hds, nodata=hdry) wt[(wt > 9999) | (wt < 0)] = np.nan outfile = '{}/wt_per{}_stp{}{}.tif'.format(rasters_dir, kper, kstp, suffix) ctr_outfile = '{}/wt_ctr_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix) export_array(outfile, wt, grid, nodata=hnflo) export_array_contours(ctr_outfile, wt, grid, levels=levels, interval=interval) outfiles += [outfile, ctr_outfile] # if the grid has cell bottom information # make an array of 1-based layers containing the water table # at each i, j location if grid.botm is not None: wt_layer = np.argmax((wt > grid.botm), axis=0) wt_layer_outfile = f'{rasters_dir}/wt-layers_per{kper}_stp{kstp}{suffix}.tif' export_array(wt_layer_outfile, wt_layer, grid) outfiles.append(wt_layer_outfile) if export_depth_to_water: if land_surface_elevations is None: raise ValueError(('export_heads: export_depth_to_water option ' 'requires specification of the land surface')) if not isinstance(land_surface_elevations, np.ndarray): land_surface_elevations = np.loadtxt(land_surface_elevations) # Depth to water dtw = land_surface_elevations - wt # Overpressurization op = dtw.copy() # For DTW, mask areas of overpressurization; # For Overpressurization, mask areas where water table is below land surface op = np.ma.masked_array(op, mask=op > 0) dtw = np.ma.masked_array(dtw, mask=dtw < 0) if np.max(dtw) > 0: #dtw_levels = None #if interval is not None: # dtw_levels = np.linspace(0, np.nanmax(dtw), interval) outfile = '{}/dtw_per{}_stp{}{}.tif'.format(rasters_dir, kper, kstp, suffix) ctr_outfile = '{}/dtw_ctr_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix) export_array(outfile, dtw, grid, nodata=hnflo) export_array_contours(ctr_outfile, dtw, grid, interval=interval) outfiles += [outfile, ctr_outfile] else: print('Water table is above land surface everywhere, skipping depth to water.') if np.nanmin(op) < 0: #op_levels = None #if interval is not None: # op_levels = np.linspace(0, np.nanmin(op), interval) outfile = '{}/op_per{}_stp{}{}.tif'.format(rasters_dir, kper, kstp, suffix) ctr_outfile = '{}/op_ctr_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix) export_array(outfile, op, grid, nodata=hnflo) export_array_contours(ctr_outfile, op, grid, interval=interval) outfiles += [outfile, ctr_outfile] else: print('No overpressurization, skipping.') hds[(hds > 9999) | (hds < 0)] = np.nan if export_layers: for k, h in enumerate(hds): outfile = '{}/hds_lay{}_per{}_stp{}{}.tif'.format(rasters_dir, k, kper, kstp, suffix) ctr_outfile = '{}/hds_ctr_lay{}_per{}_stp{}{}.shp'.format(shps_dir, k, kper, kstp, suffix) export_array(outfile, h, grid, nodata=hnflo) export_array_contours(ctr_outfile, h, grid, levels=levels, interval=interval, ) outfiles += [outfile, ctr_outfile] return outfiles
[docs] def export_sfr_results(mf2005_sfr_outputfile=None, mf2005_SfrFile_instance=None, mf6_sfr_stage_file=None, mf6_sfr_budget_file=None, mf6_package_data=None, model=None, model_top=None, grid=None, kstpkper=(0, 0), sfrlinesfile=None, pointsize=0.5, model_length_units=None, model_time_units=None, output_length_units='feet', output_time_units='seconds', gis=True, pdfs=True, output_path='postproc', suffix='', verbose=False): pdfs_dir, rasters_dir, shps_dir = make_output_folders(output_path) m = model if not isinstance(kstpkper, list): kstpkper = [kstpkper] print('Exporting SFR results...') for f in [mf2005_sfr_outputfile, mf6_sfr_stage_file, mf6_sfr_budget_file]: if f is not None: print('file: {}'.format(f)) df = read_sfr_output(mf2005_sfr_outputfile=mf2005_sfr_outputfile, mf2005_SfrFile_instance=mf2005_SfrFile_instance, mf6_sfr_stage_file=mf6_sfr_stage_file, mf6_sfr_budget_file=mf6_sfr_budget_file, mf6_package_data=mf6_package_data, model=model) if model_length_units is None: if model is None: model_length_units = 'meters' else: model_length_units = get_length_units(m) if model_time_units is None: if model is None: model_time_units = 'days' else: model_time_units = get_time_units(m) lmult = convert_length_units(model_length_units, output_length_units) tmult = convert_time_units(model_time_units, output_time_units) unit_text = get_unit_text(output_length_units, output_time_units, 3) if 'GWF' in df.columns: df['Qaquifer'] = -df.GWF # for consistency with MF2005 if 'Qmean' not in df.columns: df['Qmean'] = df[['Qin', 'Qout']].abs().mean(axis=1) # fill nan streamflow values from EXT-OUTFLOW totals # (nan values can cause issues with plotting; as of 8/8/2023 # and MODFLOW 6 version 6.3.0, these appear to be associated with # headwater reaches that are also outlets # (no Qin or Qout to another reach, just EXT-OUTFLOW; the nans # are introduced when the FLOW-JA-FACE results, # which don't include these reaches, are joined to the # full list of SFR reaches) if mf6_sfr_budget_file is not None: na_streamflow = df['Qmean'].isna() df.loc[na_streamflow, 'Qin'] = 0 df.loc[na_streamflow, 'Qout'] = df.loc[na_streamflow, 'EXT-OUTFLOW'] df.loc[na_streamflow, 'Qnet'] = df.loc[na_streamflow, 'EXT-OUTFLOW'] df.loc[na_streamflow, 'Qmean'] = df.loc[na_streamflow, ['Qin', 'Qout']].abs().mean(axis=1) # write columns in the output units df['Qmean_{}'.format(unit_text)] = df.Qmean * lmult**3/tmult df['Qaq_{}'.format(unit_text)] = df.Qaquifer * lmult**3/tmult # add model top comparison if available if isinstance(model_top, str): model_top = np.loadtxt(model_top) elif model_top is None and model is not None: model_top = m.dis.top.array if model_top is not None and 'i' in df.columns and 'j' in df.columns: df['model_top'] = model_top[df.i.values, df.j.values] if 'stage' in df.columns: df['above'] = df.stage - df.model_top groups = df.groupby('kstpkper') outfiles = [] if gis: prj_file = None if sfrlinesfile is not None: sfrlines = shp2df(sfrlinesfile) prj_file = sfrlines[:-4] + '.prj' sfrlines.sort_values(by=['iseg', 'ireach'], inplace=True) geoms = sfrlines.geometry else: #assert sr is not None, \ # 'need SpatialReference instance to locate model grid cells' #dfp = groups.get_group((0, 0)).copy() geoms = None #vertices = sr.get_vertices(dfp.i, dfp.j) #geoms = [Polygon(vrt) for vrt in vertices] for kstp, kper in kstpkper: print('stress period {}, timestep {}'.format(kper, kstp)) dfp = groups.get_group((kstp, kper)).copy() if geoms is not None: dfp['geometry'] = geoms #dfp = gp.GeoDataFrame(dfp) #dfp.crs = sr.proj4_str # to use cell polygons instead of lines # verts = m.sr.get_vertices(df.i.values, df.j.values) #df['geometry'] = [Polygon(v) for v in verts] dfp['stp'] = [t[0] for t in dfp['kstpkper']] dfp['per'] = [t[1] for t in dfp['kstpkper']] dfp.drop('kstpkper', axis=1, inplace=True) # geopandas doesn't like tuples outfile = '{}/sfrout_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix) export_shapefile(outfile, dfp, modelgrid=grid, prj=prj_file) outfiles.append(outfile) #dfp.to_file(outfile) #print('wrote {}'.format(outfile)) if pdfs: # need to add a scale that addresses units for kstp, kper in kstpkper: print('stress period {}, timestep {}'.format(kper, kstp)) df = groups.get_group((kstp, kper)).copy() bf_outfile = '{}/baseflow_per{}_stp{}{}.pdf'.format(pdfs_dir, kper, kstp, suffix) sfr_baseflow_pdf(bf_outfile, df, pointsize=pointsize, verbose=verbose) qaq_outfile = '{}/qaquifer_per{}_stp{}.pdf'.format(pdfs_dir, kper, kstp, suffix) sfr_qaquifer_pdf(qaq_outfile, df, pointsize=pointsize, verbose=verbose) outfiles += [bf_outfile, qaq_outfile] return outfiles