/***********************************************************************************/ /* Copyright 2014 WSL Institute for Snow and Avalanche Research SLF-DAVOS */ /***********************************************************************************/ /* This file is part of MeteoIO. MeteoIO is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. MeteoIO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with MeteoIO. If not, see . */ #include #include #include #include #include #include #include #include using namespace std; using namespace mio; // for the IOExceptions and IOUtils namespace ncpp { // // NetCDF C Library wrappers // void open_file(const std::string& filename, const int& omode, int& ncid) { const int status = nc_open(filename.c_str(), omode, &ncid); if (status != NC_NOERR) throw IOException("Could not open netcdf file '" + filename + "': " + nc_strerror(status), AT); } void create_file(const std::string& filename, const int& cmode, int& ncid) { const int status = nc_create(filename.c_str(), cmode, &ncid); if (status != NC_NOERR) throw IOException("Could not create netcdf file '" + filename + "': " + nc_strerror(status), AT); } void get_variable(const int& ncid, const std::string& varname, int& varid) { const int status = nc_inq_varid(ncid, varname.c_str(), &varid); if (status != NC_NOERR) throw IOException("Could not retrieve varid for variable '" + varname + "': " + nc_strerror(status), AT); } void get_dimension(const int& ncid, const std::string& dimname, int& dimid) { const int status = nc_inq_dimid(ncid, dimname.c_str(), &dimid); if (status != NC_NOERR) throw IOException("Could not retrieve dimid for dimension '" + dimname + "': " + nc_strerror(status), AT); } void get_dimension(const int& ncid, const std::string& dimname, int& dimid, size_t& dimlen) { get_dimension(ncid, dimname, dimid); const int status = nc_inq_dimlen(ncid, dimid, &dimlen); if (status != NC_NOERR) throw IOException("Could not retrieve length for dimension '" + dimname + "': " + nc_strerror(status), AT); } void get_DimAttribute(const int& ncid, const std::string& dimname, const std::string& attr_name, std::string& attr_value) { int dimid; get_dimension(ncid, dimname, dimid); size_t attr_len; int status = nc_inq_attlen (ncid, dimid, attr_name.c_str(), &attr_len); if (status != NC_NOERR) throw IOException("Could not retrieve attribute '" + attr_name + "' for var '" + dimname + "': " + nc_strerror(status), AT); char* value = new char[attr_len + 1]; // +1 for trailing null status = nc_get_att_text(ncid, dimid, attr_name.c_str(), value); if (status != NC_NOERR) throw IOException("Could not read attribute '" + attr_name + "' for var '" + dimname + "': " + nc_strerror(status), AT); value[attr_len] = '\0'; attr_value = string(value); delete[] value; } void get_VarAttribute(const int& ncid, const std::string& varname, const std::string& attr_name, std::string& attr_value) { int varid; get_variable(ncid, varname, varid); size_t attr_len; int status = nc_inq_attlen (ncid, varid, attr_name.c_str(), &attr_len); if (status != NC_NOERR) throw IOException("Could not retrieve attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); char* value = new char[attr_len + 1]; // +1 for trailing null status = nc_get_att_text(ncid, varid, attr_name.c_str(), value); if (status != NC_NOERR) throw IOException("Could not read attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); value[attr_len] = '\0'; attr_value = string(value); delete[] value; } void get_attribute(const int& ncid, const std::string& varname, const int& varid, const std::string& attr_name, std::string& attr_value) { size_t attr_len; int status = nc_inq_attlen (ncid, varid, attr_name.c_str(), &attr_len); if (status != NC_NOERR) throw IOException("Could not retrieve attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); char* value = new char[attr_len + 1]; // +1 for trailing null status = nc_get_att_text(ncid, varid, attr_name.c_str(), value); if (status != NC_NOERR) throw IOException("Could not read attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); value[attr_len] = '\0'; attr_value = string(value); delete[] value; } void get_attribute(const int& ncid, const std::string& varname, const int& varid, const std::string& attr_name, double& attr_value) { size_t attr_len; int status = nc_inq_attlen (ncid, varid, attr_name.c_str(), &attr_len); if (status != NC_NOERR) throw IOException("Could not retrieve attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); status = nc_get_att_double(ncid, varid, attr_name.c_str(), &attr_value); if (status != NC_NOERR) throw IOException("Could not read attribute '" + attr_name + "' for var '" + varname + "': " + nc_strerror(status), AT); } bool check_attribute(const int& ncid, const int& varid, const std::string& attr_name) { size_t attr_len; const int status = nc_inq_attlen (ncid, varid, attr_name.c_str(), &attr_len); if (status != NC_NOERR) return false; return true; } bool check_variable(const int& ncid, const std::string& varname) { int varid; const int status = nc_inq_varid(ncid, varname.c_str(), &varid); if (status != NC_NOERR) return false; return true; } bool check_dim_var(const int& ncid, const std::string& dimname) { int dimid; const int status = nc_inq_dimid(ncid, dimname.c_str(), &dimid); if (status != NC_NOERR) return false; return check_variable(ncid, dimname); } // Retrieve all variables with a certain set of dimensions void get_variables(const int& ncid, const std::vector& dimensions, std::vector& variables) { int nr_of_variables = -1; int status = nc_inq_nvars(ncid, &nr_of_variables); if (status != NC_NOERR) throw IOException("Could not retrieve variables for dataset: " + string(nc_strerror(status)), AT); // Variable IDs in a NetCDF file are consecutive integers starting with 0 for (int ii=0; ii& names) { int dimids[NC_MAX_VAR_DIMS], ndimsp; const int status = nc_inq_var(ncid, varid, NULL, NULL, &ndimsp, dimids, NULL); if (status != NC_NOERR) throw IOException("Could not retrieve dimensions for variable '" + varname + "': " + nc_strerror(status), AT); if ((int)names.size() != ndimsp) return false; for (int ii=0; ii& dimid, std::vector& dim_varid, std::vector& dimname, std::vector& dimlen) { dimid.clear(); dim_varid.clear(); dimname.clear(); dimlen.clear(); int dimids[NC_MAX_VAR_DIMS], ndimsp; int status = nc_inq_var(ncid, varid, NULL, NULL, &ndimsp, dimids, NULL); if (status != NC_NOERR) throw IOException("Could not retrieve dimensions for variable '" + varname + "': " + nc_strerror(status), AT); for (int ii=0; ii 0) { double last_value = IOUtils::nodata; read_value(ncid, varname, varid, dimlen-1, last_value); if (last_value == data) return (dimlen - 1); //The timestamp already exists if (last_value > data) { const size_t pos = find_record(ncid, varname, dimid, data); // Search for a possible match if (pos != IOUtils::npos) { return pos; } else { throw IOException("The variable '" + varname + "' has to be linearly increasing", AT); } } } write_record(ncid, varname, varid, dimlen, 1, &data); return dimlen; } bool get_dimensionMinMax(const int& ncid, const std::string& varname, double &min, double &max) { int dimid; size_t dimlen; get_dimension(ncid, varname, dimid, dimlen); //check if record already exists if (dimlen > 0) { double *record_value = new double[dimlen]; read_data(ncid, varname, dimid, record_value); min = record_value[0]; max = record_value[dimlen-1]; delete[] record_value; } else return false; // data not found return true; } bool get_recordMinMax(const int& ncid, const std::string& varname, const int& varid, double &min, double &max) { int dimid; size_t dimlen; get_dimension(ncid, varname, dimid, dimlen); //check if record already exists if (dimlen > 0) { double *record_value = new double[dimlen]; read_data(ncid, varname, varid, record_value); min = record_value[0]; max = record_value[dimlen-1]; delete[] record_value; } else return false; // data not found return true; } // Finding a certain record variable value (e.g. timestamp) by retrieving all // record values and then performing a linear search size_t find_record(const int& ncid, const std::string& varname, const double& data) { int dimid; size_t dimlen; get_dimension(ncid, varname, dimid, dimlen); //check if record already exists if (dimlen > 0) { double *record_value = new double[dimlen]; read_data(ncid, varname, dimid, record_value); for (size_t ii=0; ii 0) { double *record_value = new double[dimlen]; read_data(ncid, varname, varid, record_value); for (size_t ii=0; ii dimids; dimids.push_back(dimid1); dimids.push_back(dimid2); const int status = nc_def_var(ncid, varname.c_str(), xtype, 2, &dimids[0], &varid); if (status != NC_NOERR) throw IOException("Could not define variable '" + varname + "': " + nc_strerror(status), AT); } void add_3D_variable(const int& ncid, const std::string& varname, const nc_type& xtype, const int& dimid_record, const int& dimid1, const int& dimid2, int& varid) { vector dimids; dimids.push_back(dimid_record); // has to be the first one, the slowest changing index dimids.push_back(dimid1); dimids.push_back(dimid2); const int status = nc_def_var(ncid, varname.c_str(), xtype, 3, &dimids[0], &varid); if (status != NC_NOERR) throw IOException("Could not define variable '" + varname + "': " + nc_strerror(status), AT); } void start_definitions(const std::string& filename, const int& ncid) { const int status = nc_redef(ncid); if (status != NC_NOERR) throw IOException("Could not open define mode for file '" + filename + "': " + nc_strerror(status), AT); } void end_definitions(const std::string& filename, const int& ncid) { const int status = nc_enddef(ncid); if (status != NC_NOERR) throw IOException("Could not close define mode for file '" + filename + "': " + nc_strerror(status), AT); } void close_file(const std::string& filename, const int& ncid) { const int status = nc_close(ncid); if (status != NC_NOERR) throw IOException("Could not close netcdf file '" + filename + "': " + nc_strerror(status), AT); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Wrappers to MeteoIO's data classes void copy_grid(const std::string& coordin, const std::string& coordinparam, const size_t& latlen, const size_t& lonlen, const double * const lat, const double * const lon, const double * const grid, const double& nodata, mio::Grid2DObject& grid_out) { double resampling_factor_x = IOUtils::nodata, resampling_factor_y=IOUtils::nodata; const double cellsize = calculate_cellsize(latlen, lonlen, lat, lon, resampling_factor_x, resampling_factor_y); const double cntr_lat = .5*(lat[0]+lat[latlen-1]); const double cntr_lon = .5*(lon[0]+lon[lonlen-1]); mio::Coords cntr(coordin, coordinparam); cntr.setLatLon(cntr_lat, cntr_lon, IOUtils::nodata); //it will be moved to llcorner later, after correcting the aspect ratio grid_out.set(lonlen, latlen, cellsize, cntr); //Handle the case of llcorner/urcorner swapped if (lat[0]<=lat[latlen-1]) { for (size_t kk=0; kk < latlen; kk++) { const size_t row = kk*lonlen; if (lon[0]<=lon[lonlen-1]) { for (size_t ll=0; ll < lonlen; ll++) grid_out(ll, kk) = mio::IOUtils::standardizeNodata(grid[row + ll], nodata); } else { for (size_t ll=0; ll < lonlen; ll++) grid_out(ll, kk) = mio::IOUtils::standardizeNodata(grid[row + (lonlen -1) - ll], nodata); } } } else { for (size_t kk=0; kk < latlen; kk++) { const size_t row = ((latlen-1) - kk)*lonlen; if (lon[0]<=lon[lonlen-1]) { for (size_t ll=0; ll < lonlen; ll++) grid_out(ll, kk) = mio::IOUtils::standardizeNodata(grid[row + ll], nodata); } else { for (size_t ll=0; ll < lonlen; ll++) grid_out(ll, kk) = mio::IOUtils::standardizeNodata(grid[row + (lonlen -1) - ll], nodata); } } } if (resampling_factor_x != mio::IOUtils::nodata) { grid_out.grid2D = mio::ResamplingAlgorithms2D::BilinearResampling(grid_out.grid2D, resampling_factor_x, resampling_factor_y); } //computing lower left corner by using the center point as reference, AFTER we corrected for the aspect ratio grid_out.llcorner.moveByXY(-.5*(double)grid_out.getNx()*cellsize, -.5*(double)grid_out.getNy()*cellsize); } /* The Grid2DObject holds data and meta data for quadratic cells. However the NetCDF file * stores the grid as discrete latitude and longitude values. It is necessary to calculate * the distance between the edges of the grid and determine the cellsize. This cellsize may * be different for X and Y directions. We then choose one cellsize for our grid and * determine a factor that will be used for resampling the grid to likewise consist of * quadratic cells. */ double calculate_cellsize(const size_t& latlen, const size_t& lonlen, const double * const lat, const double * const lon, double& factor_x, double& factor_y) { const double cntr_lat = .5*(lat[0]+lat[latlen-1]); const double cntr_lon = .5*(lon[0]+lon[lonlen-1]); double alpha; const double distanceX = mio::CoordsAlgorithms::VincentyDistance(cntr_lat, lon[0], cntr_lat, lon[lonlen-1], alpha); const double distanceY = mio::CoordsAlgorithms::VincentyDistance(lat[0], cntr_lon, lat[latlen-1], cntr_lon, alpha); // lonlen, latlen are decremented by 1; n linearly connected points have (n-1) connections const double cellsize_x = distanceX / static_cast(lonlen-1); const double cellsize_y = distanceY / static_cast(latlen-1); // round to 1cm precision for numerical stability const double cellsize = static_cast(Optim::round( std::min(cellsize_x, cellsize_y)*100. )) / 100.; if (cellsize_x == cellsize_y) { return cellsize_x; } else { factor_x = cellsize_x / cellsize; factor_y = cellsize_y / cellsize; return cellsize; } } /* Fill the arrays of lat/lon with the lat/lon intervals * as calculated from the cellsize of the grid object. */ void calculate_dimensions(const mio::Grid2DObject& grid, double*& lat_array, double*& lon_array) { lat_array[0] = grid.llcorner.getLat(); lon_array[0] = grid.llcorner.getLon(); // The idea is to use the difference in coordinates of the upper right and the lower left // corner to calculate the lat/lon intervals between cells Coords urcorner(grid.llcorner); urcorner.setGridIndex(static_cast(grid.getNx() - 1), static_cast(grid.getNy() - 1), IOUtils::nodata, true); if (grid.gridify(urcorner) ) throw IndexOutOfBoundsException("URcorner not within the grid... This should never happen!", AT); const double lat_interval = (urcorner.getLat() - lat_array[0]) / static_cast(grid.getNy()-1); const double lon_interval = (urcorner.getLon() - lon_array[0]) / static_cast(grid.getNx()-1); // The method to use interval*ii is consistent with the corresponding // calculation of the Grid2DObject::gridify method -> numerical stability for (size_t ii=1; ii(ii); } for (size_t ii=1; ii(ii); } } // Fill a NetCDF 2D array with the data from a Grid2DObject void fill_grid_data(const mio::Grid2DObject& grid, double*& data) { const size_t nrows = grid.getNy(), ncols = grid.getNx(); for (size_t kk=0; kk( Optim::round(val) ); else data[kk*ncols + ll] = static_cast( new_nodata ); } } } } //end namespace