import netCDF4 as nc
import numpy as np
import os
from .times_in_wrf_file import times_in_wrf_file
import os
from .....utils.check.errclass import CifValueError
[docs]
def write(self, name, flx_file, date, flx, domain, mode):
""" Write flux data for one timestep to WRF-Chem flux input file
In ``mode="w"``, creates a new NetCDF file with the standard WRF
emission dimensions/variables, and copies the relevant global
attributes from the domain's ``wrfinput_d01`` file. In ``mode="a"``,
locates the matching time index in an existing file, creates the
variable if missing, checks that the horizontal (and vertical)
dimensions match, and writes the flux values (filling masked values
with 0). Supports the unstructured WRF domain by reshaping the
flattened flux back to ``(south_north, west_east)``.
Args:
self (Fluxes): the Fluxes plugin
name (str): Name of variable in flx file
flx_file (str): the file where to write fluxes
date (datetime.datetime): Timestamp of the flux
flx (numpy.ndarray): fluxes data to write, with a single time index
domain: domain plugin
mode (str): 'w' to overwrite, 'a' to append (existing files)
Raises:
CifValueError: If `flx` holds more than one time index, if
``mode='w'`` and `flx_file` already exists, if the requested
`date` is not found exactly once in an existing file
(``mode='a'``), if the horizontal dimensions of `flx` do not
match the file's, if the vertical dimension of `flx` exceeds
the file's, or if `mode` is neither ``'w'`` nor ``'a'``.
"""
if flx.shape[0] != 1:
raise CifValueError("Can only write values for one time index at a time")
# Reshape flx in case it comes on the unstructured wrf domain
if getattr(domain, "unstructured_domain", False):
flx = flx.reshape((flx.shape[0],) + (1,) + domain.zlon2d.shape)
# Define emission variable dimensions
em_dim_names = [
"Time",
"emissions_zdim",
"south_north",
"west_east",
]
em_dim_lens = [None] + list(flx.shape[1:])
# Define emission variable datatype
em_dtype = "f4"
# Define emission variable attributes
em_attr_names = [
"FieldType",
"MemoryOrder",
"description",
"units",
"stagger",
"coordinates"
]
em_attr_vals = [
104.,
"XYZ",
name+" emission",
"mole/km2/hr",
"Z",
"XLONG XLAT"
]
# Cannot work with nested domains
dom = 1
# mode w: write file from scratch
if mode=="w":
if os.path.exists(flx_file):
raise CifValueError("Output file exists")
# Open output file for writing
ncf = nc.Dataset(flx_file, "w")
# Create dimensions
# For emission variables:
for n in range(len(em_dim_names)):
ncf.createDimension(em_dim_names[n],
em_dim_lens[n])
# For Times:
ncf.createDimension("DateStrLen", 19)
# Create variables
times = ncf.createVariable("Times", "S1", ("Time", "DateStrLen"))
var = ncf.createVariable(name, em_dtype, em_dim_names)
# Write Variable attributes
for n in range(len(em_attr_names)):
setattr(var, em_attr_names[n], em_attr_vals[n])
# Fill variables with values
date_str = date.strftime("%Y-%m-%d_%H:%M:%S")
times[:] = [[c for c in date_str]]
var[:] = flx
# Get and write global attributes
# Get these from wrfinput_d01:
glob_attr_names = [
"TITLE",
"CEN_LAT",
"CEN_LON",
"TRUELAT1",
"TRUELAT2",
"MOAD_CEN_LAT",
"STAND_LON",
"POLE_LAT",
"POLE_LON",
"MAP_PROJ",
"DX",
"DY",
"MMINLU",
"NUM_LAND_CAT",
"ISWATER",
"ISLAKE",
"ISICE",
"ISURBAN",
"ISOILWATER"
]
fp_wrfinput = os.path.join(
domain.dir_wrfinput,
f"wrfinput_d{dom:02d}")
wrfin = nc.Dataset(fp_wrfinput)
glob_attr_vals = [getattr(wrfin, nm) for nm in glob_attr_names]
# Change title so that wrf.exe wont complain about versions
version = wrfin.TITLE[len(" OUTPUT FROM REAL_EM "):-len(" PREPROCESSOR")]
title_out = "OUTPUT FROM PREPROCESSING_FOR " + version
glob_attr_vals[0] = title_out
wrfin.close()
# Add date information
glob_attr_names.append("GMT")
glob_attr_vals.append(np.int32(1))
glob_attr_names.append("JULYR")
glob_attr_vals.append(np.int32(date.year))
glob_attr_names.append("JULDAY")
glob_attr_vals.append(np.int32(date.timetuple().tm_yday))
# Write attributes
for n in range(len(glob_attr_names)):
setattr(ncf, glob_attr_names[n], glob_attr_vals[n])
ncf.close()
# mode a: Append to existing file
elif mode=="a":
# Check times
times = times_in_wrf_file(flx_file)
ind = [n for n, t in enumerate(times) if t==date]
# Complain if requested date not found exactly once in file
if len(ind)!=1:
fmt = "%Y-%m-%d %H:%M:%S"
date_str = date.strftime(fmt)
times_str_l = [time.strftime(fmt) for time in times]
times_str = ", ".join(times_str_l)
if len(ind)==0:
msg = "Date {} not found in file {}. Dates in file: {}."
elif len(ind)>1:
msg = ("Date {} found more than once in file {}. " + \
"Dates in file: {}.")
raise CifValueError(msg.format(date_str, flx_file, times_str))
# Write
ncf = nc.Dataset(flx_file, mode)
# If variable does not exist, create it
if not name in ncf.variables:
var = ncf.createVariable(name, em_dtype, em_dim_names)
# Write Variable attributes
for n in range(len(em_attr_names)):
setattr(var, em_attr_names[n], em_attr_vals[n])
if not flx.shape[2:3] == ncf[name].shape[2:3]:
msg = "Horizontal flux dimensions are {}, but in file {} they are {}"
raise CifValueError(msg.format(str(flx.shape[2:3]),
flx_file,
str(ncf[name].shape[2:3])))
if flx.shape[1] > ncf[name].shape[1]:
msg = "Vertical flux dimension is {}, but in file {} it is {}, i.e. too small"
raise CifValueError(msg.format(str(flx.shape[1]),
flx_file,
str(ncf[name].shape[1])))
ncf[name][ind, ...] = 0.0
ncf[name][ind, :flx.shape[1], ...] = flx
# Set missing values to 0 (else WRF might transport
# the fill value)
mask = ncf[name][ind, ...].mask
if np.any(mask):
ncf[name][ind, ...] = np.ma.filled(ncf[name][ind, ...], 0.0)
ncf.close()
else:
raise CifValueError("Unknown mode: " + mode)