/***********************************************************************************/
/* 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
using namespace std;
namespace mio {
/**
* @page oshd OshdIO
* This plugin reads the meteorological forecast data downscaled for each of the Swiss meteorological networks IMIS/ANETZ stations
* as preprocessed by the Operational Snow-Hydrological Service
* of the WSL. The data is written as Matlab
* binary files (.mat), one per meteorological parameter and per timestep,
* available on an access-controlled server after each new COSMO run. It therefore requires a third party
* library to read this file format: the Open Source MatIO library. This can be installed directly from
* the default repositories under Linux or installed by downloading the proper package for Windows or OsX.
*
* \note If non-ascii characters have been used and the file has been created under Windows, some of the strings might end up using the UTF-16 encoding.
* This requires a recent version of libmatio (see this issue). Another option would be to
* add at the begining of the Matlab routine a call to *feature('DefaultCharacterSet', 'UTF8')* in order to switch from the current default (which can be read by the same call,
* ommitting the 'UTF8' option) to the (partial) UTF-8 encoding of Matlab.
*
* @section oshd_data_structure Data structure
* The files are named with the following schema: {parameter}_{timestep}_{cosmo model version}_F_{run time}.mat with the following possible values:
* + *parameter* is one of idif, idir, albd, ilwr, pair, prec, rhum, tcor, wcor, wdir;
* + *timestep* is written as purely numeric ISO with minute resolution;
* + *cosmo model version* could be any of cosmo7, cosmo2, cosmo1, cosmoE;
* + *run time* is the purely numeric ISO date and time of when COSMO produced the dataset.
*
* The files have the following internal data structure (represented as "name {data type}"):
* @verbatim
stat {1x1 struct}
├── time {1x1 array of doubles}
├── data {1x623 array of doubles}
├── acro {1x623 array of arrays of char}
├── dunit {array of char}
├── type {array of char}
└── name {array of char}
@endverbatim
*
* The stations' acronyms follow a fixed order but their coordinates must be provided in a separate file, given as *METAFILE* key (see below). This file
* must have the following structure (the *x* and *y* coordinates being the CH1903 easting and northing, respectively):
* @verbatim
statlist {1x1 struct}
├── acro {1x623 array of arrays of char}
├── name {1x623 array of arrays of char}
├── x {1x623 array of doubles}
├── y {1x623 array of doubles}
└── z {1x623 array of doubles}
@endverbatim
*
*
* @section oshd_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
* - METEOPATH: directory containing all the data files with the proper file naming schema; [Input] section
* - METEOPATH_RECURSIVE: should *meteopath* be searched recursively for files? (default: no); [Input] section
* - STATION#: input stations' IDs (in METEOPATH). As many meteofiles as needed may be specified
* - METAFILE: file containing the stations' IDs, names and location; [Input] section (either within METEOPATH if not path is
provided or within the provided path)
* - OSHD_DEBUG: write out extra information to better show what is in the files
*
* @section oshd_example Example use
* @code
* [Input]
* METEO = OSHD
* METEOPATH = /local/LATEST_03h_RUN
* METEOPATH_RECURSIVE = true
* METAFILE = STAT_LIST.mat ;another possibility could be /local/metadata/STAT_LIST.mat
* STATION1 = ATT2
* STATION2 = WFJ2
* @endcode
*
*/
/**********************************************************************************
* Here we define some wrappers around libmatio. These are not declared as class methods in
* order to avoid having to expose matio.h when including OshdIO.h
**********************************************************************************/
void listFields(matvar_t *matvar)
{
const unsigned int nrFields = Mat_VarGetNumberOfFields(matvar);
char * const *fields = Mat_VarGetStructFieldnames(matvar);
for (unsigned int ii=0; iiname, matvar->class_type, matvar->data_type, matvar->rank);
for (int ii=0; iirank; ii++)
printf("\tdims[%d]=%d", ii, (int)matvar->dims[ii]);
printf("\n");
}
std::string readString(const std::string &filename, const std::string &fieldname, mat_t *matfp, matvar_t *matvar)
{
matvar_t *field = Mat_VarGetStructFieldByName(matvar, fieldname.c_str(), 0);
if (matvar==NULL) throw NotFoundException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (Mat_VarReadDataAll(matfp, field))
throw InvalidFormatException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (field->class_type!=MAT_C_CHAR) throw InvalidFormatException("field '"+fieldname+"' in file '"+filename+"' is not a type string", AT);
return std::string( static_cast(field->data) );
}
void readStringVector(const std::string &filename, const std::string &fieldname, mat_t *matfp, matvar_t *matvar, std::vector &vecString)
{
vecString.clear();
matvar_t *field = Mat_VarGetStructFieldByName(matvar, fieldname.c_str(), 0);
if (matvar==NULL) throw NotFoundException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (Mat_VarReadDataAll(matfp, field))
throw InvalidFormatException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (field->class_type!=MAT_C_CELL) throw InvalidFormatException("field '"+fieldname+"' in file '"+filename+"' is not a cell type", AT);
if (field->data_type!=MAT_T_CELL) throw InvalidFormatException("field '"+fieldname+"' in file '"+filename+"' is not a cell array data type", AT);
matvar_t *cell = Mat_VarGetCell(matvar, 0);
if (cell==NULL) throw InvalidFormatException("could not read data in field '"+fieldname+"' in file '"+filename+"'", AT);
if (field->rank!=2) throw InvalidFormatException("invalid rank for field '"+fieldname+"' in file '"+filename+"'", AT);
const size_t nrows = field->dims[0];
const size_t ncols = field->dims[1];
if (nrows!=1) throw InvalidFormatException("invalid nrows for field '"+fieldname+"' in file '"+filename+"'", AT);
vecString.resize( ncols );
for (size_t ii=0; ii(ii));
if (cell->rank!=2) throw InvalidFormatException("invalid cell rank in file '"+filename+"'", AT);
if (cell->class_type!=MAT_C_CHAR) throw InvalidFormatException("field '"+fieldname+"' in file '"+filename+"' is not a type string", AT);
vecString[ii] = static_cast(cell->data);
}
}
void readDoubleVector(const std::string &filename, const std::string &fieldname, mat_t *matfp, matvar_t *matvar, std::vector &vecData)
{
vecData.clear();
matvar_t *field = Mat_VarGetStructFieldByName(matvar, fieldname.c_str(), 0);
if (matvar==NULL) throw NotFoundException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (Mat_VarReadDataAll(matfp, field))
throw InvalidFormatException("could not read field '"+fieldname+"' in file '"+filename+"'", AT);
if (field->class_type!=MAT_C_DOUBLE) throw InvalidFormatException("field '"+fieldname+"' in file '"+filename+"' is not a double type", AT);
if (field->rank!=2) throw InvalidFormatException("invalid rank for field '"+fieldname+"' in file '"+filename+"'", AT);
const size_t nrows = field->dims[0];
const size_t ncols = field->dims[1];
if (nrows!=1) throw InvalidFormatException("invalid nrows for field '"+fieldname+"' in file '"+filename+"'", AT);
const double* matData( static_cast( field->data ) );
vecData.resize( ncols );
for (size_t ii=0; ii\n";
matvar_t *matvar;
while ( (matvar = Mat_VarReadNextInfo(matfp)) != NULL ) {
std::cout << "\t" << matvar->name << " [";
for (int ii=0; iirank; ii++) {
std::cout << (int)matvar->dims[ii];
if (ii<(matvar->rank-1)) std::cout << "x";
}
std::cout << "]\n";
const unsigned int nrFields = Mat_VarGetNumberOfFields(matvar);
char * const *fields = Mat_VarGetStructFieldnames(matvar);
for (unsigned int ii=0; iiclass_type==MAT_C_CHAR)
std::cout << " = \"" << readString(filename, field_name, matfp, matvar) << "\"";
if (field->class_type==MAT_C_DOUBLE) {
std::cout << " [";
size_t count=1;
for (int jj=0; jjrank; jj++) {
std::cout << field->dims[jj];
if (jj<(field->rank-1)) std::cout << "x";
count *= field->dims[jj];
}
std::cout << "]";
if (count==1) {
if (Mat_VarReadDataAll(matfp, field))
throw InvalidFormatException("could not read field '"+field_name+"' in file '"+filename+"'", AT);
const double val = static_cast(field->data)[0];
if (field_name=="time") {
Date timestep;
timestep.setMatlabDate( val, OshdIO::in_dflt_TZ );
std::cout << " = " << timestep.toString(Date::ISO_TZ);
} else
std::cout << " = " << val;
}
}
if (field->class_type==MAT_C_CELL) {
std::cout << " [";
for (int jj=0; jjrank; jj++) {
std::cout << field->dims[jj];
if (jj<(field->rank-1)) std::cout << "x";
}
std::cout << "]";
}
std::cout << "\n";
}
}
std::cout << "" << FileUtils::getFilename( filename ) << ">\n\n";
Mat_VarFree(matvar);
matvar = NULL;
Mat_Close(matfp);
}
/**********************************************************************************
* Now really implementing the OshdIO class
**********************************************************************************/
const char* OshdIO::meteo_ext = ".mat";
const double OshdIO::in_dflt_TZ = 0.; //COSMO data is always GMT
OshdIO::OshdIO(const std::string& configfile) : cfg(configfile), cache_meteo_files(), vecMeta(), vecIDs(), params_map(), vecIdx(),
in_meteopath(), in_metafile(), debug(false)
{
parseInputOutputSection();
}
OshdIO::OshdIO(const Config& cfgreader) : cfg(cfgreader), cache_meteo_files(), vecMeta(), vecIDs(), params_map(), vecIdx(),
in_meteopath(), in_metafile(), debug(false)
{
parseInputOutputSection();
}
void OshdIO::parseInputOutputSection()
{
cfg.getValue("OSHD_DEBUG", "INPUT", debug, IOUtils::nothrow);
//IOUtils::getProjectionParameters(cfg, coordin, coordinparam, coordout, coordoutparam);
cfg.getValues("STATION", "INPUT", vecIDs);
cfg.getValue("METEOPATH", "Input", in_meteopath);
bool is_recursive = false;
cfg.getValue("METEOPATH_RECURSIVE", "Input", is_recursive, IOUtils::nothrow);
scanMeteoPath(in_meteopath, is_recursive, cache_meteo_files);
cfg.getValue("METAFILE", "INPUT", in_metafile);
if (FileUtils::getFilename(in_metafile) == in_metafile) { //ie there is no path in the provided filename
in_metafile = in_meteopath + "/" + in_metafile;
}
//fill the params mapping vector
params_map.push_back( std::make_pair(MeteoData::ILWR, "ilwc") );
params_map.push_back( std::make_pair(MeteoData::P, "pair") );
params_map.push_back( std::make_pair(MeteoData::PSUM, "prec") ); //in mm/ts
params_map.push_back( std::make_pair(MeteoData::RH, "rcor") ); //old: rhum
params_map.push_back( std::make_pair(MeteoData::TA, "tcor") ); //old:tair
params_map.push_back( std::make_pair(MeteoData::VW, "wcor") ); //old: wind
params_map.push_back( std::make_pair(MeteoData::DW, "wdir") );
}
void OshdIO::scanMeteoPath(const std::string& meteopath_in, const bool& is_recursive, std::vector< struct file_index > &meteo_files)
{
meteo_files.clear();
std::list dirlist( FileUtils::readDirectory(meteopath_in, "prec", is_recursive) ); //we consider that if we have found one parameter, the others are also there
std::map mapIdx; //make sure each timestamp only appears once, ie remove duplicates
for (std::list::iterator it = dirlist.begin(); it != dirlist.end(); ++it) {
const std::string file_and_path( *it );
const std::string filename( FileUtils::getFilename(file_and_path) );
//we need to split the file name into its components: parameter, date, run_date
const std::string::size_type pos_param = filename.find('_');
if (pos_param==string::npos) continue;
const std::string::size_type date_start = filename.find_first_of("0123456789");
if (date_start==string::npos) continue;
const std::string::size_type date_end = filename.find('_', date_start);
if (date_end==string::npos) continue;
const std::string::size_type rundate_start = filename.rfind('_');
if (rundate_start==string::npos) continue;
const std::string::size_type rundate_end = filename.rfind('.');
if (rundate_end==string::npos) continue;
const std::string date_str( filename.substr(date_start, date_end-date_start) );
const std::string run_date( filename.substr(rundate_start+1, rundate_end-rundate_start-1) );
//do we already have an entry for this date?
size_t idx = IOUtils::npos;
const std::map::const_iterator it_map = mapIdx.find( date_str );
if (it_map!=mapIdx.end()) {
idx = it_map->second;
if (meteo_files[idx].run_date>run_date) continue;
}
//we don't have an entry or it is too old -> create new entry / replace existing one
const std::string path( FileUtils::getPath(file_and_path) );
Date date;
IOUtils::convertString(date, date_str, in_dflt_TZ);
const file_index elem(date, path, filename.substr(pos_param), run_date);
if (idx==IOUtils::npos) {
meteo_files.push_back( elem );
mapIdx[ date_str ] = meteo_files.size()-1;
} else {
meteo_files[ idx] = elem;
mapIdx[ date_str ] = idx;
}
}
std::sort(meteo_files.begin(), meteo_files.end());
}
size_t OshdIO::getFileIdx(const Date& start_date) const
{
if (cache_meteo_files.empty())
throw InvalidArgumentException("No input files found or configured!", AT);
//find which file we should open
if (cache_meteo_files.size()==1) {
return 0;
} else {
for (size_t idx=1; idx=cache_meteo_files[idx-1].date && start_date& vecStation)
{
vecStation.clear();
if (vecMeta.empty()) fillStationMeta();
vecStation = vecMeta;
}
void OshdIO::readMeteoData(const Date& dateStart, const Date& dateEnd,
std::vector< std::vector >& vecMeteo)
{
vecMeteo.clear();
size_t file_idx = getFileIdx( dateStart );
Date station_date( cache_meteo_files[file_idx].date );
if (station_datedateEnd) return; //the requested period is NOT in the available files
const size_t nr_files = cache_meteo_files.size();
const size_t nrIDs = vecIDs.size();
if (vecMeta.empty()) fillStationMeta(); //this also fills vecIdx
vecMeteo.resize( nrIDs );
do {
//create empty MeteoData for the current timestep
for (size_t jj=0; jj vecData;
for (size_t ii=0; ii >& vecMeteo) const
{
std::vector vecDir;
vecDir.resize( nrIDs, IOUtils::nodata );
const std::string filename_dir( path + "/" + "idrc" + file_suffix );
readFromFile(filename_dir, MeteoData::ISWR, station_date, vecDir);
std::vector vecDiff;
vecDiff.resize( nrIDs, IOUtils::nodata );
const std::string filename_diff( path + "/" + "idfc" + file_suffix );
readFromFile(filename_diff, MeteoData::ISWR, station_date, vecDiff);
std::vector vecAlbd;
const std::string filename_albd( path + "/" + "albd" + file_suffix );
if (FileUtils::fileExists(filename_albd)) {
vecAlbd.resize( nrIDs, IOUtils::nodata );
readFromFile(filename_albd, MeteoData::RSWR, station_date, vecAlbd); //We read ALBD and use it to build RSWR
}
const double albedo = !vecAlbd.empty();
for (size_t jj=0; jj >& vecMeteo) const
{
const std::string filename( path + "/" + "snfl" + file_suffix );
if (FileUtils::fileExists(filename)) {
const double half_elevation_band = 50.; //we consider that there are mixed precip in the elevation range snow_line ± half_elevation_band
std::vector vecSnowLine;
vecSnowLine.resize( nrIDs, IOUtils::nodata );
readFromFile(filename, MeteoData::PSUM_PH, station_date, vecSnowLine);
for (size_t jj=0; jj(vecSnowLine[jj]+half_elevation_band))
vecMeteo[jj].back()( MeteoData::PSUM_PH ) = 0.;
else if (altitude<(vecSnowLine[jj]-half_elevation_band))
vecMeteo[jj].back()( MeteoData::PSUM_PH ) = 1.;
else
vecMeteo[jj].back()( MeteoData::PSUM_PH ) = .5;
}
}
}
void OshdIO::readFromFile(const std::string& filename, const MeteoData::Parameters& param, const Date& in_timestep, std::vector &vecData) const
{
if (debug) printFileStructure(filename);
mat_t *matfp = Mat_Open(filename.c_str(), MAT_ACC_RDONLY);
if ( NULL == matfp ) throw AccessException(filename, AT);
//open the file and read some metadata
matvar_t *matvar = Mat_VarReadInfo(matfp, "stat");
if (matvar==NULL) throw NotFoundException("structure 'stat' not found in file '"+filename+"'", AT);
if (matvar->class_type!=MAT_C_STRUCT) throw InvalidFormatException("The matlab file should contain 1 structure", AT);
const std::string type( readString(filename, "type", matfp, matvar) );
checkFieldType(param, type);
//check that the timestep is as expected
std::vector vecTime;
readDoubleVector(filename, "time", matfp, matvar, vecTime);
if (vecTime.size()!=1) throw InvalidFormatException("one and only one time step must be present in the 'time' vector", AT);
Date timestep;
timestep.setMatlabDate( vecTime[0], in_dflt_TZ );
if (in_timestep!=timestep) throw InvalidArgumentException("the in-file timestep and the filename time step don't match for for '"+filename+"'", AT);
//check that each station is still at the same index, build the index cache if necessary
std::vector vecAcro;
readStringVector(filename, "acro", matfp, matvar, vecAcro);
const size_t nrIDs = vecIDs.size();
for (size_t ii=0; ii vecRaw;
readDoubleVector(filename, "data", matfp, matvar, vecRaw);
if (vecAcro.size() != vecRaw.size()) throw InvalidFormatException("'acro' and 'data' arrays don't match in file '"+filename+"'", AT);
for (size_t ii=0; ii vecAcro;
readStringVector(in_metafile, "acro", matfp, matvar, vecAcro);
std::vector vecNames;
readStringVector(in_metafile, "name", matfp, matvar, vecNames);
std::vector easting, northing, altitude;
readDoubleVector(in_metafile, "x", matfp, matvar, easting);
readDoubleVector(in_metafile, "y", matfp, matvar, northing);
readDoubleVector(in_metafile, "z", matfp, matvar, altitude);
Mat_VarFree(matvar);
Mat_Close(matfp);
if (debug) {
for (size_t ii=0; ii& vecAcro)
{
const size_t nrIDs = vecIDs.size();
if (nrIDs==0)
throw InvalidArgumentException("Please provide at least one station ID to read!", AT);
vecIdx.resize( nrIDs, 0 );
for (size_t ii=0; ii