/***********************************************************************************/ /* 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