/***********************************************************************************/ /* Copyright 2009 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 //for PI #include #include #include #include using namespace std; namespace mio { /** * @page arps ARPSIO * @section arps_format Format * This is for reading grid data in the ARPS grid format (it transparently supports both true ARPS ascii grids and * grids modified by the ARPSGRID utility). DEM reading works well while reading meteo parameters might be a rough ride * (since ARPS files do not always contain a consistent set of meteo fields). * * It is possible to manually print the list of available variables as well as extract a whole section, for a given variable * with the following one line scripts, in order to ease debugging: * @code * awk '/^ [a-z]/{print $0}' {arps_file} * awk '/^ [a-z]/{isVar=0} /^ radsw/{isVar=1;next} {if(isVar) print $0}' {arps_file} * @endcode * * @section arps_units Units * All units are assumed to be MKSA. * * @section arps_keywords Keywords * This plugin uses the following keywords: * - COORDSYS: coordinate system (see Coords); [Input] and [Output] section * - COORDPARAM: extra coordinates parameters (see Coords); [Input] and [Output] section * - DEMFILE: path and file containing the DEM; [Input] section * - ARPS_XCOORD: x coordinate of the lower left corner of the grids; [Input] section * - ARPS_YCOORD: y coordinate of the lower left corner of the grids; [Input] section * - GRID2DPATH: path to the input directory where to find the arps files to be read as grids; [Input] section * - GRID3DPATH: path to the input directory where to find the arps 3D files to be read as grids; [Input] section * - ARPS_EXT: arps file extension, or none for no file extension (default: .asc) */ const double ARPSIO::plugin_nodata = -999.; //plugin specific nodata value const char* ARPSIO::default_ext=".asc"; //filename extension ARPSIO::ARPSIO(const std::string& configfile) : cfg(configfile), coordin(), coordinparam(), coordout(), coordoutparam(), grid2dpath_in(), grid3dpath_in(), ext(default_ext), dimx(0), dimy(0), dimz(0), cellsize(0.), xcoord(IOUtils::nodata), ycoord(IOUtils::nodata), zcoord(), is_true_arps(true) { IOUtils::getProjectionParameters(cfg, coordin, coordinparam, coordout, coordoutparam); setOptions(); } ARPSIO::ARPSIO(const Config& cfgreader) : cfg(cfgreader), coordin(), coordinparam(), coordout(), coordoutparam(), grid2dpath_in(), grid3dpath_in(), ext(default_ext), dimx(0), dimy(0), dimz(0), cellsize(0.), xcoord(IOUtils::nodata), ycoord(IOUtils::nodata), zcoord(), is_true_arps(true) { IOUtils::getProjectionParameters(cfg, coordin, coordinparam, coordout, coordoutparam); setOptions(); } ARPSIO& ARPSIO::operator=(const ARPSIO& source) { if (this != &source) { coordin = source.coordin; coordinparam = source.coordinparam; coordout = source.coordout; coordoutparam = source.coordoutparam; grid2dpath_in = source. grid2dpath_in; grid3dpath_in = source.grid3dpath_in; ext = source.ext; dimx = source.dimx; dimy = source.dimy; dimz = source.dimz; cellsize = source.cellsize; xcoord = source.xcoord; ycoord = source.ycoord; zcoord = source.zcoord; is_true_arps = source.is_true_arps; } return *this; } void ARPSIO::setOptions() { const string grid_in = cfg.get("GRID2D", "Input", IOUtils::nothrow); if (grid_in == "ARPS") { //keep it synchronized with IOHandler.cc for plugin mapping!! cfg.getValue("GRID2DPATH", "Input", grid2dpath_in); } const string grid3d_in = cfg.get("GRID3D", "Input", IOUtils::nothrow); if (grid3d_in == "ARPS") { //keep it synchronized with IOHandler.cc for plugin mapping!! cfg.getValue("GRID3DPATH", "Input", grid3dpath_in); } cfg.getValue("ARPS_XCOORD", "Input", xcoord, IOUtils::nothrow); cfg.getValue("ARPS_YCOORD", "Input", ycoord, IOUtils::nothrow); //default value has been set in constructor cfg.getValue("ARPS_EXT", "Input", ext, IOUtils::nothrow); if (ext=="none") ext.clear(); } void ARPSIO::listFields(const std::string& i_name) { const size_t pos = i_name.find_last_of(":");//a specific parameter can be provided as {filename}:{parameter} const std::string filename = (pos!=IOUtils::npos)? grid2dpath_in +"/" + i_name.substr(0, pos) : grid2dpath_in +"/" + i_name; FILE *fin; openGridFile(fin, filename); const std::string dem_marker = (is_true_arps)? "zp coordinat" : "zp_coordinat"; moveToMarker(fin, filename, dem_marker); char dummy[ARPS_MAX_LINE_LENGTH]; int nb_elems = 0; do { nb_elems = fscanf(fin," %[^\t\n] ",dummy); //HACK: possible buffer overflow if (dummy[0]>='A' && dummy[0]<='z') { //white spaces have been skipped above std::string tmp( dummy ); IOUtils::trim(tmp); std::cout << "Found '" << tmp << "'\n"; } } while (!feof(fin) && nb_elems!=0); fclose(fin); } void ARPSIO::read2DGrid(Grid2DObject& grid_out, const std::string& i_name) { const size_t pos = i_name.find_last_of(":");//a specific parameter can be provided as {filename}:{parameter} const std::string filename = (pos!=IOUtils::npos)? grid2dpath_in +"/" + i_name.substr(0, pos) : grid2dpath_in +"/" + i_name; if (pos==IOUtils::npos) { //TODO: read by default the first data grid that is found? listFields(i_name); throw InvalidArgumentException("Please provide the parameter that has to be read!", AT); } const std::string param_str = (pos!=IOUtils::npos)? i_name.substr(pos+1) : ""; const size_t param = MeteoGrids::getParameterIndex( param_str ); if (param==IOUtils::npos) throw InvalidArgumentException("Invalid MeteoGrids Parameter requested: '"+param_str+"'", AT); FILE *fin; openGridFile(fin, filename); read2DGrid_internal(fin, filename, grid_out, static_cast(param)); if (grid_out.empty()) { ostringstream ss; ss << "No suitable data found for parameter " << MeteoGrids::getParameterName(param) << " "; ss << " in file \"" << filename << "\""; throw NoDataException(ss.str(), AT); } fclose(fin); } void ARPSIO::read2DGrid(Grid2DObject& grid_out, const MeteoGrids::Parameters& parameter, const Date& date) { std::string date_str = date.toString(Date::ISO); std::replace( date_str.begin(), date_str.end(), ':', '.'); const std::string filename = grid2dpath_in + "/" + date_str + ext; FILE *fin; openGridFile(fin, filename); read2DGrid_internal(fin, filename, grid_out, parameter); if (grid_out.empty()) { ostringstream ss; ss << "No suitable data found for parameter " << MeteoGrids::getParameterName(parameter) << " "; ss << "at time step " << date.toString(Date::ISO) << " in file \"" << filename << "\""; throw NoDataException(ss.str(), AT); } fclose(fin); } void ARPSIO::read2DGrid_internal(FILE* &fin, const std::string& filename, Grid2DObject& grid_out, const MeteoGrids::Parameters& parameter) { //extra variables: //kmh / kmv: horiz./vert. turbulent mixing coeff. m^2/s //tke: turbulent kinetic energy. (m/s)^2 //Radiation parameters if (parameter==MeteoGrids::ISWR) readGridLayer(fin, filename, "radsw", 2, grid_out); if (parameter==MeteoGrids::RSWR) { Grid2DObject net; readGridLayer(fin, filename, "radsw", 2, grid_out); readGridLayer(fin, filename, "radswnet", 2, net); grid_out.grid2D -= net.grid2D; } if (parameter==MeteoGrids::ILWR) readGridLayer(fin, filename, "radlwin", 2, grid_out); if (parameter==MeteoGrids::ALB) { Grid2DObject rswr, iswr; readGridLayer(fin, filename, "radsw", 2, iswr); readGridLayer(fin, filename, "radswnet", 2, grid_out); //net radiation rswr.grid2D = iswr.grid2D - grid_out.grid2D; grid_out.grid2D = iswr.grid2D/rswr.grid2D; } //Wind grids if (parameter==MeteoGrids::U) readGridLayer(fin, filename, "u", 2, grid_out); if (parameter==MeteoGrids::V) readGridLayer(fin, filename, "v", 2, grid_out); if (parameter==MeteoGrids::W) readGridLayer(fin, filename, "w", 2, grid_out); if (parameter==MeteoGrids::VW) { Grid2DObject V; readGridLayer(fin, filename, "u", 2, grid_out); //U readGridLayer(fin, filename, "v", 2, V); for (size_t jj=0; jj local temperature? }*/ //Hydrological parameters if (parameter==MeteoGrids::HS) readGridLayer(fin, filename, "snowdpth", 2, grid_out); if (parameter==MeteoGrids::PSUM) { readGridLayer(fin, filename, "prcrate1", 2, grid_out); //in kg/m^2/s grid_out.grid2D *= 3600.; //we need kg/m^2/h } //DEM const std::string dem_marker = (is_true_arps)? "zp coordinat" : "zp_coordinat"; if (parameter==MeteoGrids::DEM) readGridLayer(fin, filename, dem_marker, 2, grid_out); if (parameter==MeteoGrids::SLOPE) { DEMObject dem; dem.setUpdatePpt(DEMObject::SLOPE); readGridLayer(fin, filename, dem_marker, 2, dem); dem.update(); grid_out.set(dem.cellsize, dem.llcorner, dem.slope); } if (parameter==MeteoGrids::AZI) { DEMObject dem; dem.setUpdatePpt(DEMObject::SLOPE); readGridLayer(fin, filename, dem_marker, 2, dem); dem.update(); grid_out.set(dem.cellsize, dem.llcorner, dem.azi); } rewind(fin); } void ARPSIO::read3DGrid(Grid3DObject& grid_out, const std::string& i_name) { const size_t pos = i_name.find_last_of(":");//a specific parameter can be provided as {filename}:{parameter} const std::string filename = (pos!=std::string::npos)? grid3dpath_in +"/" + i_name.substr(0, pos) : grid3dpath_in +"/" + i_name; if (pos==std::string::npos) { //TODO: read by default the first data grid that is found? listFields(i_name); throw InvalidArgumentException("Please provide the parameter that has to be read!", AT); } std::string parameter = (pos!=std::string::npos)? i_name.substr(pos+1) : ""; if (parameter=="DEM") { //this is called damage control... this is so ugly... parameter = (is_true_arps)? "zp coordinat" : "zp_coordinat"; } FILE *fin; openGridFile(fin, filename); //resize the grid just in case Coords llcorner(coordin, coordinparam); llcorner.setXY(xcoord, ycoord, IOUtils::nodata); grid_out.set(dimx, dimy, dimz, cellsize, llcorner); grid_out.z = zcoord; // Read until the parameter is found moveToMarker(fin, filename, parameter); //read the data we are interested in for (size_t iz = 0; iz < dimz; iz++) { for (size_t iy = 0; iy < dimy; iy++) { for (size_t ix = 0; ix < dimx; ix++) { double tmp; if (fscanf(fin," %16lf%*[\n]",&tmp)==1) { grid_out.grid3D(ix,iy,iz) = tmp; } else { fclose(fin); throw InvalidFormatException("Failure in reading 3D grid in file "+filename, AT); } } } } fclose(fin); } void ARPSIO::readDEM(DEMObject& dem_out) { const std::string filename = cfg.get("DEMFILE", "Input"); FILE *fin; openGridFile(fin, filename); read2DGrid_internal(fin, filename, dem_out, MeteoGrids::DEM); fclose(fin); } void ARPSIO::initializeGRIDARPS(FILE* &fin, const std::string& filename) { //go to read the sizes moveToMarker(fin, filename, "nnx"); //finish reading the line and move to the next one if (fscanf(fin,"%*[^\n]")!=0) { fclose(fin); throw InvalidFormatException("Error in file format of file "+filename, AT); } if (fscanf(fin," %u %u %u \n",&dimx,&dimy,&dimz)!=3) { fclose(fin); throw InvalidFormatException("Can not read dimx, dimy, dimz from file "+filename, AT); } if (dimx==0 || dimy==0 || dimz==0) { fclose(fin); throw IndexOutOfBoundsException("Invalid dimx, dimy, dimz from file "+filename, AT); } //initializing cell size moveToMarker(fin, filename, "x_coordinate"); double v1, v2; if (fscanf(fin,"%lg %lg",&v1,&v2)!=2) { fclose(fin); throw InvalidFormatException("Can not read first two x coordinates from file "+filename, AT); } const double cellsize_x = v2 - v1; moveToMarker(fin, filename, "y_coordinate"); if (fscanf(fin,"%lg %lg",&v1,&v2)!=2) { fclose(fin); throw InvalidFormatException("Can not read first two y coordinates from file "+filename, AT); } const double cellsize_y = v2 - v1; if (cellsize_x!=cellsize_y) { fclose(fin); throw InvalidFormatException("Only square cells currently supported! Non compliance in file "+filename, AT); } cellsize = cellsize_y; //HACK: zcoords must be read from zp. But they are NOT constant... } void ARPSIO::initializeTrueARPS(FILE* &fin, const std::string& filename, const char curr_line[ARPS_MAX_LINE_LENGTH]) { //go to read the sizes if (sscanf(curr_line," nx = %u, ny = %u, nz = %u ",&dimx,&dimy,&dimz)!=3) { fclose(fin); throw InvalidFormatException("Can not read dimx, dimy, dimz from file "+filename, AT); } if (dimx==0 || dimy==0 || dimz==0) { fclose(fin); throw IndexOutOfBoundsException("Invalid dimx, dimy, dimz from file "+filename, AT); } //initializing cell size moveToMarker(fin, filename, "x coordinate"); double v1, v2; if (fscanf(fin,"%lg %lg",&v1,&v2)!=2) { fclose(fin); throw InvalidFormatException("Can not read first two x coordinates from file "+filename, AT); } const double cellsize_x = v2 - v1; moveToMarker(fin, filename, "y coordinate"); if (fscanf(fin,"%lg %lg",&v1,&v2)!=2) { fclose(fin); throw InvalidFormatException("Can not read first two y coordinates from file "+filename, AT); } const double cellsize_y = v2 - v1; if (cellsize_x!=cellsize_y) { fclose(fin); throw InvalidFormatException("Only square cells currently supported! Non compliance in file "+filename, AT); } cellsize = cellsize_y; moveToMarker(fin, filename, "z coordinate"); while (fscanf(fin,"%lg",&v1)==1) { zcoord.push_back( v1 ); } if (zcoord.size()!=dimz) { ostringstream ss; ss << "Expected " << dimz << " z coordinates in file \""+filename+"\", found " << zcoord.size(); fclose(fin); throw InvalidFormatException(ss.str(), AT); } } void ARPSIO::openGridFile(FILE* &fin, const std::string& filename) { if (!FileUtils::fileExists(filename)) throw AccessException(filename, AT); //prevent invalid filenames if ((fin=fopen(filename.c_str(),"r")) == NULL) { throw AccessException("Can not open file "+filename, AT); } //identify if the file is an original arps file or a file modified by ARPSGRID char dummy[ARPS_MAX_LINE_LENGTH]; for (unsigned char j=0; j<5; j++) { //the first easy difference in the structure happens at line 5 if (fgets(dummy,ARPS_MAX_STRING_LENGTH,fin)==NULL) { fclose(fin); throw InvalidFormatException("Fail to read header lines of file "+filename, AT); } } zcoord.clear(); //reset the zcoord unsigned int v1; if (sscanf(dummy," nx = %u, ny = ", &v1)<1) { //this is an ASCII file modified by ARPSGRID is_true_arps=false; initializeGRIDARPS(fin, filename); } else { //this is a true ARPS file initializeTrueARPS(fin, filename, dummy); } //set xcoord, ycoord to a default value if not set by the user if (xcoord==IOUtils::nodata) xcoord = -cellsize; if (ycoord==IOUtils::nodata) ycoord = -cellsize; //come back to the begining of the file rewind(fin); } /** @brief Skip the given number of layers from the ARPS file * Usually, the first layer for a given parameter is purely numeric and therefore should * be skipped. * @param fin file pointer * @param filename file name to use for error messages * @param layer Index of the layer to skip to (1 to dimz) */ void ARPSIO::skipToLayer(FILE* &fin, const std::string& filename, const unsigned int& layers) const { if (layers>1) { double tmp; const size_t jmax=dimx*dimy*(layers-1); for (size_t j = 0; j < jmax; j++) { if (fscanf(fin," %16lf%*[\n]",&tmp)==EOF) { fclose(fin); throw InvalidFormatException("Fail to skip data layers in file "+filename, AT); } } } } /** @brief Read a specific layer for a given parameter from the ARPS file * @param parameter The parameter to extract. This could be any of the following: * - x_coordinate for getting the X coordinates of the mesh * - y_coordinate for getting the Y coordinates of the mesh * - zp_coordinat for getting the Z coordinates of the mesh * - u for getting the u component of the wind field * - v for getting the v component of the wind field * - w for getting the w component of the wind field * @param layer Index of the layer to extract (1 to dimz) * @param grid [out] grid containing the values. The grid will be resized if necessary. */ void ARPSIO::readGridLayer(FILE* &fin, const std::string& filename, const std::string& parameter, const unsigned int& layer, Grid2DObject& grid) { if (layer<1 || layer>dimz) { fclose(fin); ostringstream tmp; tmp << "Layer " << layer << " does not exist in ARPS file " << filename << " (nr layers=" << dimz << ")"; throw IndexOutOfBoundsException(tmp.str(), AT); } //resize the grid just in case Coords llcorner(coordin, coordinparam); llcorner.setXY(xcoord, ycoord, IOUtils::nodata); grid.set(dimx, dimy, cellsize, llcorner); // Read until the parameter is found moveToMarker(fin, filename, parameter); // move to the begining of the layer of interest skipToLayer(fin, filename, layer); //read the data we are interested in for (size_t iy = 0; iy < dimy; iy++) { for (size_t ix = 0; ix < dimx; ix++) { double tmp; const int readPts = fscanf(fin," %16lf%*[\n]",&tmp); if (readPts==1) { grid(ix,iy) = tmp; } else { char dummy[ARPS_MAX_LINE_LENGTH]; const int status = fscanf(fin,"%s",dummy); fclose(fin); string msg = "Fail to read data layer for parameter '"+parameter+"' in file '"+filename+"'"; if (status>0) msg += ", instead read: '"+string(dummy)+"'"; throw InvalidFormatException(msg, AT); } } } } void ARPSIO::moveToMarker(FILE* &fin, const std::string& filename, const std::string& marker) { char dummy[ARPS_MAX_LINE_LENGTH]; do { const int nb_elems = fscanf(fin," %[^\t\n] ",dummy); //HACK: possible buffer overflow if (nb_elems==0) { fclose(fin); throw InvalidFormatException("Matching failure in file "+filename+" when looking for "+marker, AT); } if (dummy[0]>='A' && dummy[0]<='z') { //this is a "marker" line if (strncmp(dummy,marker.c_str(), marker.size()) == 0) return; } } while (!feof(fin)); fclose(fin); throw InvalidFormatException("End of file "+filename+" should NOT have been reached when looking for "+marker, AT); } } //namespace