/***********************************************************************************/
/* 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
#include
#include // to avoid forward declaration hell
#include // to avoid forward declaration hell
namespace mio {
#ifdef _MSC_VER
//This is C99, Microsoft should move on and suppport it, it is almost 15 years old!!
double round(const double& x) {
//middle value point test
if (ceil(x+0.5) == floor(x+0.5)) {
const int a = (int)ceil(x);
if (a%2 == 0) {
return ceil(x);
} else {
return floor(x);
}
} else {
return floor(x+0.5);
}
}
#endif
std::string getLibVersion() {
std::ostringstream ss;
ss << _VERSION << " compiled on " << __DATE__ << " " << __TIME__;
return ss.str();
}
namespace IOUtils {
double bearing_to_angle(const double& bearing) {
return (fmod(360.-bearing+90., 360.)*Cst::to_rad);
}
double angle_to_bearing(const double& angle) {
return (fmod( 90.-angle*Cst::to_deg+360. , 360. ));
}
double bearing(std::string bearing_str)
{
trim(bearing_str);
toUpper(bearing_str);
if (bearing_str=="N") return 0.;
if (bearing_str=="NNE") return 22.5;
if (bearing_str=="NE") return 45.;
if (bearing_str=="ENE") return 67.5;
if (bearing_str=="E") return 90.;
if (bearing_str=="ESE") return 112.5;
if (bearing_str=="SE") return 135.;
if (bearing_str=="SSE") return 157.5;
if (bearing_str=="S") return 180.;
if (bearing_str=="SSW") return 202.5;
if (bearing_str=="SW") return 225.;
if (bearing_str=="WSW") return 247.5;
if (bearing_str=="W") return 270.;
if (bearing_str=="WNW") return 292.5;
if (bearing_str=="NW") return 315.;
if (bearing_str=="NNW") return 337.5;
//no match
return nodata;
}
std::string bearing(double bearing)
{
if (bearing==nodata) return "n/a";
bearing = fmod( fmod(bearing, 360.)+360., 360.); //from -infty to +infty back to [0, 360]
if (bearing<=11.25 || bearing>348.75) return "N";
if (bearing<=33.75) return "NNE";
if (bearing<=56.25) return "NE";
if (bearing<=78.75) return "ENE";
if (bearing<=101.25) return "E";
if (bearing<=123.75) return "ESE";
if (bearing<=146.25) return "SE";
if (bearing<=168.75) return "SSE";
if (bearing<=191.25) return "S";
if (bearing<=213.75) return "SSW";
if (bearing<=236.25) return "SW";
if (bearing<=258.75) return "WSW";
if (bearing<=281.25) return "W";
if (bearing<=303.75) return "WNW";
if (bearing<=326.25) return "NW";
if (bearing<=348.75) return "NNW";
//should not be reached
return "";
}
void stripComments(std::string& str)
{
const size_t found = str.find_first_of("#;");
if (found != std::string::npos){
str.erase(found); //rest of line disregarded
}
}
void trim(std::string& str)
{
const std::string whitespaces(" \t\f\v\n\r");
const size_t startpos = str.find_first_not_of(whitespaces); // Find the first character position after excluding leading blank spaces
const size_t endpos = str.find_last_not_of(whitespaces); // Find the first character position from reverse af
// if all spaces or empty return an empty string
if(startpos!=std::string::npos && endpos!=std::string::npos) {
str.erase(endpos+1); //right trim
str.erase(0, startpos); //left trim
} else {
str.clear();
}
}
void toUpper(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
}
void toLower(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
}
std::string strToUpper(std::string str) {
//based on http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
//it is better to let the compiler copy (or not!) the parameters
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
std::string strToLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
bool isNumeric(std::string str, const unsigned int& nBase)
{
trim(str); //delete trailing and leading whitespaces and tabs
std::istringstream iss(str);
if( nBase == 10 ) {
double tmp;
iss >> tmp;
} else if( nBase == 8 || nBase == 16 ) {
int tmp;
iss >> ( ( nBase == 8 ) ? std::oct : std::hex ) >> tmp;
} else
return false;
if( !iss ) //the conversion failed
return false;
return ( iss.rdbuf()->in_avail() == 0 ); //true if nothing was left after conversion
}
bool readKeyValuePair(const std::string& in_line, const std::string& delimiter, std::string &key, std::string &value, const bool& setToUpperCase)
{
const size_t pos = ((delimiter==" ") || (delimiter=="\t"))? in_line.find_first_of(" \t", 0) : in_line.find(delimiter); //first occurence of delimiter
if(pos != std::string::npos) { //ignore in_lines that are empty or without '='
key = in_line.substr(0, pos);
value = in_line.substr(pos + 1);
trim(key);
trim(value);
if (key.empty() || value.empty()) {
return false;
}
if (setToUpperCase)
toUpper(key);
} else {
key="";
value="";
return false;
}
return true;
}
std::string getLogName() {
char *tmp;
if((tmp=getenv("USERNAME"))==NULL) { //Windows & Unix
if((tmp=getenv("LOGNAME"))==NULL) { //Unix
tmp=getenv("USER"); //Windows & Unix
}
}
if(tmp==NULL) return std::string("N/A");
return std::string(tmp);
}
void readKeyValueHeader(std::map& headermap,
std::istream& fin, const size_t& linecount,
const std::string& delimiter, const bool& keep_case)
{
size_t linenr = 0;
std::string line;
//make a test for end of line encoding:
const char eol = getEoln(fin);
for (size_t ii=0; ii< linecount; ii++){
if (std::getline(fin, line, eol)) {
std::string key, value;
linenr++;
const bool result = readKeyValuePair(line, delimiter, key, value);
if(result) {
if(!keep_case) headermap[ strToLower(key) ] = value;
else headermap[key] = value;
} else { // means if ((key == "") || (value==""))
std::ostringstream out;
out << "Invalid key value pair in line: " << linenr << " of header";
throw IOException(out.str(), AT);
}
} else {
throw InvalidFormatException("Premature EOF while reading Header", AT);
}
}
}
size_t readLineToVec(const std::string& line_in, std::vector& vec_data)
{
vec_data.clear();
std::istringstream iss(line_in); //construct inputstream with the string line as input
iss.setf(std::ios::fixed);
iss.precision(std::numeric_limits::digits10);
double tmp;
while (!iss.eof()) {
iss >> std::skipws >> tmp;
if (iss.fail()) {
std::ostringstream ss;
ss << "Can not read column " << vec_data.size()+1 << " in data line \"" << line_in << "\"";
throw InvalidFormatException(ss.str(), AT);
}
vec_data.push_back(tmp);
}
return vec_data.size();
}
size_t readLineToVec(const std::string& line_in, std::vector& vecString)
{
vecString.clear();
std::istringstream iss(line_in); //construct inputstream with the string line as input
std::string tmp_string;
while (!iss.eof()) {
iss >> std::skipws >> tmp_string;
if (!tmp_string.empty()) {
vecString.push_back(tmp_string);
tmp_string.clear();
}
}
return vecString.size();
}
size_t readLineToVec(const std::string& line_in, std::vector& vecString, const char& delim)
{
vecString.clear();
std::string tmp_string;
std::istringstream iss(line_in);
while (getline(iss, tmp_string, delim)){
vecString.push_back(tmp_string);
}
return vecString.size();
}
// generic template function convertString must be defined in the header
const char ALPHANUM[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char NUM[] = "0123456789";
template<> bool convertString(std::string& t, const std::string& str, std::ios_base& (*f)(std::ios_base&))
{
(void)f;
t = str;
trim(t); //delete trailing and leading whitespaces and tabs
return true;
}
template<> bool convertString(bool& t, const std::string& str, std::ios_base& (*f)(std::ios_base&))
{
std::string s(str);
trim(s); //delete trailing and leading whitespaces and tabs
if (toupper(s[0])=='T' || toupper(s[0])=='Y') {
t = true;
} else if (toupper(s[0])=='F' || toupper(s[0])=='N') {
t = false;
} else {
std::istringstream iss(s);
int i;
iss >> f >> i; //Convert first part of stream with the formatter (e.g. std::dec, std::oct)
if (iss.fail()) {//Conversion failed
return false;
}
t = (i != 0);
}
const std::string::size_type pos = s.find_first_not_of(ALPHANUM);
if (pos != std::string::npos) {
std::string tmp = s.substr(pos);
trim(tmp);
if (!tmp.empty() && tmp[0] != '#' && tmp[0] != ';') {//if line holds more than one value it's invalid
return false;
}
}
return true;
}
template<> bool convertString(double& t, const std::string& str, std::ios_base& (*f)(std::ios_base&))
{
if (f == std::dec) {
//First check if string is empty
const char* start = str.c_str();
while(*start && isspace(*start)) start++;
if (*start == '\0' || *start == '#' || *start == ';') { // line empty or comment
t = static_cast (nodata);
return true;
}
//string is not empty
char* end;
t = strtod(str.c_str(), &end); //would strip leading whitespaces, but already done
if (*end == '\0') { //conversion successful
return true;
} else { // conversion might have worked, let's check what is left
while((*end != '\0') && isspace(*end)) end++;
if (*end == '\0' || *end == '#' || *end == ';') { // we allow the number to be followed by a comment
return true;
}
return false; // Invalid string to convert to double
}
}
std::string s(str);
trim(s); //delete trailing and leading whitespaces and tabs
if (s.empty()) {
t = static_cast (nodata);
return true;
}
std::istringstream iss(s);
iss.setf(std::ios::fixed);
iss.precision(std::numeric_limits::digits10); //try to read values with maximum precision
iss >> f >> t; //Convert first part of stream with the formatter (e.g. std::dec, std::oct)
if (iss.fail()) {
//Conversion failed
return false;
}
std::string tmp;
getline(iss, tmp); //get rest of line, if any
trim(tmp);
if (!tmp.empty() && tmp[0] != '#' && tmp[0] != ';') {
//if line holds more than one value it's invalid
return false;
}
return true;
}
template<> bool convertString(unsigned int& t, const std::string& str, std::ios_base& (*f)(std::ios_base&))
{
std::string s(str);
trim(s); //delete trailing and leading whitespaces and tabs
if (s.empty()) {
t = unodata;
return true;
} else {
std::istringstream iss(s);
iss.setf(std::ios::fixed);
iss.precision(std::numeric_limits::digits10); //try to read values with maximum precision
iss >> f >> t; //Convert first part of stream with the formatter (e.g. std::dec, std::oct)
if (iss.fail()) {
//Conversion failed
return false;
}
std::string tmp;
getline(iss, tmp); //get rest of line, if any
trim(tmp);
if (!tmp.empty() && tmp[0] != '#' && tmp[0] != ';') {
//if line holds more than one value it's invalid
return false;
}
return true;
}
}
bool convertString(Date& t, const std::string& str, const double& time_zone, std::ios_base& (*f)(std::ios_base&))
{
std::string s(str);
trim(s); //delete trailing and leading whitespaces and tabs
(void)f;
int year;
unsigned int month, day, hour, minute;
double second;
char rest[32] = "";
const char *c_str = s.c_str();
if (sscanf(c_str, "%d-%u-%u %u:%u:%lg%31s", &year, &month, &day, &hour, &minute, &second, rest) >= 6) {
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate(year, month, day, hour, minute, second, tz);
return true;
} else if (sscanf(c_str, "%d-%u-%uT%u:%u:%lg%31s", &year, &month, &day, &hour, &minute, &second, rest) >= 6) { //ISO
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate(year, month, day, hour, minute, second, tz);
return true;
} else if (sscanf(c_str, "%d-%u-%u %u:%u%31s", &year, &month, &day, &hour, &minute, rest) >= 5) {
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate(year, month, day, hour, minute, (unsigned)0, tz);
return true;
} else if (sscanf(c_str, "%d-%u-%uT%u:%u%31s", &year, &month, &day, &hour, &minute, rest) >= 5) {
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate(year, month, day, hour, minute, (unsigned)0, tz);
return true;
} else if (sscanf(c_str, "%d-%u-%u%31s", &year, &month, &day, rest) >= 3) {
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate(year, month, day, (unsigned)0, (unsigned)0, (unsigned)0, tz);
return true;
} else if (sscanf(c_str, "%u:%u%31s", &hour, &minute, rest) >= 2) {
std::string timezone_iso(rest);
stripComments(timezone_iso);
const double tz = (timezone_iso.empty())? time_zone : Date::parseTimeZone(timezone_iso);
if(tz==nodata) return false;
t.setDate( ((double)hour)/24. + ((double)minute)/24./60. , tz);
return true;
} else {
//try to read purely numerical date, potentially surrounded by other chars
//and potentially containing an ISO time zone string
const size_t in_len = s.length();
//extract date/time
const size_t date_beg = s.find_first_of(NUM);
if (date_beg==npos || date_beg==in_len) return false;
size_t date_end = s.find_first_not_of(NUM, date_beg+1);
if (date_end==npos) date_end = in_len;
const std::string date = s.substr(date_beg, date_end-date_beg);
//parse date/time
const size_t date_len = date.length();
if (date_len<10 || date_len>14) return false;
if (convertString(year,date.substr(0,4))==false) return false;
if (convertString(month,date.substr(4,2))==false) return false;
if (convertString(day,date.substr(6,2))==false) return false;
if (convertString(hour,date.substr(8,2))==false) return false;
if (date_len==10)
minute=0;
else {
if (date_len>=12) {
if( convertString(minute,date.substr(10,2))==false ) return false;
} else
return false;
if (date_len==12)
second=0;
else {
if (date_len==14) {
if (convertString(second,date.substr(12,2))==false) return false;
} else
return false;
}
}
//extract potential ISO time zone string
double tz = time_zone;
const size_t tz_beg = s.find_first_of("+-", date_end);
if (tz_beg!=npos && tz_beg!=in_len) {
size_t tz_end = s.find_first_not_of("0123456789:", date_end+1);
if (tz_end==npos) tz_end = in_len;
const std::string timezone_iso = s.substr(tz_beg, tz_end-tz_beg);
if(!timezone_iso.empty()) tz = Date::parseTimeZone(timezone_iso);
}
t.setDate( year, month, day, hour, minute, second, tz );
}
return true;
}
template<> bool convertString(Coords& t, const std::string& str, std::ios_base& (*f)(std::ios_base&))
{
std::string s(str);
trim(s); //delete trailing and leading whitespaces and tabs
(void)f;
double lat, lon;
try {
Coords::parseLatLon(s, lat, lon);
} catch(const IOException&) {
return false;
}
t.setLatLon(lat, lon, nodata);
return true;
}
void getProjectionParameters(const Config& cfg, std::string& coordin, std::string& coordinparam,
std::string& coordout, std::string& coordoutparam) {
cfg.getValue("COORDSYS", "Input", coordin);
cfg.getValue("COORDPARAM", "Input", coordinparam, IOUtils::nothrow);
cfg.getValue("COORDSYS", "Output", coordout, IOUtils::nothrow);
cfg.getValue("COORDPARAM", "Output", coordoutparam, IOUtils::nothrow);
}
void getTimeZoneParameters(const Config& cfg, double& tz_in, double& tz_out) {
cfg.getValue("TIME_ZONE", "Input", tz_in, IOUtils::nothrow);
cfg.getValue("TIME_ZONE", "Output", tz_out, IOUtils::nothrow);
}
//returns index of element, if element does not exist it returns closest index after soughtdate
//the element needs to be an exact hit or embedded between two measurments
size_t seek(const Date& soughtdate, const std::vector& vecM, const bool& exactmatch)
{
if (vecM.empty() || soughtdate > vecM.back().date || soughtdate < vecM.front().date) {
//the sought date is not contained in the vector, return npos
return npos;
}
const size_t max_idx = vecM.size()-1; //obviously, the index must be <= max_idx
//since usually the sampling rate is quite constant, try to guess where our point
//should be and provide a much smaller search interval around it
const double start_date = vecM.front().date.getJulian(true);
const double end_date = vecM.back().date.getJulian(true);
const double curr_date = soughtdate.getJulian(true);
const double raw_pos = (curr_date-start_date) / (end_date-start_date) * static_cast(max_idx); //always >=0
const size_t start_idx = (size_t)floor(raw_pos*.9);
const size_t end_idx = std::min( (size_t)ceil(raw_pos*1.1), max_idx);
//first and last index of the search interval, either using our initial guess or the full vector
size_t first = (curr_date >= vecM[start_idx].date.getJulian(true))? start_idx : 0;
size_t last = (curr_date <= vecM[end_idx].date.getJulian(true))? end_idx : max_idx;
//if we reach this point: the date is spanned by the buffer and there are at least two elements
if (exactmatch){
//perform binary search
while (first <= last) {
const size_t mid = (first + last) / 2; // compute mid point
if (soughtdate > vecM[mid].date)
first = mid + 1; // repeat search in top half
else if (soughtdate < vecM[mid].date)
last = mid - 1; // repeat search in bottom half
else
return mid; // found it. return position
}
} else {
//perform binary search
while (first <= last) {
const size_t mid = (first + last) / 2; // compute mid point
if (mid < max_idx) {
if ((soughtdate > vecM[mid].date) && (soughtdate < vecM[mid+1].date))
return mid+1;
}
if (soughtdate > vecM[mid].date)
first = mid + 1; // repeat search in top half
else if (soughtdate < vecM[mid].date)
last = mid - 1; // repeat search in bottom half
else
return mid; // found it. return position
}
}
return npos;
}
void getArraySliceParams(const size_t& dimx, const unsigned int& nbworkers, const unsigned int &wk, size_t& startx, size_t& nx)
{
if(nbworkers>dimx) {
std::ostringstream ss;
ss << "Can not split " << dimx << " columns in " << nbworkers << " bands!";
throw InvalidArgumentException(ss.str(), AT);
}
const size_t nx_avg = dimx / nbworkers;
const size_t remainder = dimx % nbworkers;
if(wk<=remainder) { //distribute remainder, 1 extra column per worker, on first workers
nx = nx_avg+1;
startx = (wk-1)*nx;
} else { //all remainder has been distributed, we now attribute a normal number of columns
nx = nx_avg;
startx = remainder*(nx+1) + (wk-1-remainder)*nx;
}
}
double unitsPrefix(const char& prefix)
{
if (prefix == 'f') {
return 1e-15;
} else if (prefix == 'p') {
return 1e-12;
} else if (prefix == 'n') {
return 1e-9;
} else if (prefix == 'u') {
return 1e-6;
} else if (prefix == 'm') {
return 1e-3;
} else if (prefix == 'c') {
return 1e-2;
} else if (prefix == 'd') {
return 1e-1;
} else if (prefix == 'h') {
return 1e2;
} else if (prefix == 'k') {
return 1e3;
} else if (prefix == 'M') {
return 1e6;
} else if (prefix == 'G') {
return 1e9;
} else if (prefix == 'T') {
return 1e12;
} else if (prefix == 'P') {
return 1e15;
}
const std::string prefix_str( 1, prefix );
throw IOException("Invalid unit prefix '"+prefix_str+"'", AT);
}
//currently, only the most simple ase of units are handled. Composite units
//such as 'W/m2 <-> mW/cm2' are NOT handled.
double unitsConversion(const double& val, std::string unitIn, std::string unitOut)
{
if (val==IOUtils::nodata)
return IOUtils::nodata;
if (unitIn.empty() || unitOut.empty())
throw InvalidArgumentException("Units can not be empty!", AT);
if (unitIn=="degK" || unitIn=="°K" || unitIn=="Kelvin")
unitIn = "K";
if (unitOut=="degK" || unitOut=="°K" || unitOut=="Kelvin")
unitOut = "K";
if (unitIn=="degC" || unitIn=="Celsius")
unitIn = "°C";
if (unitOut=="degC" || unitOut=="Celsius")
unitOut = "°C";
if (unitIn=="degF" || unitIn=="Fahrenheit")
unitIn = "°F";
if (unitOut=="degF" || unitOut=="Fahrenheit")
unitOut = "°F";
if (unitIn=="°C" && unitOut=="K") {
return (val+Cst::t_water_triple_pt);
} else if (unitIn=="K" && unitOut=="°C") {
return (val-Cst::t_water_triple_pt);
} else if (unitIn=="K" && unitOut=="°F") {
return ((val-Cst::t_water_triple_pt)*1.8+32.);
} else if (unitIn=="°F" && unitOut=="K") {
return ((val-32.)/1.8+Cst::t_water_triple_pt);
} else if (unitIn=="°F" && unitOut=="°C") {
return ((val-32.)/1.8);
} else if (unitIn=="°C" && unitOut=="°F") {
return (val*1.8+32.);
} else {
//extract the unit prefix
const double inPrefix_factor = (isalpha(unitIn[0]) && isalpha(unitIn[1]))? unitsPrefix( unitIn[0] ) : 1;
const double outPrefix_factor = (isalpha(unitOut[0]) && isalpha(unitOut[1]))? unitsPrefix( unitOut[0] ) : 1;
//extract the unit exponent
const char in_last_char = unitIn[ unitIn.size()-1 ];
const char out_last_char = unitOut[ unitOut.size()-1 ];
const unsigned char inExponent = (isdigit(in_last_char))? static_cast( in_last_char-'0' ) : static_cast( 1 );
const unsigned char outExponent = (isdigit(out_last_char))? static_cast( out_last_char-'0' ) : static_cast( 1 );
//compute the input and output units factor
const double inFactor = (inExponent==1)? inPrefix_factor : Optim::fastPow(inPrefix_factor, inExponent);
const double outFactor = (outExponent==1)? outPrefix_factor : Optim::fastPow(outPrefix_factor, outExponent);
const double ratio = inFactor / outFactor;
return val*ratio;
}
throw ConversionFailedException("Unable to perform unit conversion.", AT);
}
} //namespace IOUtils
} //namespace