def create_variable(name, attributes, t_chunk): # Normalize variable name name = name.replace('.', '_').replace(' ', '_').replace('-', '_') var = nc.createVariable(name, 'f4', ('time', 'layer', 'x', 'y',), fill_value=fillvalue, chunksizes=(t_chunk, z_chunk, x_chunk, y_chunk,), zlib=True) for k, v in attributes.iteritems(): try: var.setncattr(k, v) except: logger.exception("Could not set variable attribute {} on {}. Check that its value is supported in NetCDF4.".format(k, name)) return var
def get_output_objects(self): headfile = tuple() cellfile = tuple() # Find output files we want to process. These are the defaults if # no specific file is specified in the configuration for u, f, b in zip(self.mf.external_units, self.mf.external_fnames, self.mf.external_binflag): _, ext = os.path.splitext(f) if ext == ".bud": cellfile = (u, f, b) elif ext == ".bhd": headfile = (u, f, b) else: pass # Headfile head_obj = None with LoggingTimer("Loading head file", logger.info): try: if self.head_file: head_obj = flopy_binary.HeadFile(self.head_file, precision=self.precision) elif headfile: head_obj = flopy_binary.HeadFile(headfile[1], precision=self.precision) else: logger.warning("No Headfile found") except BaseException: logger.exception("Exception occured when trying to load the HeadFile into flopy. Skipping!") # Cell budget file cell_obj = None with LoggingTimer("Loading cell budget file", logger.info): try: if self.cbud_file: cell_obj = flopy_binary.CellBudgetFile(self.cbud_file, precision=self.precision) elif cellfile: cell_obj = flopy_binary.CellBudgetFile(cellfile[1], precision=self.precision) else: logger.warning("No CellBudget file found") except BaseException: logger.exception("Exception occured when trying to load the CellBudgetFile into flopy. Skipping!") return dict(head_obj=head_obj, cell_obj=cell_obj)
def to_netcdf(self, output_file): fillvalue = -9999.9 outputs = self.get_output_objects() head_obj = outputs["head_obj"] cell_obj = outputs["cell_obj"] if os.path.exists(output_file): os.remove(output_file) with LoggingTimer("Setting up NetCDF file", logger.info): # Metadata z_size, x_size, y_size = self.zs.shape x_chunk = int(x_size / 4) + 1 y_chunk = int(y_size / 4) + 1 z_chunk = z_size min_vertical = np.min(self.zs) max_vertical = np.max(self.zs) nc = netCDF4.Dataset(output_file, "w") nc.setncattr("Conventions", "CF-1.6") nc.setncattr("date_created", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:00Z")) nc.setncattr("geospatial_vertical_positive", "up") nc.setncattr("geospatial_vertical_min", min_vertical) nc.setncattr("geospatial_vertical_max", max_vertical) nc.setncattr("geospatial_vertical_resolution", "variable") nc.setncattr("featureType", "Grid") nc.setncattr("origin_x", self.grid_x) nc.setncattr("origin_y", self.grid_y) nc.setncattr("origin_crs", self.config_crs) nc.setncattr("grid_rotation_from_origin", self.grid_rotation) for k, v in self.global_attributes.iteritems(): try: nc.setncattr(k, v) except: logger.exception("Could not set global attribute {}. Check that its value is supported in NetCDF4.".format(k)) # Dimensions nc.createDimension('x', x_size) nc.createDimension('y', y_size) nc.createDimension('layer', z_size) # Metadata variables crs = nc.createVariable("crs", "i4") crs.long_name = "http://www.opengis.net/def/crs/EPSG/0/4326" crs.epsg_code = "EPSG:4326" crs.semi_major_axis = float(6378137.0) crs.inverse_flattening = float(298.257223563) # Latitude lat = nc.createVariable('latitude', 'f8', ('x', 'y',), chunksizes=(x_chunk, y_chunk,)) lat.units = "degrees_north" lat.standard_name = "latitude" lat.long_name = "latitude" lat.axis = "Y" lat[:] = self.ys # Longitude lon = nc.createVariable('longitude', 'f8', ('x', 'y',), chunksizes=(x_chunk, y_chunk,)) lon.units = "degrees_east" lon.standard_name = "longitude" lon.long_name = "longitude" lon.axis = "X" lon[:] = self.xs # Elevation ele = nc.createVariable('elevation', 'f8', ('layer', 'x', 'y',), chunksizes=(z_chunk, x_chunk, y_chunk,), zlib=True) ele.units = "meters" ele.standard_name = "elevation" ele.long_name = "elevation" ele.valid_min = min_vertical ele.valid_max = max_vertical ele.positive = "down" ele[:] = self.zs lay = nc.createVariable('layer', 'f4', ('layer',)) lay.units = '' lay.long_name = 'layer' lay.positive = "down" lay.axis = 'Z' lay[:] = np.arange(0, z_size) delc = nc.createVariable('delc', 'f4', ('x',)) delc.units = 'meters' delc.long_name = "Column spacing in the rectangular grid" delc.setncattr("origin_x", self.grid_x) delc.setncattr("origin_y", self.grid_y) delc.setncattr("origin_crs", self.config_crs) if self.grid_units == 'feet': delc[:] = self.dis.delc.array[::-1] * 0.3048 else: delc[:] = self.dis.delc.array[::-1] if self.grid_rotation != 0: delc.comments = textwrap.dedent("""This is the column spacing that applied to the UNROTATED grid. \ This grid HAS been rotated before being saved to NetCDF. \ To compute the unrotated grid, use the origin point and this array.""") delr = nc.createVariable('delr', 'f4', ('y')) delr.units = 'meters' delr.long_name = "Row spacing in the rectangular grid" delr.setncattr("origin_x", self.grid_x) delr.setncattr("origin_y", self.grid_y) delr.setncattr("origin_crs", self.config_crs) if self.grid_units == 'feet': delr[:] = self.dis.delr.array[::-1] * 0.3048 else: delr[:] = self.dis.delr.array[::-1] if self.grid_rotation != 0: delr.comments = textwrap.dedent("""This is the row spacing that applied to the UNROTATED grid. \ This grid HAS been rotated before being saved to NetCDF. \ To compute the unrotated grid, use the origin point and this array.""") # Workaround for CF/CDM. # http://www.unidata.ucar.edu/software/thredds/current/netcdf-java/reference/StandardCoordinateTransforms.html # "explicit_field" exp = nc.createVariable('VerticalTransform', 'S1') exp.transform_name = "explicit_field" exp.existingDataField = "elevation" exp._CoordinateTransformType = "vertical" exp._CoordinateAxes = "layer" def create_time(time_values, t_chunk): nc.createDimension("time", len(time_values)) time = nc.createVariable("time", "f8", ("time",), chunksizes=(t_chunk,)) time.units = "{0} since {1}".format(self.time_units, self.base_date.isoformat().split('.')[0].split('+')[0] + "Z") time.standard_name = "time" time.long_name = "time of measurement" time.calendar = "gregorian" time[:] = np.asarray(time_values) def create_variable(name, attributes, t_chunk): # Normalize variable name name = name.replace('.', '_').replace(' ', '_').replace('-', '_') var = nc.createVariable(name, 'f4', ('time', 'layer', 'x', 'y',), fill_value=fillvalue, chunksizes=(t_chunk, z_chunk, x_chunk, y_chunk,), zlib=True) for k, v in attributes.iteritems(): try: var.setncattr(k, v) except: logger.exception("Could not set variable attribute {} on {}. Check that its value is supported in NetCDF4.".format(k, name)) return var # Headfile if head_obj is not None: with LoggingTimer("Writing HEAD to file", logger.info): # Time time_values = head_obj.get_times() t_chunk = min(len(time_values), 100) create_time(time_values, t_chunk) attrs = dict(standard_name='head', long_name='head', coordinates='time layer latitude longitude', units="{0}".format(self.grid_units)) head = create_variable('head', attrs, t_chunk) for i, time in enumerate(time_values): head_array = head_obj.get_data(totim=time) for f in self.fills: head_array[head_array == f] = fillvalue head_array[head_array <= -1e15] = fillvalue head[i, :, :, :] = head_array[:, :, ::-1] head_obj.close() if cell_obj is not None: if nc.variables.get("time") is None: # Time time_values = cell_obj.get_times() t_chunk = min(len(time_values), 100) create_time(time_values, t_chunk) for j, var_name in enumerate(cell_obj.unique_record_names()): standard_var_name = var_name.strip().lower().replace(' ', '_') with LoggingTimer("Writing {} to file".format(standard_var_name), logger.info): attrs = dict(units="{0}^3/time".format(self.grid_units), standard_name=standard_var_name, long_name=standard_var_name.upper().replace("_", ""), coordinates="time layer latitude longitude") var = create_variable(standard_var_name, attrs, t_chunk) # Average the flows onto the grid center centered_variable = None if standard_var_name in ["flow_right_face", "flow_front_face", "flow_lower_face"]: vname = '{0}_centered'.format(standard_var_name) if standard_var_name == "flow_right_face": standard_name = "grid_directed_groundwater_velocity_in_the_u_direction" elif standard_var_name == "flow_front_face": standard_name = "grid_directed_groundwater_velocity_in_the_w_direction" elif standard_var_name == "flow_lower_face": standard_name = "grid_directed_groundwater_velocity_in_the_v_direction" attrs = dict(units="{0}^3/time".format(self.grid_units), standard_name=standard_name, long_name=vname.upper().replace("_", ""), coordinates="time layer latitude longitude") centered_variable = create_variable(vname, attrs, t_chunk) centered_variable[:] = fillvalue for i, time in enumerate(time_values): cell_array = cell_obj.get_data(text=var_name, totim=time, full3D=True) if isinstance(cell_array, list) and len(cell_array) == 1: cell_array = cell_array[0] elif isinstance(cell_array, list) and len(cell_array) > 1: # Sum values if there are more than one in a grid cell. # This was suggested by Chris L. on the 12/22/14 call. cell_array = np.sum(cell_array, axis=0) elif isinstance(cell_array, list) and len(cell_array) == 0: # No data returned logger.warning("No data returned for '{!s}' at time index {!s} ({!s}).".format(var_name.strip(), i, time)) cell_array = np.ma.zeros(var.shape[1:]) cell_array.mask = True for f in self.fills: cell_array[cell_array == f] = fillvalue cell_array[cell_array <= -1e15] = fillvalue cell_array[cell_array == 0.] = fillvalue try: if len(cell_array.shape) == 2: # Returned a 2D array, so set data to the top layer var[i, 0, :, :] = cell_array[:, ::-1] else: # Returned a 3D array var[i, :, :, :] = cell_array[:, :, ::-1] except IndexError: logger.exception("Could not save variable {!s} into NetCDF file. Trying to fit {!s} into {!s}".format(var_name.strip(), var[i, :, :, :].shape, cell_array.shape)) # Average the flows onto the grid center if centered_variable is not None: z, m, n = cell_array.shape new_cell_data = var[i, :, :, :] if standard_var_name == "flow_right_face": # We lose the last column. averaged_array = 0.5 * (new_cell_data[:, :, 0:n-1] + new_cell_data[:, :, 1:n]) centered_variable[i, :, :, 0:-1] = averaged_array elif standard_var_name == "flow_lower_face": # We lose the last row averaged_array = 0.5 * (new_cell_data[:, 0:m-1, :] + new_cell_data[:, 1:m, :]) centered_variable[i, :, 0:-1, :] = averaged_array elif standard_var_name == "flow_front_face": # We lose the first vertical averaged_array = 0.5 * (new_cell_data[0:z-1, :, :] + new_cell_data[1:z, :, :]) centered_variable[i, 1:, :, :] = averaged_array cell_obj.close() with LoggingTimer("Writing NetCDF file", logger.info): nc.sync() nc.close() return nc