IODA Bundle
IodaEncoder.cpp
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020 NOAA/NWS/NCEP/EMC
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 "IodaEncoder.h"
9 
10 #include <memory>
11 #include <type_traits>
12 
13 #include "eckit/exception/Exceptions.h"
14 #include "ioda/Layout.h"
15 
16 #include <boost/algorithm/string.hpp>
17 
18 
19 namespace Ingester
20 {
21  IodaEncoder::IodaEncoder(const eckit::Configuration& conf) :
22  description_(IodaDescription(conf))
23  {
24  }
25 
27  description_(description)
28  {
29  }
30 
31  std::map<SubCategory, ioda::ObsGroup>
32  IodaEncoder::encode(const std::shared_ptr<DataContainer>& dataContainer, bool append)
33  {
34  auto backendParams = ioda::Engines::BackendCreationParameters();
35 
36  std::map<SubCategory, ioda::ObsGroup> obsGroups;
37 
38  for (const auto& categories : dataContainer->allSubCategories())
39  {
40  // Make the filename string
42  {
43  std::string filename = description_.getFilepath();
44 
45  size_t catIdx = 0;
46  std::map<std::string, std::string> substitutions;
47  for (const auto &catPair : dataContainer->getCategoryMap())
48  {
49  substitutions.insert({catPair.first, categories.at(catIdx)});
50  catIdx++;
51  }
52 
53  backendParams.fileName = makeStrWithSubstitions(filename, substitutions);
54  }
55 
56  backendParams.openMode = ioda::Engines::BackendOpenModes::Read_Write;
58  backendParams.action = append ? ioda::Engines::BackendFileActions::Open : \
59  ioda::Engines::BackendFileActions::Create;
60  backendParams.flush = true;
61  backendParams.allocBytes = dataContainer->size(categories);
62 
64  backendParams);
65 
66  bool foundInvalidDim = false;
68  for (const auto& scale : description_.getDims())
69  {
70  std::size_t size = 0;
71  if (isInteger(scale.size))
72  {
73  size = std::stoi(scale.size);
74  }
75  else
76  {
77  std::string token = scale.size.substr(scale.size.find('.') + 1,
78  scale.size.size());
79 
80  std::string varName = scale.size.substr(0,
81  scale.size.find('.'));
82 
83  if (token == "ncols")
84  {
85  size = dataContainer->get(varName, categories)->ncols();
86  }
87  else if (token == "nrows")
88  {
89  size = dataContainer->get(varName, categories)->nrows();
90  }
91  else
92  {
93  std::ostringstream errStr;
94  errStr << "Tried to get unknown parameter " << token;
95  errStr << " from " << varName;
96  throw eckit::BadParameter(errStr.str());
97  }
98  }
99 
100  auto newDim = ioda::NewDimensionScale<int>(scale.name, size);
101  newDims.push_back(newDim);
102 
103  if (size <= 0) { foundInvalidDim = true; }
104  }
105 
106  if (foundInvalidDim)
107  {
108  continue;
109  }
110 
112  auto layoutPolicy = ioda::detail::DataLayoutPolicy::generate(policy);
113  auto obsGroup = ioda::ObsGroup::generate(rootGroup, newDims, layoutPolicy);
114 
115  auto scaleMap = std::map<std::string, ioda::Variable>();
116  for (const auto& scale : description_.getDims())
117  {
118  scaleMap.insert({scale.name, obsGroup.vars[scale.name]});
119  }
120 
121  // Create Globals
122  for (auto& global : description_.getGlobals())
123  {
124  global->addTo(rootGroup);
125  }
126 
127  // Create Variables
128  for (const auto& varDesc : description_.getVariables())
129  {
130  std::vector<ioda::Dimensions_t> chunks;
131  auto dimensions = std::vector<ioda::Variable>();
132  for (size_t dimIdx = 0; dimIdx < varDesc.dimensions.size(); dimIdx++)
133  {
134  auto dimVar = scaleMap.at(varDesc.dimensions[dimIdx]);
135  dimensions.push_back(dimVar);
136 
137  if (dimIdx < varDesc.chunks.size())
138  {
139  chunks.push_back(std::min(dimVar.getChunkSizes()[0],
140  varDesc.chunks[dimIdx]));
141  }
142  else
143  {
144  chunks.push_back(dimVar.getChunkSizes()[0]);
145  }
146  }
147 
148  auto data = dataContainer->get(varDesc.source, categories);
149  auto var = data->createVariable(obsGroup,
150  varDesc.name,
151  dimensions,
152  chunks,
153  varDesc.compressionLevel);
154 
155 
156 
157  var.atts.add<std::string>("long_name", { varDesc.longName }, {1});
158  var.atts.add<std::string>("units", { varDesc.units }, {1});
159 
160  if (varDesc.coordinates)
161  {
162  var.atts.add<std::string>("coordinates", { *varDesc.coordinates }, {1});
163  }
164 
165  if (varDesc.range)
166  {
167  var.atts.add<FloatType>("valid_range",
168  {varDesc.range->start, varDesc.range->end},
169  {2});
170  }
171  }
172 
173  obsGroups.insert({categories, obsGroup});
174  }
175 
176  return obsGroups;
177  }
178 
179  std::string IodaEncoder::makeStrWithSubstitions(const std::string& prototype,
180  const std::map<std::string, std::string>& subMap)
181  {
182  auto resultStr = prototype;
183  auto subIdxs = findSubIdxs(prototype);
184 
185  std::reverse(subIdxs.begin(), subIdxs.end());
186 
187  for (const auto& subs : subIdxs)
188  {
189  if (subMap.find(subs.first) != subMap.end())
190  {
191  auto repIdxs = subs.second;
192  resultStr = resultStr.replace(repIdxs.first,
193  repIdxs.second - repIdxs.first + 1,
194  subMap.at(subs.first));
195  }
196  else
197  {
198  std::ostringstream errStr;
199  errStr << "Can't find " << subs.first << ". No category with that name.";
200  throw eckit::BadParameter(errStr.str());
201  }
202  }
203 
204  return resultStr;
205  }
206 
207  std::vector<std::pair<std::string, std::pair<int, int>>>
208  IodaEncoder::findSubIdxs(const std::string& str)
209  {
210  std::vector<std::pair<std::string, std::pair<int, int>>> result;
211 
212  size_t startPos = 0;
213  size_t endPos = 0;
214 
215  while (startPos < str.size())
216  {
217  startPos = str.find("{", startPos+1);
218 
219  if (startPos < str.size())
220  {
221  endPos = str.find("}", startPos+1);
222 
223  if (endPos < str.size())
224  {
225  result.push_back({str.substr(startPos + 1, endPos - startPos - 1),
226  {startPos, endPos}});
227  startPos = endPos;
228  }
229  else
230  {
231  throw eckit::BadParameter("Unmatched { found in output filename.");
232  }
233  }
234  }
235 
236  return result;
237  }
238 
239  bool IodaEncoder::isInteger(const std::string& str) const
240  {
241  bool isInt = true;
242  if (str.empty())
243  {
244  isInt = false;
245  }
246  else
247  {
248  for (auto it = str.begin(); it != str.end(); it++)
249  {
250  if (!std::isdigit(*it))
251  {
252  isInt = false;
253  break;
254  }
255  }
256  }
257 
258  return isInt;
259  }
260 } // namespace Ingester
Contains definitions for how data are arranged in ioda internally.
Describes how to write data to IODA.
ioda::Engines::BackendNames getBackend() const
VariableDescriptions getVariables() const
GlobalDescriptions getGlobals() const
std::string getFilepath() const
DimDescriptions getDims() const
IodaEncoder(const eckit::Configuration &conf)
Definition: IodaEncoder.cpp:21
std::map< SubCategory, ioda::ObsGroup > encode(const std::shared_ptr< DataContainer > &data, bool append=false)
Encode the data into an ioda::ObsGroup object.
Definition: IodaEncoder.cpp:32
std::string makeStrWithSubstitions(const std::string &prototype, const std::map< std::string, std::string > &subMap)
Create a string from a template string.
const IodaDescription description_
The description.
Definition: IodaEncoder.h:38
std::vector< std::pair< std::string, std::pair< int, int > > > findSubIdxs(const std::string &str)
Used to find indecies of { and } by the makeStrWithSubstitions method.
bool isInteger(const std::string &str) const
static ObsGroup generate(Group &emptyGroup, const NewDimensionScales_t &fundamentalDims, std::shared_ptr< const detail::DataLayoutPolicy > layout=nullptr)
Create an empty ObsGroup and populate it with the fundamental dimensions.
Definition: ObsGroup.cpp:72
static std::shared_ptr< const DataLayoutPolicy > generate(const std::string &polid="")
Factory generator.
Definition: Layout.cpp:28
IODA_DL Group constructBackend(BackendNames name, BackendCreationParameters &params)
This is a simple factory style function that will instantiate a different backend based on a given na...
Definition: Factory.cpp:124
@ Open
Open an existing file.
@ Hdf5File
HDF5 file access.
@ Truncate_If_Exists
If the file already exists, overwrite it.
@ Read_Write
Open the file in read-write mode.
list newDims
Definition: 05-ObsGroup.py:95
float FloatType
Definition: IngesterTypes.h:18
std::vector< std::shared_ptr< NewDimensionScale_Base > > NewDimensionScales_t
Used to specify backend creation-time properties.
Definition: Factory.h:59