1 /*
2  MDAL - Mesh Data Abstraction Library (MIT License)
3  Copyright (C) 2019 Peter Petrik (zilolv at gmail dot com)
4 */
5 
6 #include <string>
7 #include <vector>
8 #include <assert.h>
9 #include <netcdf.h>
10 #include <cmath>
11 
12 #include "mdal_netcdf.hpp"
13 #include "mdal.h"
14 #include "mdal_utils.hpp"
15 #include "mdal_logger.hpp"
16 
NetCDFFile()17 NetCDFFile::NetCDFFile(): mNcid( 0 ) {}
18 
~NetCDFFile()19 NetCDFFile::~NetCDFFile()
20 {
21   if ( mNcid != 0 )
22   {
23     nc_close( mNcid );
24     mNcid = 0;
25   }
26 }
27 
handle() const28 int NetCDFFile::handle() const
29 {
30   return mNcid;
31 }
32 
openFile(const std::string & fileName)33 void NetCDFFile::openFile( const std::string &fileName )
34 {
35   int res = nc_open( fileName.c_str(), NC_NOWRITE, &mNcid );
36   if ( res != NC_NOERR )
37   {
38     throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not open file " + fileName );
39   }
40   mFileName = fileName;
41 }
42 
readIntArr(const std::string & name,size_t dim) const43 std::vector<int> NetCDFFile::readIntArr( const std::string &name, size_t dim ) const
44 {
45   assert( mNcid != 0 );
46   int arr_id;
47   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Internal error in Netcfd - unknown format" );
48   std::vector<int> arr_val( dim );
49   if ( nc_get_var_int( mNcid, arr_id, arr_val.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Internal error in Netcfd - unknown format" );
50   return arr_val;
51 }
52 
readIntArr(int arr_id,size_t start_dim1,size_t start_dim2,size_t count_dim1,size_t count_dim2) const53 std::vector<int> NetCDFFile::readIntArr( int arr_id, size_t start_dim1, size_t start_dim2, size_t count_dim1, size_t count_dim2 ) const
54 {
55   assert( mNcid != 0 );
56 
57   const std::vector<size_t> startp = {start_dim1, start_dim2};
58   const std::vector<size_t> countp = {count_dim1, count_dim2};
59   const std::vector<ptrdiff_t> stridep = {1, 1};
60 
61   std::vector<int> arr_val( count_dim1 * count_dim2 );
62   int res = nc_get_vars_int( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val.data() );
63   if ( res != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read numeric array" );
64   return arr_val;
65 }
66 
readIntArr(int arr_id,size_t start_dim,size_t count_dim) const67 std::vector<int> NetCDFFile::readIntArr( int arr_id, size_t start_dim, size_t count_dim ) const
68 {
69   assert( mNcid != 0 );
70 
71   const std::vector<size_t> startp = {start_dim};
72   const std::vector<size_t> countp = {count_dim};
73   const std::vector<ptrdiff_t> stridep = {1};
74 
75   std::vector<int> arr_val( count_dim );
76   int res = nc_get_vars_int( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val.data() );
77   if ( res != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read numeric array" );
78   return arr_val;
79 }
80 
readDoubleArr(const std::string & name,size_t dim) const81 std::vector<double> NetCDFFile::readDoubleArr( const std::string &name, size_t dim ) const
82 {
83   assert( mNcid != 0 );
84 
85   int arr_id;
86   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
87   std::vector<double> arr_val( dim );
88 
89   nc_type typep;
90   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
91   if ( nc_inq_vartype( mNcid, arr_id, &typep ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
92 
93   if ( typep == NC_FLOAT )
94   {
95     std::vector<float> arr_val_f( dim );
96     if ( nc_get_var_float( mNcid, arr_id, arr_val_f.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
97     for ( size_t i = 0; i < dim; ++i )
98     {
99       const float val = arr_val_f[i];
100       if ( std::isnan( val ) )
101         arr_val[i] = std::numeric_limits<double>::quiet_NaN();
102       else
103         arr_val[i] = static_cast<double>( val );
104     }
105   }
106   else if ( typep == NC_DOUBLE )
107   {
108     if ( nc_get_var_double( mNcid, arr_id, arr_val.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
109   }
110   else
111   {
112     throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
113   }
114   return arr_val;
115 }
116 
readDoubleArr(int arr_id,size_t start_dim1,size_t start_dim2,size_t count_dim1,size_t count_dim2) const117 std::vector<double> NetCDFFile::readDoubleArr( int arr_id,
118     size_t start_dim1, size_t start_dim2,
119     size_t count_dim1, size_t count_dim2 ) const
120 {
121   assert( mNcid != 0 );
122 
123   const std::vector<size_t> startp = {start_dim1, start_dim2};
124   const std::vector<size_t> countp = {count_dim1, count_dim2};
125   const std::vector<ptrdiff_t> stridep = {1, 1};
126 
127   std::vector<double> arr_val( count_dim1 * count_dim2 );
128 
129   nc_type typep;
130   if ( nc_inq_vartype( mNcid, arr_id, &typep ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
131 
132   if ( typep == NC_FLOAT )
133   {
134     std::vector<float> arr_val_f( count_dim1 * count_dim2 );
135     if ( nc_get_vars_float( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val_f.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
136     for ( size_t i = 0; i < count_dim1 * count_dim2; ++i )
137     {
138       const float val = arr_val_f[i];
139       if ( std::isnan( val ) )
140         arr_val[i] = std::numeric_limits<double>::quiet_NaN();
141       else
142         arr_val[i] = static_cast<double>( val );
143     }
144   }
145   else if ( typep == NC_BYTE )
146   {
147     std::vector<unsigned char> arr_val_b( count_dim1 * count_dim2 );
148     if ( nc_get_vars_uchar( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val_b.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
149     for ( size_t i = 0; i < count_dim1 * count_dim2; ++i )
150     {
151       const unsigned char val = arr_val_b[i];
152       if ( val == 129 )
153         arr_val[i] = std::numeric_limits<double>::quiet_NaN();
154       else
155         arr_val[i] = double( int( val ) );
156     }
157   }
158   else if ( typep == NC_DOUBLE )
159   {
160     if ( nc_get_vars_double( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
161   }
162   else
163   {
164     throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
165   }
166   return arr_val;
167 }
168 
readDoubleArr(int arr_id,size_t start_dim,size_t count_dim) const169 std::vector<double> NetCDFFile::readDoubleArr( int arr_id,
170     size_t start_dim,
171     size_t count_dim
172                                              ) const
173 {
174   assert( mNcid != 0 );
175 
176   const std::vector<size_t> startp = {start_dim};
177   const std::vector<size_t> countp = {count_dim};
178   const std::vector<ptrdiff_t> stridep = {1, 1};
179 
180   std::vector<double> arr_val( count_dim );
181 
182   nc_type typep;
183   if ( nc_inq_vartype( mNcid, arr_id, &typep ) != NC_NOERR )
184     throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
185 
186   if ( typep == NC_FLOAT )
187   {
188     std::vector<float> arr_val_f( count_dim );
189     if ( nc_get_vars_float( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val_f.data() ) != NC_NOERR )
190       throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
191     for ( size_t i = 0; i < count_dim; ++i )
192     {
193       const float val = arr_val_f[i];
194       if ( std::isnan( val ) )
195         arr_val[i] = std::numeric_limits<double>::quiet_NaN();
196       else
197         arr_val[i] = static_cast<double>( val );
198     }
199   }
200   else if ( typep == NC_INT )
201   {
202     std::vector<int> arr_val_int( count_dim );
203     if ( nc_get_vars_int( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val_int.data() ) != NC_NOERR )
204       throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
205     for ( size_t i = 0; i < count_dim; ++i )
206     {
207       arr_val[i] = static_cast<double>( arr_val_int[i] );
208     }
209   }
210   else if ( typep == NC_DOUBLE )
211   {
212     if ( nc_get_vars_double( mNcid, arr_id, startp.data(), countp.data(), stridep.data(), arr_val.data() ) != NC_NOERR )
213       throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
214   }
215   else
216   {
217     throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read double array" );
218   }
219   return arr_val;
220 }
221 
hasArr(const std::string & name) const222 bool NetCDFFile::hasArr( const std::string &name ) const
223 {
224   assert( mNcid != 0 );
225   int arr_id;
226   return nc_inq_varid( mNcid, name.c_str(), &arr_id ) == NC_NOERR;
227 }
228 
arrId(const std::string & name) const229 int NetCDFFile::arrId( const std::string &name ) const
230 {
231   int arr_id = -1;
232   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR )
233   {
234     arr_id = -1;
235   }
236   return arr_id;
237 }
238 
readArrNames() const239 std::vector<std::string> NetCDFFile::readArrNames() const
240 {
241   assert( mNcid != 0 );
242 
243   std::vector<std::string> res;
244   int nvars;
245   if ( nc_inq_varids( mNcid, &nvars, nullptr ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read variable names" );
246 
247   std::vector<int> varids( static_cast<size_t>( nvars ) );
248   if ( nc_inq_varids( mNcid, &nvars, varids.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read variable names" );
249 
250   for ( size_t i = 0; i < static_cast<size_t>( nvars ); ++i )
251   {
252     std::vector<char> cname( NC_MAX_NAME + 1 );
253     if ( nc_inq_varname( mNcid, varids[i], cname.data() ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not read variable names" );
254     res.push_back( cname.data() );
255   }
256 
257   return res;
258 }
259 
hasAttrInt(const std::string & name,const std::string & attr_name) const260 bool NetCDFFile::hasAttrInt( const std::string &name, const std::string &attr_name ) const
261 {
262   assert( mNcid != 0 );
263 
264   int arr_id;
265   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR ) return false;
266 
267   int val;
268   if ( nc_get_att_int( mNcid, arr_id, attr_name.c_str(), &val ) ) return false;
269 
270   return true;
271 }
272 
getAttrInt(const std::string & name,const std::string & attr_name) const273 int NetCDFFile::getAttrInt( const std::string &name, const std::string &attr_name ) const
274 {
275   assert( mNcid != 0 );
276 
277   int arr_id;
278   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get numeric attribute" );
279 
280   int val;
281   if ( nc_get_att_int( mNcid, arr_id, attr_name.c_str(), &val ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get numeric attribute" );
282   return val;
283 }
284 
getAttrStr(const std::string & name,const std::string & attr_name) const285 std::string NetCDFFile::getAttrStr( const std::string &name, const std::string &attr_name ) const
286 {
287   assert( mNcid != 0 );
288 
289   int arr_id;
290   if ( nc_inq_varid( mNcid, name.c_str(), &arr_id ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get string attribute" );
291   return getAttrStr( attr_name, arr_id );
292 }
293 
getAttrStr(const std::string & attr_name,int varid) const294 std::string NetCDFFile::getAttrStr( const std::string &attr_name, int varid ) const
295 {
296   assert( mNcid != 0 );
297 
298   size_t attlen = 0;
299 
300   if ( nc_inq_attlen( mNcid, varid, attr_name.c_str(), &attlen ) )
301   {
302     // attribute is missing
303     return std::string();
304   }
305 
306   char *string_attr = static_cast<char *>( malloc( attlen + 1 ) );
307 
308   if ( nc_get_att_text( mNcid, varid, attr_name.c_str(), string_attr ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get string attribute" );
309   string_attr[attlen] = '\0';
310 
311   std::string res( string_attr );
312   free( string_attr );
313 
314   return res;
315 }
316 
getFillValue(int varid) const317 double NetCDFFile::getFillValue( int varid ) const
318 {
319   return getAttrDouble( varid, "_FillValue" );
320 }
321 
hasAttrDouble(int varid,const std::string & attr_name) const322 bool NetCDFFile::hasAttrDouble( int varid, const std::string &attr_name ) const
323 {
324   double res;
325   if ( nc_get_att_double( mNcid, varid, attr_name.c_str(), &res ) )
326     return false;
327   return true;
328 }
329 
getAttrDouble(int varid,const std::string & attr_name) const330 double NetCDFFile::getAttrDouble( int varid, const std::string &attr_name ) const
331 {
332   double res;
333   if ( nc_get_att_double( mNcid, varid, attr_name.c_str(), &res ) )
334     res = std::numeric_limits<double>::quiet_NaN(); // not present/set
335   return res;
336 }
337 
338 
getVarId(const std::string & name)339 int NetCDFFile::getVarId( const std::string &name )
340 {
341   int ncid_val;
342   if ( nc_inq_varid( mNcid, name.c_str(), &ncid_val ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get variable id" );
343   return ncid_val;
344 }
345 
getDimension(const std::string & name,size_t * val,int * ncid_val) const346 void NetCDFFile::getDimension( const std::string &name, size_t *val, int *ncid_val ) const
347 {
348   assert( mNcid != 0 );
349 
350   if ( nc_inq_dimid( mNcid, name.c_str(), ncid_val ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get dimension, invalid dimension ID or name" );
351   if ( nc_inq_dimlen( mNcid, *ncid_val, val ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get dimension, invalid dimension ID or name" );
352 }
353 
getDimensions(const std::string & variableName,std::vector<size_t> & dimensions,std::vector<int> & dimensionIds)354 void NetCDFFile::getDimensions( const std::string &variableName, std::vector<size_t> &dimensions, std::vector<int> &dimensionIds )
355 {
356   assert( mNcid != 0 );
357 
358   int n;
359   int varId;
360   if ( nc_inq_varid( mNcid, variableName.c_str(), &varId ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get dimensions" );
361   if ( nc_inq_varndims( mNcid, varId, &n ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get dimensions" );
362 
363   dimensionIds.resize( size_t( n ) );
364   dimensions.resize( size_t( n ) );
365 
366   if ( nc_inq_vardimid( mNcid, varId, dimensionIds.data() ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not get dimensions" );
367 
368   for ( int i = 0; i < n; ++i )
369   {
370     nc_inq_dimlen( mNcid, dimensionIds[size_t( i )], &dimensions[size_t( i )] );
371   }
372 }
373 
hasDimension(const std::string & name) const374 bool NetCDFFile::hasDimension( const std::string &name ) const
375 {
376   int ncid_val;
377   return nc_inq_dimid( mNcid, name.c_str(), &ncid_val ) == NC_NOERR;
378 }
379 
createFile(const std::string & fileName)380 void NetCDFFile::createFile( const std::string &fileName )
381 {
382   int res = nc_create( fileName.c_str(), NC_CLOBBER, &mNcid );
383   if ( res != NC_NOERR )
384   {
385     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
386   }
387 }
388 
defineDimension(const std::string & name,size_t size)389 int NetCDFFile::defineDimension( const std::string &name, size_t size )
390 {
391   int dimId = 0;
392   int res = nc_def_dim( mNcid, name.c_str(), size, &dimId );
393   if ( res != NC_NOERR )
394   {
395     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
396   }
397   return dimId;
398 }
399 
defineVar(const std::string & varName,int ncType,int dimensionCount,const int * dimensions)400 int NetCDFFile::defineVar( const std::string &varName,
401                            int ncType, int dimensionCount, const int *dimensions )
402 {
403   int varIdp;
404 
405   int res = nc_def_var( mNcid, varName.c_str(), ncType, dimensionCount, dimensions, &varIdp );
406   if ( res != NC_NOERR )
407   {
408     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
409   }
410 
411   return varIdp;
412 }
413 
putAttrStr(int varId,const std::string & attrName,const std::string & value)414 void NetCDFFile::putAttrStr( int varId, const std::string &attrName, const std::string &value )
415 {
416   int res = nc_put_att_text( mNcid, varId, attrName.c_str(), value.size(), value.c_str() );
417   if ( res != NC_NOERR )
418   {
419     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
420   }
421 }
422 
putAttrInt(int varId,const std::string & attrName,int value)423 void NetCDFFile::putAttrInt( int varId, const std::string &attrName, int value )
424 {
425   int res = nc_put_att_int( mNcid, varId, attrName.c_str(), NC_INT, 1, &value );
426   if ( res != NC_NOERR )
427   {
428     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
429   }
430 }
431 
putAttrDouble(int varId,const std::string & attrName,double value)432 void NetCDFFile::putAttrDouble( int varId, const std::string &attrName, double value )
433 {
434   int res = nc_put_att_double( mNcid, varId, attrName.c_str(), NC_DOUBLE, 1, &value );
435   if ( res != NC_NOERR )
436   {
437     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
438   }
439 }
440 
putDataDouble(int varId,const size_t index,const double value)441 void NetCDFFile::putDataDouble( int varId, const size_t index, const double value )
442 {
443   int res = nc_put_var1_double( mNcid, varId, &index, &value );
444   if ( res != NC_NOERR )
445   {
446     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
447   }
448 }
449 
putDataArrayInt(int varId,size_t line,size_t faceVerticesMax,int * values)450 void NetCDFFile::putDataArrayInt( int varId, size_t line, size_t faceVerticesMax, int *values )
451 {
452   // Configuration of these two vectors determines how is value array read and stored in the file
453   // https://www.unidata.ucar.edu/software/netcdf/docs/programming_notes.html#specify_hyperslabfileNameToSave
454   const size_t start[] = { line, 0 };
455   const size_t count[] = { 1, faceVerticesMax };
456 
457   int res = nc_put_vara_int( mNcid, varId, start, count, values );
458   if ( res != NC_NOERR )
459   {
460     throw MDAL::Error( MDAL_Status::Err_FailToWriteToDisk, nc_strerror( res ) );
461   }
462 }
463 
getFileName() const464 std::string NetCDFFile::getFileName() const
465 {
466   return mFileName;
467 }
468