From 3b1d5c3157963d5e2804a3083b81403d1a77099a Mon Sep 17 00:00:00 2001 From: Michael Uleysky Date: Sun, 30 Jul 2023 17:27:14 +1000 Subject: [PATCH] New NCFileW class to simplify writing netcdf files --- include/ncfilew.h | 201 ++++++++++++++++++++++++++++++++++++++++++++++ src/ncfilew.cpp | 103 ++++++++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 include/ncfilew.h create mode 100644 src/ncfilew.cpp diff --git a/include/ncfilew.h b/include/ncfilew.h new file mode 100644 index 0000000..08bc46e --- /dev/null +++ b/include/ncfilew.h @@ -0,0 +1,201 @@ +#pragma once +#include "MString.h" +#include "traits.h" +#include +#include +#include +#include + +using michlib::MString; + +class NCFileW +{ + enum Type + { + UNKNOWN, + G1V1, + G1V2, + G2V2 + }; + struct Var + { + MString name; + int id; + }; + + static constexpr auto fill = std::numeric_limits::max(); + + int ncid; + Type type = UNKNOWN; + union + { + struct + { + int xdimid, ydimid; + }; + int dimid[2]; + }; + int xid, yid; + int compress; + std::vector vars; + + template static constexpr Type DetType() + { + if constexpr(ReadIs2DGeoRectArray) return G1V2; + if constexpr(ReadIs2DGeoArray) return G2V2; + if constexpr(ReadIs1DGeoArray) return G1V1; + return UNKNOWN; + }; + + template static constexpr bool Is1DType() { return Is1DType(DetType()); } + + static constexpr bool Is1DType(Type t) { return t == G1V1; } + + MString CreateFile(Type stype, const MString& name, const MString& history, int compression, size_t nx, size_t ny); + + public: + static constexpr auto Fill() { return fill; } + + ~NCFileW() { Close(); } + + template MString Create(const D& data, const MString& name, const MString& history, int compression) + { + if(type != UNKNOWN) return "File already created"; + if constexpr(Is1DType()) + return CreateFile(DetType(), name, history, compression, data.N(), 0); + else + return CreateFile(DetType(), name, history, compression, data.Nx(), data.Ny()); + } + + size_t VarId(const MString& name) const + { + for(size_t i = 0; i < vars.size(); i++) + if(vars[i].name == name) return i + 1; + + return 0; + } + + bool HaveVar(const MString& name) const { return VarId(name) != 0; } + + void EndDef() const + { + if(type != UNKNOWN) nc_enddef(ncid); + } + void Close() + { + if(type != UNKNOWN) nc_close(ncid); + type = UNKNOWN; + } + + MString AddVariable(const MString& name, const MString& stname, const MString& lname, const MString& units); + + template MString WriteVariable(const D& data, size_t varid, Op op) const + { + static constexpr auto dtype = DetType(); + if(type == UNKNOWN) return "File not open"; + if(type != dtype) return "Incompatible data type"; + if(varid == 0) return "Incorrect variable"; + if constexpr(dtype == UNKNOWN) return "Unknown data type"; + + size_t v = varid - 1; + + EndDef(); + + int ret; + if constexpr(Is1DType(dtype)) + { + const size_t i = 0, c = data.N(); + float buf[c]; + for(size_t ix = 0; ix < c; ix++) buf[ix] = data.IsFill(ix) ? fill : op(ix); + + ret = nc_put_vara_float(ncid, vars[v].id, &i, &c, buf); + if(ret != NC_NOERR) return "Can't write " + vars[v].name + " variable in the netcdf file"; + } + else + { + const size_t i[2] = {0, 0}; + const size_t c[2] = {data.Nx(), data.Ny()}; + float buf[c[0] * c[1]]; + + for(size_t ix = 0; ix < c[0]; ix++) + for(size_t iy = 0; iy < c[1]; iy++) buf[ix * c[1] + iy] = data.IsFill(ix, iy) ? fill : op(ix, iy); + + ret = nc_put_vara_float(ncid, vars[v].id, i, c, buf); + if(ret != NC_NOERR) return "Can't write " + vars[v].name + " variable in the netcdf file"; + } + return ""; + } + + template MString WriteVariable(const D& data, const MString& name, Op op) const { return WriteVariable(data, VarId(name), op); } + + template MString WriteVariable(const D& data, size_t varid) const + { + if constexpr(Is1DType(DetType())) + return WriteVariable(data, varid, [&data = std::as_const(data)](size_t i) { return data(i); }); + else + return WriteVariable(data, varid, [&data = std::as_const(data)](size_t i, size_t j) { return data(i, j); }); + } + + template MString WriteVariable(const D& data, const MString& name) const { return WriteVariable(data, VarId(name)); } + + template MString WriteGrid(const D& data) const + { + static constexpr auto dtype = DetType(); + if(type == UNKNOWN) return "File not open"; + if(type != dtype) return "Incompatible data type"; + + EndDef(); + + int ret; + if constexpr(dtype == G1V1) + { + const size_t i = 0, c = data.N(); + float bufx[c]; + float bufy[c]; + for(size_t ix = 0; ix < c; ix++) + { + bufx[ix] = data.Lon(ix); + bufy[ix] = data.Lat(ix); + } + ret = nc_put_vara_float(ncid, xid, &i, &c, bufx); + if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file"; + ret = nc_put_vara_float(ncid, yid, &i, &c, bufy); + if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file"; + } + else if constexpr(dtype == G1V2) + { + const size_t i = 0, cx = data.Nx(), cy = data.Ny(); + float bufx[cx]; + float bufy[cy]; + + for(size_t ix = 0; ix < cx; ix++) bufx[ix] = data.Ix2Lon(ix); + for(size_t iy = 0; iy < cy; iy++) bufy[iy] = data.Iy2Lat(iy); + + ret = nc_put_vara_float(ncid, xid, &i, &cx, bufx); + if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file"; + ret = nc_put_vara_float(ncid, yid, &i, &cy, bufy); + if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file"; + } + else if constexpr(dtype == G2V2) + { + const size_t i[2] = {0, 0}; + const size_t c[2] = {data.Nx(), data.Ny()}; + float bufx[c[0] * c[1]]; + float bufy[c[0] * c[1]]; + for(size_t ix = 0; ix < c[0]; ix++) + for(size_t iy = 0; iy < c[1]; iy++) + { + bufx[ix * c[1] + iy] = data.Lon(ix, iy); + bufy[ix * c[1] + iy] = data.Lat(ix, iy); + } + ret = nc_put_vara_float(ncid, xid, i, c, bufx); + if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file"; + ret = nc_put_vara_float(ncid, yid, i, c, bufy); + if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file"; + } + else + return "Unknown data type"; + + return ""; + } +}; diff --git a/src/ncfilew.cpp b/src/ncfilew.cpp new file mode 100644 index 0000000..1d450e1 --- /dev/null +++ b/src/ncfilew.cpp @@ -0,0 +1,103 @@ +#define MICHLIB_NOSOURCE +#include "ncfilew.h" + +MString NCFileW::CreateFile(NCFileW::Type stype, const MString& name, const MString& history, int compression, size_t nx, size_t ny) +{ + if(stype == UNKNOWN) return "Can't determine file type"; + + compress = compression; + + int ret; + MString text; + + ret = nc_create(name.Buf(), NC_CLOBBER | NC_NETCDF4, &ncid); + if(ret != NC_NOERR) return "Can't create netcdf file: " + name; + + ret = nc_put_att_text(ncid, NC_GLOBAL, "history", history.Len() + 1, history.Buf()); + if(ret != NC_NOERR) return "Can't write history attribute in the netcdf file"; + + switch(stype) + { + case(G1V1): + { + ret = nc_def_dim(ncid, "i", nx, &xdimid); + if(ret != NC_NOERR) return "Can't create dimension in the netcdf file"; + ret = nc_def_var(ncid, "longitude", NC_FLOAT, 1, &xdimid, &xid); + if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file"; + ret = nc_def_var(ncid, "latitude", NC_FLOAT, 1, &xdimid, &yid); + if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file"; + break; + } + case(G1V2): + { + ret = nc_def_dim(ncid, "longitude", nx, &xdimid); + if(ret != NC_NOERR) return "Can't create x-dimension in the netcdf file"; + ret = nc_def_dim(ncid, "latitude", ny, &ydimid); + if(ret != NC_NOERR) return "Can't create y-dimension in the netcdf file"; + ret = nc_def_var(ncid, "longitude", NC_FLOAT, 1, &xdimid, &xid); + if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file"; + ret = nc_def_var(ncid, "latitude", NC_FLOAT, 1, &ydimid, &yid); + if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file"; + break; + } + case(G2V2): + { + ret = nc_def_dim(ncid, "longitude", nx, &xdimid); + if(ret != NC_NOERR) return "Can't create x-dimension in the netcdf file"; + ret = nc_def_dim(ncid, "latitude", ny, &ydimid); + if(ret != NC_NOERR) return "Can't create y-dimension in the netcdf file"; + ret = nc_def_var(ncid, "longitude", NC_FLOAT, 2, dimid, &xid); + if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file"; + ret = nc_def_var(ncid, "latitude", NC_FLOAT, 2, dimid, &yid); + if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file"; + break; + } + case(UNKNOWN): return "Can't determine file type"; + } + + ret = nc_def_var_deflate(ncid, xid, 1, 1, compress); + if(ret != NC_NOERR) return "Can't set deflate parameters for longitude variable in the netcdf file"; + ret = nc_def_var_deflate(ncid, yid, 1, 1, compress); + if(ret != NC_NOERR) return "Can't set deflate parameters for latitude variable in the netcdf file"; + + text = "longitude"; + ret = nc_put_att_text(ncid, xid, "standard_name", text.Len() + 1, text.Buf()); + if(ret != NC_NOERR) return "Can't write standard_name attribute of longitude variable in the netcdf file"; + text = "latitude"; + ret = nc_put_att_text(ncid, yid, "standard_name", text.Len() + 1, text.Buf()); + if(ret != NC_NOERR) return "Can't write standard_name attribute of latitude variable in the netcdf file"; + + type = stype; + return ""; +} + +MString NCFileW::AddVariable(const MString& name, const MString& stname, const MString& lname, const MString& units) +{ + if(type == UNKNOWN) return "File not opened"; + if(HaveVar(name)) return "Variable " + name + " already defined"; + + struct Var v(name, 0); + + int ret; + if(type == G1V1) + ret = nc_def_var(ncid, v.name.Buf(), NC_FLOAT, 1, &xdimid, &v.id); + else + ret = nc_def_var(ncid, v.name.Buf(), NC_FLOAT, 2, dimid, &v.id); + if(ret != NC_NOERR) return "Can't create " + v.name + " variable in the netcdf file"; + + ret = nc_def_var_deflate(ncid, v.id, 1, 1, compress); + if(ret != NC_NOERR) return "Can't set deflate parameters for " + v.name + " variable in the netcdf file"; + + if(stname.Exist()) ret = nc_put_att_text(ncid, v.id, "standard_name", stname.Len() + 1, stname.Buf()); + if(ret != NC_NOERR) return "Can't write standard_name attribute of " + v.name + " variable in the netcdf file"; + if(lname.Exist()) ret = nc_put_att_text(ncid, v.id, "long_name", lname.Len() + 1, lname.Buf()); + if(ret != NC_NOERR) return "Can't write long_name attribute of " + v.name + " variable in the netcdf file"; + if(units.Exist()) ret = nc_put_att_text(ncid, v.id, "units", units.Len() + 1, units.Buf()); + if(ret != NC_NOERR) return "Can't write units attribute of " + v.name + " variable in the netcdf file"; + + ret = nc_put_att_float(ncid, v.id, "_FillValue", NC_FLOAT, 1, &fill); + if(ret != NC_NOERR) return "Can't write _FillValue attribute of " + v.name + " variable in the netcdf file"; + + vars.push_back(v); + return ""; +}