/***********************************************************************************/
/* Copyright 2017 SLF */
/***********************************************************************************/
/* 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
#include
using namespace std;
namespace mio {
/**
* @page dbo DBO
* @section dbo_format Format
* This plugin reads meteorological data from DBO
* via the RESTful web service. To compile the plugin you need to have the CURL library with its headers present.
*
* @section dbo_keywords Keywords
* This plugin uses the following keywords:
* - DBO_URL: The URL of the RESTful web service e.g.http://developwis.wsl.ch:8730/osper-api
* - DBO_USER: The username to access the service (optional)
* - DBO_PASS: The password to authenticate the USER (optional)
* - STATION#: station code for the given station, prefixed by the network it belongs ot (for example: IMIS::SLF2)
* - DBO_TIMEOUT: timeout (in seconds) for the connection to the server (default: 60s)
* - DBO_DEBUG: print the full requests/answers from the server when something does not work as expected
*
* @code
* METEO = DBO
* DBO_URL = http://developwis.wsl.ch:8730/osper-api
* DBO_USER = mylogin
* DBO_PASS = mypasswd
* STATION1 = wind_tunnel_meteo
* @endcode
*
* @section dbo_dependencies Picojson
* This plugin relies on picojson for reading and parsing
* JSON data. Picojson is released under a
* 2-Clause BSD License. Please find here below
* the full license agreement for picojson:
*
* @code
* Copyright 2009-2010 Cybozu Labs, Inc.
* Copyright 2011-2014 Kazuho Oku
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* @endcode
*/
//we keep it as a simple function in order to avoid exposing picojson stuff in the header
void printJSON(const picojson::value& v, const unsigned int& depth)
{
if (v.is()) {
for (unsigned int jj=0; jj()) {
const picojson::value::object& obj = v.get();
for (picojson::value::object::const_iterator ii = obj.begin(); ii != obj.end(); ++ii) {
for (unsigned int jj=0; jjfirst << "\n";
printJSON(ii->second, depth+1);
}
} else if (v.is()){
for (unsigned int jj=0; jj() << "\n";
} else if (v.is()){
for (unsigned int jj=0; jj() << "\n";
} else if (v.is()){
for (unsigned int jj=0; jj() << "\n";
} else if (v.is()){ //ie vector
for (unsigned int jj=0; jj();
std::cout << "array " << array.size() << "\n";
for (size_t jj=0; jj()) {
picojson::value::object& obj = v.get();
for (std::map::iterator it = obj.begin(); it != obj.end(); ++it) {
if (it->first==local_path) {
if (!remaining_path.empty())
goToJSONPath(remaining_path, it->second);
else
return it->second;
}
}
}
return picojson::value();
}
void JSONQuery(const std::string& path, picojson::value& v, std::vector& results)
{
if (v.is()) return;
size_t start_pos = 0;
if (path[0]=='$') start_pos++;
if (path[1]=='.') start_pos++;
const size_t end_pos = path.find(".", start_pos);
const std::string local_path = (end_pos!=std::string::npos)? path.substr(start_pos, end_pos-start_pos) : path.substr(start_pos);
const std::string remaining_path = (end_pos!=std::string::npos)? path.substr(end_pos+1) : "";
if (v.is()) {
picojson::value::object& obj = v.get();
for (std::map::iterator it = obj.begin(); it != obj.end(); ++it) {
if (it->first==local_path) {
if (!remaining_path.empty()) {
if (it->second.is()){ //ie vector
picojson::array& array = it->second.get();
for (size_t jj=0; jjsecond, results);
} else {
results.push_back( it->second );
}
}
}
}
}
std::string getString(const std::string& path, picojson::value& v)
{
std::vector results;
JSONQuery(path, v, results);
if (!results.empty()) {
if (! results.front().is() && results.front().is()) return results.front().get();
}
return std::string();
}
std::vector getStrings(const std::string& path, picojson::value& v)
{
std::vector results;
JSONQuery(path, v, results);
std::vector vecString;
for (size_t ii=0; ii()){ //ie vector
const picojson::array& array = results[ii].get();
for (size_t jj=0; jj() && array[jj].is()) vecString.push_back( array[jj].get() );
}
} else
if (! results[ii].is() && results[ii].is()) vecString.push_back( results[ii].get() );
}
return vecString;
}
double getDouble(const std::string& path, picojson::value& v)
{
std::vector results;
JSONQuery(path, v, results);
if (!results.empty()) {
if (! results.front().is() && results.front().is()) return results.front().get();
}
return IOUtils::nodata;
}
std::vector getDoubles(const std::string& path, picojson::value& v)
{
std::vector results;
JSONQuery(path, v, results);
std::vector vecDouble;
for (size_t ii=0; ii()){ //ie vector
const picojson::array& array = results[ii].get();
for (size_t jj=0; jj() && array[jj].is()) vecDouble.push_back( array[jj].get() );
}
} else
if (! results[ii].is() && results[ii].is()) vecDouble.push_back( results[ii].get() );
}
return vecDouble;
}
//converts C to Kelvin, converts RH to [0,1], HS to m
double convertUnits(const MeteoData::Parameters& param, const double& value)
{
if (value==IOUtils::nodata) return value;
switch (param) {
case MeteoData::TA: case MeteoData::TSG: case MeteoData::TSS:
return IOUtils::C_TO_K(value);
case MeteoData::RH: case MeteoData::HS:
return value/100.;
default:
return value;
}
}
bool parseTsPoint(const picojson::value& v, Date& datum, double& value)
{
if (!v.is()) return false;
const picojson::array& array = v.get();
if (array.size()!=2) return false;
if (array[0].is())
IOUtils::convertString(datum, array[0].get(), 0.); //HACK: hard-coding GMT
else
return false;
if (array[1].is())
value = array[1].get();
else {
if (!array[1].is()) return false;
value = IOUtils::nodata;
return true;
}
return true;
}
void parseTimeSerie(const std::string& tsID, const MeteoData::Parameters& param, const StationData& sd, picojson::value& v, std::vector& vecMeteo)
{
picojson::value ts( goToJSONPath("$.measurements", v) );
if (!ts.is())
throw InvalidFormatException("Could not parse timeserie "+tsID, AT);
const picojson::array& vecRaw = ts.get();
if (vecMeteo.empty()) {
vecMeteo.reserve(vecRaw.size());
for (size_t ii=0; ii > getTsProperties(picojson::value& v)
{
std::map > tsMap;
std::vector results;
JSONQuery("$.properties.timeseries", v, results);
for (size_t ii=0; ii()){
const picojson::array& array = results[ii].get();
for (size_t jj=0; jj()) {
std::string code, device_code, agg_type;
double id = -1.;
unsigned int interval = 0;
Date since, until;
const picojson::value::object& obj = array[jj].get();
for (picojson::value::object::const_iterator it = obj.begin(); it != obj.end(); ++it) {
if (it->first=="code" && it->second.is()) code = it->second.get();
if (it->first=="deviceCode" && it->second.is()) device_code = it->second.get();
if (it->first=="id" && it->second.is()) id = it->second.get();
if (it->first=="since" && it->second.is()) IOUtils::convertString(since, it->second.get(), 0.);
if (it->first=="until" && it->second.is()) IOUtils::convertString(until, it->second.get(), 0.);
if (it->first=="aggregationType" && it->second.is()) agg_type = it->second.get();
if (it->first=="aggregationInterval" && it->second.is()) interval = parseInterval(it->second.get());
}
if (device_code=="BATTERY" || device_code=="LOGGER") break;
if (agg_type=="SD") break; //we don't care about standard deviation anyway
if (id==-1.) break; //no id was provided
const std::string param_str( IOUtils::strToUpper( code.substr(0, code.find('_')) ) );
tsMap[param_str].push_back( DBO::tsMeta(since, until, agg_type, id, interval) );
}
}
}
}
return tsMap;
}
/*************************************************************************************************/
const int DBO::http_timeout_dflt = 60; // seconds until connect time out for libcurl
const std::string DBO::metadata_endpoint = "/osper-api/osper/stations/";
const std::string DBO::data_endpoint = "/osper-api/osper/timeseries/";
const std::string DBO::null_string = "null";
DBO::DBO(const std::string& configfile)
: cfg(configfile), vecStationName(), vecMeta(), vecTsMeta(),
coordin(), coordinparam(), coordout(), coordoutparam(),
endpoint(), default_timezone(1.),
http_timeout(http_timeout_dflt), dbo_debug(false)
{
initDBOConnection();
IOUtils::getProjectionParameters(cfg, coordin, coordinparam, coordout, coordoutparam);
cfg.getValues("STATION", "INPUT", vecStationName); //reads station names into vector vecStationName
}
DBO::DBO(const Config& cfgreader)
: cfg(cfgreader), vecStationName(), vecMeta(), vecTsMeta(),
coordin(), coordinparam(), coordout(), coordoutparam(),
endpoint(), default_timezone(1.),
http_timeout(http_timeout_dflt), dbo_debug(false)
{
initDBOConnection();
IOUtils::getProjectionParameters(cfg, coordin, coordinparam, coordout, coordoutparam);
cfg.getValues("STATION", "INPUT", vecStationName); //reads station names into vector vecStationName
}
void DBO::initDBOConnection() {
curl_global_init(CURL_GLOBAL_ALL);
cfg.getValue("DBO_TIMEOUT", "Input", http_timeout, IOUtils::nothrow);
cfg.getValue("TIME_ZONE", "Input", default_timezone, IOUtils::nothrow);
cfg.getValue("DBO_URL", "Input", endpoint);
if (*endpoint.rbegin() != '/') endpoint += "/";
cerr << "[i] Using DBO URL: " << endpoint << endl;
cfg.getValue("DBO_DEBUG", "INPUT", dbo_debug, IOUtils::nothrow);
}
void DBO::readStationData(const Date& /*date*/, std::vector& vecStation)
{
vecStation.clear();
if (vecMeta.empty()) fillStationMeta();
vecStation = vecMeta;
}
void DBO::readMeteoData(const Date& dateStart, const Date& dateEnd,
std::vector< std::vector >& vecMeteo)
{
vecMeteo.clear();
if (vecMeta.empty()) fillStationMeta();
vecMeteo.resize(vecMeta.size());
for(size_t ii=0; ii coordinates( getDoubles("$.geometry.coordinates", v) );
if (coordinates.size()!=3) throw InvalidFormatException("Wrong coordinates specification!", AT);
Coords position(coordin, coordinparam);
position.setLatLon(coordinates[1], coordinates[0], coordinates[2]);
const StationData sd(position, getString("$.properties.name", v), getString("$.properties.locationName", v));
vecMeta.push_back( sd );
//select proper time series
vecTsMeta[ii] = getTsProperties(v);
} else {
if (dbo_debug)
std::cout << "****\nRequest: " << request << "\n****\n";
throw IOException("Could not retrieve data for station " + station_id, AT);
}
}
}
//read all data for the given station
void DBO::readData(const Date& dateStart, const Date& dateEnd, std::vector& vecMeteo, const size_t& stationindex)
{
const Date Start(dateStart.getJulian(true), 0.);
const Date End(dateEnd.getJulian(true), 0.);
//debug info
/*for(size_t ii=0; ii >::iterator it = vecTsMeta[ii].begin(); it != vecTsMeta[ii].end(); ++it) {
for(size_t jj=0; jjsecond.size(); jj++)
std::cout << it->first << " " << it->second[jj].toString() << "\n";
}
}*/
//TODO: for station stationindex, loop over the timeseries that cover [Start, End] for the current station
//vecTsMeta[ stationindex ]
for (std::map >::iterator it = vecTsMeta[stationindex].begin(); it != vecTsMeta[stationindex].end(); ++it) {
for(size_t jj=0; jjsecond.size(); jj++)
std::cout << it->first << " " << it->second[jj].toString() << "\n";
}
readTimeSerie(15, MeteoData::TA, Start, End, vecMeta[stationindex], vecMeteo);
readTimeSerie(23, MeteoData::RH, Start, End, vecMeta[stationindex], vecMeteo);
readTimeSerie(93, MeteoData::RSWR, Start, End, vecMeta[stationindex], vecMeteo);
readTimeSerie(31, MeteoData::HS, Start, End, vecMeta[stationindex], vecMeteo);
readTimeSerie(46, MeteoData::TSG, Start, End, vecMeta[stationindex], vecMeteo);
/*for (size_t param=MeteoData::firstparam; param<=MeteoData::lastparam; param++) {
}*/
}
//dateStart and dateEnd should already be GMT
void DBO::readTimeSerie(const unsigned int& ts_id, const MeteoData::Parameters& param, const Date& dateStart, const Date& dateEnd, const StationData& sd, std::vector& vecMeteo)
{
std::ostringstream ss_ID; ss_ID << ts_id;
const std::string base_url( data_endpoint + ss_ID.str() );
const std::string period( "?from=" + dateStart.toString(Date::ISO) + "Z&until=" + dateEnd.toString(Date::ISO)+"Z" );
//const std::string period( string("?from=2016-11-17T13:00Z") + "&until=" + "2017-01-05T13:00Z" );
const std::string request( base_url + period );
std::stringstream ss;
if (curl_read(request, ss)) {
if (ss.str().empty()) throw UnknownValueException("Timeseries not found: '"+ss_ID.str()+"'", AT);
picojson::value v;
const std::string err( picojson::parse(v, ss.str()) );
if (!err.empty()) {
std::cerr << ss.str() << "\n";
throw IOException("Error while parsing JSON: "+err, AT);
}
parseTimeSerie(ss_ID.str(), param, sd, v, vecMeteo);
} else {
if (dbo_debug)
std::cout << "****\nRequest: " << request << "\n****\n";
throw IOException("Could not retrieve data for timeseries " + ss_ID.str(), AT);
}
}
size_t DBO::data_write(void* buf, size_t size, size_t nmemb, void* userp)
{
if (userp) {
ostream& os = *static_cast(userp);
const std::streamsize len = size * nmemb;
if (os.write(static_cast(buf), len)) return len;
}
return 0;
}
bool DBO::curl_read(const std::string& url_query, std::ostream& os)
{
CURLcode code(CURLE_FAILED_INIT);
CURL* curl = curl_easy_init();
const std::string url( endpoint + url_query );
if (curl) {
if (CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &data_write))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &os))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, DBO::http_timeout))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str())))
{
code = curl_easy_perform(curl);
}
curl_easy_cleanup(curl);
}
if (code!=CURLE_OK) {
if (dbo_debug)
std::cout << "****\nRequest: " << url_query << "\n****\n";
std::cout << "[E] " << curl_easy_strerror(code) << "\t";
}
return (code==CURLE_OK);
}
} //namespace