FV3 Bundle
NetcdfIO.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2017 UCAR
3  *
4  * This software is licensed under the terms of the Apache Licence Version 2.0
5  * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6  */
7 
8 #include "fileio/NetcdfIO.h"
9 
10 #include <netcdf.h>
11 
12 #include <iostream>
13 #include <memory>
14 #include <string>
15 
16 #include "oops/util/abor1_cpp.h"
17 #include "oops/util/datetime_f.h"
18 #include "oops/util/Duration.h"
19 #include "oops/util/Logger.h"
20 
21 
22 ////////////////////////////////////////////////////////////////////////
23 // Implementation of IodaIO for netcdf.
24 ////////////////////////////////////////////////////////////////////////
25 
26 namespace ioda {
27 // -----------------------------------------------------------------------------
28 /*!
29  * \details This constructor will open the netcdf file. If opening in read
30  * mode, the parameters nlocs, nobs, nrecs and nvars will be set
31  * by querying the size of dimensions of the same names in the input
32  * file. If opening in write mode, the parameters will be set from the
33  * same named arguements to this constructor.
34  *
35  * \param[in] FileName Path to the netcdf file
36  * \param[in] FileMode "r" for read, "w" for overwrite to an existing file
37  * and "W" for create and write to a new file.
38  * \param[in] Nlocs Number of unique locations in the obs data.
39  * \param[in] Nobs Number of unique observations in the obs data.
40  * \param[in] Nrecs Number of unique records in the obs data. Records are
41  * atomic units that will remain intact when obs are
42  * distributed across muliple process elements. A single
43  * radiosonde sounding would be an example.
44  * \param[in] Nvars Number of unique varibles in the obs data.
45  */
46 
47 NetcdfIO::NetcdfIO(const std::string & FileName, const std::string & FileMode,
48  const std::size_t & Nlocs, const std::size_t & Nobs,
49  const std::size_t & Nrecs, const std::size_t & Nvars) {
50  int retval_;
51 
52  // Set the data members to the file name, file mode and provide a trace message.
53  fname_ = FileName;
54  fmode_ = FileMode;
55  nlocs_ = Nlocs;
56  nobs_ = Nobs;
57  nrecs_ = Nrecs;
58  nvars_ = Nvars;
59  oops::Log::trace() << __func__ << " fname_: " << fname_ << " fmode_: " << fmode_ << std::endl;
60 
61  // Open the file. The fmode_ values that are recognized are:
62  // "r" - read
63  // "w" - write, disallow overriting an existing file
64  // "W" - write, allow overwriting an existing file
65  if (fmode_ == "r") {
66  retval_ = nc_open(fname_.c_str(), NC_NOWRITE, &ncid_);
67  } else if (fmode_ == "w") {
68  retval_ = nc_create(fname_.c_str(), NC_NOCLOBBER|NC_NETCDF4, &ncid_);
69  } else if (fmode_ == "W") {
70  retval_ = nc_create(fname_.c_str(), NC_CLOBBER|NC_NETCDF4, &ncid_);
71  } else {
72  oops::Log::error() << __func__ << ": Unrecognized FileMode: " << fmode_ << std::endl;
73  oops::Log::error() << __func__ << ": Must use one of: 'r', 'w', 'W'" << std::endl;
74  ABORT("Unrecognized file mode for NetcdfIO constructor");
75  }
76 
77  // Abort if open failed
78  if (retval_ != NC_NOERR) {
79  oops::Log::error() << __func__ << ": Unable to open file '" << fname_
80  << "' in mode: " << fmode_ << std::endl;
81  ABORT("Unable to open file");
82  }
83 
84  // When in read mode, the constructor is responsible for setting
85  // the data members nlocs_, nobs_, nrecs_ and nvars_.
86  //
87  // The old files have nobs and optionally nchans.
88  // If nchans is present, nvars = nchans
89  // If nchans is not present, nvars = 1
90  // Then:
91  // nlocs = nobs / nvars
92  //
93  // The new files have nlocs, nobs, nrecs, nvars.
94  //
95  // The way to tell if you have a new file versus and old file is that
96  // only the new files have a dimension named nrecs.
97 
98  if (fmode_ == "r") {
99  // First, check what dimensions we have in the file.
100  retval_ = nc_inq_dimid(ncid_, "nrecs", &nrecs_id_);
101  have_nrecs_ = (retval_ == NC_NOERR);
102  retval_ = nc_inq_dimid(ncid_, "nobs", &nobs_id_);
103  have_nobs_ = (retval_ == NC_NOERR);
104  retval_ = nc_inq_dimid(ncid_, "nlocs", &nlocs_id_);
105  have_nlocs_ = (retval_ == NC_NOERR);
106  retval_ = nc_inq_dimid(ncid_, "nvars", &nvars_id_);
107  have_nvars_ = (retval_ == NC_NOERR);
108 
109  retval_ = nc_inq_dimid(ncid_, "nchans", &nchans_id_);
110  have_nchans_ = (retval_ == NC_NOERR);
111 
112  if (have_nrecs_) {
113  // nrecs is present --> new file
114  nc_inq_dimlen(ncid_, nlocs_id_, &nlocs_);
115  nc_inq_dimlen(ncid_, nobs_id_, &nobs_);
116  nc_inq_dimlen(ncid_, nrecs_id_, &nrecs_);
117  nc_inq_dimlen(ncid_, nvars_id_, &nvars_);
118  } else {
119  // nrecs is not present --> old file
120  nc_inq_dimlen(ncid_, nobs_id_, &nobs_);
121 
122  if (have_nchans_) {
123  nc_inq_dimlen(ncid_, nchans_id_, &nvars_);
124  } else {
125  nvars_ = 1;
126  }
127 
128  nlocs_ = nobs_ / nvars_;
129  nrecs_ = nlocs_;
130  }
131  }
132 
133  // When in write mode, create dimensions in the output file based on
134  // nlocs_, nobs_, nrecs_, nvars_.
135  if ((fmode_ == "W") || (fmode_ == "w")) {
136  retval_ = nc_def_dim(ncid_, "nlocs", Nlocs, &nlocs_id_);
137  have_nlocs_ = (retval_ == NC_NOERR);
138  retval_ = nc_def_dim(ncid_, "nobs", Nobs, &nobs_id_);
139  have_nobs_ = (retval_ == NC_NOERR);
140  retval_ = nc_def_dim(ncid_, "nrecs", Nrecs, &nrecs_id_);
141  have_nrecs_ = (retval_ == NC_NOERR);
142  retval_ = nc_def_dim(ncid_, "nvars", Nvars, &nvars_id_);
143  have_nvars_ = (retval_ == NC_NOERR);
144  }
145  }
146 
147 // -----------------------------------------------------------------------------
148 
150  oops::Log::trace() << __func__ << " fname_: " << fname_ << std::endl;
151 
152  nc_close(ncid_);
153  }
154 
155 // -----------------------------------------------------------------------------
156 /*!
157  * \brief Read data from netcdf file to memory
158  *
159  * \details The three ReadVar methods are the same with the exception of the
160  * datatype that is being read (integer, float, double). It is the
161  * caller's responsibility to allocate memory to hold the data being
162  * read. The caller then passes a pointer to that memory for the VarData
163  * argument.
164  *
165  * \param[in] VarName Name of dataset in the netcdf file
166  * \param[out] VarData Pointer to memory that will receive the file data
167  */
168 
169 void NetcdfIO::ReadVar(const std::string & VarName, int* VarData) {
170  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
171 
172  std::string ErrorMsg;
173 
174  ErrorMsg = "NetcdfIO::ReadVar: Netcdf dataset not found: " + VarName;
175  CheckNcCall(nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_), ErrorMsg);
176 
177  ErrorMsg = "NetcdfIO::ReadVar: Unable to read dataset: " + VarName;
178  CheckNcCall(nc_get_var_int(ncid_, nc_varid_, VarData), ErrorMsg);
179  }
180 
181 // -----------------------------------------------------------------------------
182 
183 void NetcdfIO::ReadVar(const std::string & VarName, float* VarData) {
184  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
185 
186  std::string ErrorMsg;
187 
188  ErrorMsg = "NetcdfIO::ReadVar: Netcdf dataset not found: " + VarName;
189  CheckNcCall(nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_), ErrorMsg);
190 
191  ErrorMsg = "NetcdfIO::ReadVar: Unable to read dataset: " + VarName;
192  CheckNcCall(nc_get_var_float(ncid_, nc_varid_, VarData), ErrorMsg);
193  }
194 
195 // -----------------------------------------------------------------------------
196 
197 void NetcdfIO::ReadVar(const std::string & VarName, double* VarData) {
198  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
199 
200  std::string ErrorMsg;
201 
202  ErrorMsg = "NetcdfIO::ReadVar: Netcdf dataset not found: " + VarName;
203  CheckNcCall(nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_), ErrorMsg);
204 
205  ErrorMsg = "NetcdfIO::ReadVar: Unable to read dataset: " + VarName;
206  CheckNcCall(nc_get_var_double(ncid_, nc_varid_, VarData), ErrorMsg);
207  }
208 
209 // -----------------------------------------------------------------------------
210 /*!
211  * \brief Write data from memory to netcdf file
212  *
213  * \details The three WriteVar methods are the same with the exception of the
214  * datatype that is being written (integer, float, double). It is the
215  * caller's responsibility to allocate and assign memory to the data
216  * that are to be written. The caller then passes a pointer to that
217  * memory for the VarData argument.
218  *
219  * \param[in] VarName Name of dataset in the netcdf file
220  * \param[in] VarData Pointer to memory that will be written into the file
221  */
222 
223 void NetcdfIO::WriteVar(const std::string & VarName, int* VarData) {
224  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
225 
226  std::string ErrorMsg;
227 
228  if (nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_) != NC_NOERR) {
229  // Var does not exist, so create it
230  ErrorMsg = "NetcdfIO::WriteVar: Unable to create variable dataset: " + VarName;
231  CheckNcCall(nc_def_var(ncid_, VarName.c_str(), NC_FLOAT, 1, &nlocs_id_, &nc_varid_), ErrorMsg);
232  }
233 
234  ErrorMsg = "NetcdfIO::WriteVar: Unable to write dataset: " + VarName;
235  CheckNcCall(nc_put_var_int(ncid_, nc_varid_, VarData), ErrorMsg);
236  }
237 
238 // -----------------------------------------------------------------------------
239 
240 void NetcdfIO::WriteVar(const std::string & VarName, float* VarData) {
241  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
242 
243  std::string ErrorMsg;
244 
245  if (nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_) != NC_NOERR) {
246  // Var does not exist, so create it
247  ErrorMsg = "NetcdfIO::WriteVar: Unable to create variable dataset: " + VarName;
248  CheckNcCall(nc_def_var(ncid_, VarName.c_str(), NC_FLOAT, 1, &nlocs_id_, &nc_varid_), ErrorMsg);
249  }
250 
251  ErrorMsg = "NetcdfIO::WriteVar: Unable to write dataset: " + VarName;
252  CheckNcCall(nc_put_var_float(ncid_, nc_varid_, VarData), ErrorMsg);
253  }
254 
255 // -----------------------------------------------------------------------------
256 
257 void NetcdfIO::WriteVar(const std::string & VarName, double* VarData) {
258  oops::Log::trace() << __func__ << " VarName: " << VarName << std::endl;
259 
260  std::string ErrorMsg;
261 
262  if (nc_inq_varid(ncid_, VarName.c_str(), &nc_varid_) != NC_NOERR) {
263  // Var does not exist, so create it
264  ErrorMsg = "NetcdfIO::WriteVar: Unable to create variable dataset: " + VarName;
265  CheckNcCall(nc_def_var(ncid_, VarName.c_str(), NC_FLOAT, 1, &nlocs_id_, &nc_varid_), ErrorMsg);
266  }
267 
268  ErrorMsg = "NetcdfIO::WriteVar: Unable to write dataset: " + VarName;
269  CheckNcCall(nc_put_var_double(ncid_, nc_varid_, VarData), ErrorMsg);
270  }
271 
272 // -----------------------------------------------------------------------------
273 /*!
274  * \brief Read and format the date, time values
275  *
276  * \details This method will read in the date and time information (timestamp)
277  * from the netcdf file, and convert them into a convenient format for
278  * usage by the JEDI system. Currently, the netcdf files contain an
279  * attribute called "date_time" that holds the analysis time for
280  * the obs data in the format yyyymmddhh. For example April 15, 2018
281  * at 00Z is recorded as 2018041500. The netcdf file also contains
282  * a time variable (float) which is the offset from the date_time
283  * value in hours. This method will convert the date time information to two
284  * integer vectors. The first is the date (yyyymmdd) and the second
285  * is the time (hhmmss). With the above date_time example combined
286  * with a time value of -3.5 (hours), the resulting date and time entries
287  * in the output vectors will be date = 20180414 and time = 233000.
288  *
289  * Eventually, the yyyymmdd and hhmmss values can be recorded in the
290  * netcdf file as thier own datasets and this method could be removed.
291  *
292  * \param[out] VarDate Date portion of the timestamp values (yyyymmdd)
293  * \param[out] VarTime Time portion of the timestamp values (hhmmss)
294  */
295 
296 void NetcdfIO::ReadDateTime(int* VarDate, int* VarTime) {
297  int Year;
298  int Month;
299  int Day;
300  int Hour;
301  int Minute;
302  int Second;
303 
304  std::string ErrorMsg;
305 
306  oops::Log::trace() << __func__ << std::endl;
307 
308  // Read in the date_time attribute which is in the form: yyyymmddhh
309  // Convert the date_time to a Datetime object.
310  int dtvals_;
311  ErrorMsg = "NetcdfIO::ReadDateTime: Unable to read attribute: date_time";
312  CheckNcCall(nc_get_att_int(ncid_, NC_GLOBAL, "date_time", &dtvals_), ErrorMsg);
313 
314  util::DateTime refdt_;
315  datetime_setints_f(&refdt_, dtvals_/100, dtvals_%100);
316 
317  // Read in the time variable and convert to a Duration object. Time is an
318  // offset from the date_time attribute. This fits in nicely with a Duration
319  // object.
320  // Look for "time" and "Obs_Time" for the time variable.
321  if (nc_inq_varid(ncid_, "time", &nc_varid_) != NC_NOERR) {
322  ErrorMsg = "NetcdfIO::ReadDateTime: Unable to find time variable: time OR Obs_Time";
323  CheckNcCall(nc_inq_varid(ncid_, "Obs_Time", &nc_varid_), ErrorMsg);
324  }
325 
326  int dimid_;
327  std::unique_ptr<float[]> OffsetTime;
328  std::size_t vsize_;
329 
330  ErrorMsg = "NetcdfIO::ReadDateTime: Unable to find dimension of time variable";
331  CheckNcCall(nc_inq_vardimid(ncid_, nc_varid_, &dimid_), ErrorMsg);
332 
333  ErrorMsg = "NetcdfIO::ReadDateTime: Unable to find size of dimension of time variable";
334  CheckNcCall(nc_inq_dimlen(ncid_, dimid_, &vsize_), ErrorMsg);
335 
336  OffsetTime.reset(new float[vsize_]);
337  ErrorMsg = "NetcdfIO::ReadDateTime: Unable to read time variable: ";
338  CheckNcCall(nc_get_var_float(ncid_, nc_varid_, OffsetTime.get()), ErrorMsg);
339 
340  // Combine the refdate with the offset time, and convert to yyyymmdd and
341  // hhmmss values.
342  std::unique_ptr<util::DateTime> dt_(new util::DateTime[vsize_]);
343  for (std::size_t i = 0; i < vsize_; ++i) {
344  dt_.get()[i] = refdt_ + util::Duration(static_cast<int>(OffsetTime.get()[i] * 3600));
345  dt_.get()[i].toYYYYMMDDhhmmss(Year, Month, Day, Hour, Minute, Second);
346 
347  VarDate[i] = Year*10000 + Month*100 + Day;
348  VarTime[i] = Hour*10000 + Minute*100 + Second;
349  }
350  }
351 
352 // -----------------------------------------------------------------------------
353 /*!
354  * \brief print method for stream output
355  *
356  * \details This method is supplied for the Printable base class. It defines
357  * how to print an object of this class in an output stream.
358  */
359 
360 void NetcdfIO::print(std::ostream & os) const {
361  os << "Netcdf: In " << __FILE__ << " @ " << __LINE__ << std::endl;
362  }
363 
364 // -----------------------------------------------------------------------------
365 /*!
366  * \brief check results of netcdf call
367  *
368  * \details This method will check the return code from a netcdf API call.
369  * Successful completion of the call is indicated by the return
370  * code being equal to NC_NOERR. If the call was not successful,
371  * then the error message is written to the OOPS log, and is also
372  * sent to the OOPS ABORT call (execution is aborted).
373  *
374  * \param[in] RetCode Return code from netcdf call
375  * \param[in] ErrorMsg Message for the OOPS error logger
376  */
377 
378 void NetcdfIO::CheckNcCall(int RetCode, std::string & ErrorMsg) {
379  if (RetCode != NC_NOERR) {
380  oops::Log::error() << ErrorMsg << " (" << RetCode << ")" << std::endl;
381  ABORT(ErrorMsg);
382  }
383 }
384 
385 } // namespace ioda
std::size_t nobs_
number of unique observations
l_size ! loop over number of fields ke do je do i
void print(std::ostream &os) const
print method for stream output
Definition: NetcdfIO.cc:360
std::size_t nlocs_
number of unique locations
bool have_nlocs_
This data member is a flag that indicates the existence of the "nlocs" dimension in the opened netcdf...
Definition: NetcdfIO.h:119
bool have_nrecs_
This data member is a flag that indicates the existence of the "nrecs" dimension in the opened netcdf...
Definition: NetcdfIO.h:137
std::string fname_
file name
int nobs_id_
This data member holds the netcdf id of the "nobs" dimension in the opened netcdf file...
Definition: NetcdfIO.h:95
void ReadDateTime(int *VarDate, int *VarTime)
Read and format the date, time values.
Definition: NetcdfIO.cc:296
int nvars_id_
This data member holds the netcdf id of the "nvars" dimension in the opened netcdf file...
Definition: NetcdfIO.h:89
int nlocs_id_
This data member holds the netcdf id of the "nlocs" dimension in the opened netcdf file...
Definition: NetcdfIO.h:83
bool have_nobs_
This data member is a flag that indicates the existence of the "nobs" dimension in the opened netcdf ...
Definition: NetcdfIO.h:131
integer error
Definition: mpp.F90:1310
int nc_varid_
This data member holds the netcdf id of the current dataset (variable) in the opened netcdf file...
Definition: NetcdfIO.h:113
void CheckNcCall(int, std::string &)
check results of netcdf call
Definition: NetcdfIO.cc:378
int ncid_
netcdf file id
Definition: NetcdfIO.h:77
bool have_nchans_
This data member is a flag that indicates the existence of the "nchans" dimension in the opened netcd...
Definition: NetcdfIO.h:143
std::string fmode_
file mode ("r" -> read, "w" -> overwrite, "W" -> create and write)
bool have_nvars_
This data member is a flag that indicates the existence of the "nvars" dimension in the opened netcdf...
Definition: NetcdfIO.h:125
void ReadVar(const std::string &VarName, int *VarData)
Read data from netcdf file to memory.
Definition: NetcdfIO.cc:169
int nrecs_id_
This data member holds the netcdf id of the "nrecs" dimension in the opened netcdf file...
Definition: NetcdfIO.h:101
std::size_t nrecs_
number of unique records
void WriteVar(const std::string &VarName, int *VarData)
Write data from memory to netcdf file.
Definition: NetcdfIO.cc:223
int nchans_id_
This data member holds the netcdf id of the "nchans" dimension in the opened netcdf file...
Definition: NetcdfIO.h:107
std::size_t nvars_
number of unique variables
NetcdfIO(const std::string &FileName, const std::string &FileMode, const std::size_t &Nlocs, const std::size_t &Nobs, const std::size_t &Nrecs, const std::size_t &Nvars)
Definition: NetcdfIO.cc:47