diff options
Diffstat (limited to 'src/localtemp.cpp')
| -rw-r--r-- | src/localtemp.cpp | 340 |
1 files changed, 165 insertions, 175 deletions
diff --git a/src/localtemp.cpp b/src/localtemp.cpp index e8314bb..e6a5c2b 100644 --- a/src/localtemp.cpp +++ b/src/localtemp.cpp @@ -1,7 +1,7 @@ /******************************************************************************* * File: localtemp.cpp * - * Version: 0.3 * - * Date: 20080508 * + * Version: 0.4 * + * Date: 20260809 * * Author: Gaspar Fernández (helyo@totaki.com) * * * * Copyright (C) 2008 Gaspar Fernández * @@ -31,6 +31,7 @@ * Date (D.M.Y) Author Modification * 08.05.2008 Gaspar Fernández Initial release * 31.10.2010 Gaspar Fernández Bug Corrections + * 09.08.2026 Koudai Kudo Ported to JMA * ********************************************************************************/ @@ -48,7 +49,7 @@ using namespace std; * * * Input: * * char *metar - ICAO name locator (4 chars) * - * char location_name - Name of this location * + * char location_name - Amedas station code (5 digits) * * * * Change History: * * Date Author Modification * @@ -79,13 +80,10 @@ localtemp::localtemp(char *metar, char *location_name) * Date Author Modification * * * *************************************************************/ -void localtemp::set_temp(string tmp_str) +void localtemp::set_temp(double temp_c) { - char buf[8],buf3[8]; - // Format xx F (xx C) - sscanf(tmp_str.data(), "%s %s (%s", buf, buf3, buf3); // We don't want to store the middle F - this->celsius=atoi(buf3); - this->fahrenheit=atoi(buf); + this->celsius=(int)(temp_c+0.5); + this->fahrenheit=(int)(temp_c*9.0/5.0+32.0+0.5); } /************************************************************* @@ -95,143 +93,53 @@ void localtemp::set_temp(string tmp_str) * Extract humidity information. * * * * Input: * - * string hum - Temporary string containing humidity info. * + * int hum - Relative humidity, from the "humidity" field * + * of the Amedas JSON * * * * Change History: * * Date Author Modification * * * *************************************************************/ -void localtemp::set_humidity(string hum) +void localtemp::set_humidity(int hum) { - char buf[5]; // Be able to store a 100%\0 - // Format (xxx%) - sscanf(hum.data(), "%s", buf); - this->humidity=atoi(buf); + this->humidity=hum; } /************************************************************* * Method: get_ob_info * ************************************************************* * Description: * - * Extracts METAR encoded information. We find it in * - * the line starting with "ob: ". We parse this information * - * and fill in variables. Used for time and sky conditions. * + * Works out which theme to use from the 10 minute * + * precipitation figure and current temperature, since * + * Amedas doesn't report general sky conditions the way * + * METAR does. * * * * Input: * - * Nothing * +* double precip10m - 10 minute precipitation, in mm, as * + * read from the "precipitation10m" field of the Amedas * + * JSON (0 or negative means no precipitation reported) * * * * Change History: * * Date Author Modification * * * *************************************************************/ -void localtemp::get_ob_info() // ob: line stores METAR information. This info. is useful to know more exactly sky conditions. -{ // It stores also temperature, time and other info extracted by the decoded text - - size_t pos; - struct tm *localtm; - struct tm *remotetm; - remotetm =new struct tm; - string timeinfo; -try - { - timeinfo=this->ob.substr(6,this->ob.find(' ',6)-6); // DDHHMM[Z] Day Hour Minute Z (Zulu, Aviation Time Reference) - } catch (exception &e) - { - cout<<"fallo en 1"<<ob<<"***"<<endl; - } - localtm=localtime(&get_time); // tm of the fetch time - - // We need month and year of the time given. So let's calculate. -try - { - - remotetm->tm_mday=atoi(timeinfo.substr(0,2).data()); - remotetm->tm_hour=atoi(timeinfo.substr(2,2).data()); - remotetm->tm_min=atoi(timeinfo.substr(4,2).data()); - } catch (exception &e) +void localtemp::get_ob_info(double precip10m) +{ + mInfo.CB=false; + mInfo.fog=undef_fog; // Not reported by Amedas + if (precip10m>0.0) { - cout<<"fallo en 2"<<timeinfo<<"*"<<endl; + if (this->celsius<=2) + mInfo.rain=snow; + else + mInfo.rain=rain; + mInfo.sky=undef_sky; } - remotetm->tm_mon=localtm->tm_mon; - remotetm->tm_year=localtm->tm_year; - remotetm->tm_sec=0; - - if (remotetm->tm_mday==1) // Month of the data - { - if (remotetm->tm_mday!=localtm->tm_mday) - remotetm->tm_mon++; - if (remotetm->tm_mon==12) // It can't be 12!! - { - remotetm->tm_mon=0; - remotetm->tm_year++; - } - } - info_time=mktime(remotetm); - -try - { - this->ob=this->ob.substr(ob.find(' ',6)); // Extract ICAO station name and TIME INFO. - // It will prevent some errors. We could find any of these strings in that name. - - //this->ob=this->ob.substr(this->ob.find(this->metar)+5); - this->ob=this->ob.substr(0,this->ob.rfind('/')-2); // The info. we want is located betweet the last - - pos=this->ob.rfind('/'); // 2 slashes. The las one indicates temperature and dew point - if (pos!=string::npos) // The other one, may not exists but sometimes it does. - this->ob=this->ob.substr(this->ob.rfind('/')); - - } catch (exception &e) + else { - cout <<"Fallo en 3"<<ob<<"***"<<endl; - } - //Sky - if (this->ob.find("TCU")!=string::npos) - mInfo.sky=tcu; - else if ((this->ob.find("BKN")!=string::npos) || - (this->ob.find("OVC")!=string::npos)) - mInfo.sky=brkovc; - else if ((this->ob.find("FEW")!=string::npos) || - (this->ob.find("SCT")!=string::npos)) - mInfo.sky=cloudy; - else if ((this->ob.find("CAVOK")!=string::npos) || - (this->ob.find("SKC")!=string::npos) || - (this->ob.find("CLR")!=string::npos)) - mInfo.sky=clear; - else - mInfo.sky=undef_sky; - - mInfo.CB=(this->ob.find("CB")!=string::npos); - - - //Rain - if ((this->ob.find("DZ")!=string::npos) || - (this->ob.find("RA")!=string::npos)) - mInfo.rain=rain; - else if ((this->ob.find("SN")!=string::npos) || - (this->ob.find("SG")!=string::npos)) - mInfo.rain=snow; - else if ((this->ob.find("GS")!=string::npos) || - (this->ob.find("GR")!=string::npos)) - mInfo.rain=hail; - else mInfo.rain=undef_rain; - - //Fog - if ((this->ob.find("BR")!=string::npos) || - (this->ob.find("FG")!=string::npos)) - mInfo.fog=fog; - else if ((this->ob.find("DU")!=string::npos) || - (this->ob.find("DS")!=string::npos)) - mInfo.fog=dust; - else if ((this->ob.find("FC")!=string::npos) || - (this->ob.find("FU")!=string::npos) || - (this->ob.find("HZ")!=string::npos) || - (this->ob.find("SA")!=string::npos) || - (this->ob.find("SS")!=string::npos) || - (this->ob.find("VA")!=string::npos)) - mInfo.fog=other; - else - mInfo.fog=undef_fog; + mInfo.sky=clear; + } // The most important for the display theme is rain, then fog and then sky conditions if (mInfo.rain!=undef_rain) @@ -240,17 +148,6 @@ try { case rain: theme=RAINY_THEME; break; case snow: theme=SNOWY_THEME; break; - case hail: theme=HAIL_THEME; break; - default: break; - } - } - else if (mInfo.fog!=undef_fog) - { - switch (mInfo.fog) - { - case fog : theme=FOG_THEME; break; - case dust : theme=DUST_THEME; break; - case other: theme=PARTS_THEME; break; default: break; } } @@ -259,9 +156,6 @@ try switch (mInfo.sky) { case clear : theme=CLEAR_THEME; break; - case cloudy : theme=CLOUDY_THEME; break; - case brkovc : theme=BROKEN_THEME; break; - case tcu : theme=TCU_THEME; break; default: break; } } @@ -270,6 +164,99 @@ try } /************************************************************* + * Method: extract_json_number * + ************************************************************* + * Description: * + * Very small, purpose built JSON scraper. It looks for * + * "key":[ inside the given text and parses the first number * + * after it. Amedas values always look like "temp":[26.3,0] * + * (value, quality flag), so we only need the number before * + * the first comma or closing bracket. * + * * + * Input: * + * string json - Text to search in (one HHMM observation * + * block) * + * string key - Field name, e.g. "temp" * + * * + * Output: * + * double &value - Parsed number * + * bool - true if the key was found and a number was read * + * * + * Change History: * + * Date Author Modification * + * * + *************************************************************/ +bool localtemp::extract_json_number(const string &json, const string &key, double &value) +{ + string needle="\""+key+"\":["; + size_t pos=json.find(needle); + if (pos==string::npos) + return false; + pos+=needle.length(); + size_t endpos=json.find_first_of(",]", pos); + if (endpos==string::npos) + return false; + string numstr=json.substr(pos, endpos-pos); + if (numstr.empty() || numstr=="null") + return false; + value=atof(numstr.data()); + return true; +} +/************************************************************* + * Method: fetch_latest_time * + ************************************************************* + * Description: * + * Downloads AMEDAS_LATEST_URL, which returns a single * + * ISO-8601 JST timestamp such as * + * "2024-08-09T15:40:00+09:00", telling us which day's file * + * to request and which HHMM block inside it is the most * + * recent observation. * + * It also converts that JST timestamp into a real UTC * + * epoch (obs_utc), because dwgo.cpp's displaytemp() expects * + * info_time to be a plain UTC instant: it later does * + * "info_time + tm_diff" and feeds that into localtime() to * + * show it in the viewer's own local time zone. JMA gives us * + * JST (UTC+9) directly, so we build the calendar fields with * + * timegm() (which treats a struct tm as UTC, unlike * + * mktime() which would apply the *host*'s time zone) and * + * then subtract 9 hours to land back on true UTC. * + * * + * Output: * + * string &yyyymmdd - Date, e.g. "20240809" * + * string &hhmm - Time, e.g. "1540" * + * time_t &obs_utc - Observation instant as a UTC epoch * + * bool - true if we could fetch and parse the timestamp * + * * + * Change History: * + * Date Author Modification * + * * + *************************************************************/ +bool localtemp::fetch_latest_time(string &yyyymmdd, string &hhmm, time_t &obs_utc) +{ + MySock *skt; + HTTP_Request *http; + struct tm jst; + skt = new MySock((char*)AMEDAS_LATEST_URL); + http=skt->GetHTTPData(); + delete skt; + if (http==NULL || http->status!=200 || http->data.length()<19) + return false; + // Expected format: YYYY-MM-DDTHH:MM:SS+09:00 + string d=http->data; + yyyymmdd=d.substr(0,4)+d.substr(5,2)+d.substr(8,2); + hhmm=d.substr(11,2)+d.substr(14,2); + memset(&jst, 0, sizeof(jst)); + jst.tm_year=atoi(d.substr(0,4).data())-1900; + jst.tm_mon =atoi(d.substr(5,2).data())-1; + jst.tm_mday=atoi(d.substr(8,2).data()); + jst.tm_hour=atoi(d.substr(11,2).data()); + jst.tm_min =atoi(d.substr(14,2).data()); + jst.tm_sec =atoi(d.substr(17,2).data()); + obs_utc=timegm(&jst)-9*3600; // JST -> UTC (JMA has no DST, always UTC+9) + return true; +} + +/************************************************************* * Method: getInfo * ************************************************************* * Description: * @@ -284,65 +271,68 @@ try *************************************************************/ bool localtemp::getInfo() { - char *metar_fetch; // Get Metar data URL + char *point_fetch; // Amedas point data URL MySock *skt; HTTP_Request *http; - string::size_type pos, pos2; - TKey_Value datarl; + string yyyymmdd, hhmm; + time_t obs_utc; + double temp_c, hum, precip10m; this->error=0; // No error this->loaded=false; - metar_fetch= (char*) malloc(80); - string tmp; - sprintf(metar_fetch, METAR_URL, this->metar.data()); + if (!fetch_latest_time(yyyymmdd, hhmm, obs_utc)) + return false; - skt = new MySock(metar_fetch); + point_fetch= (char*) malloc(strlen(AMEDAS_POINT_URL)+this->metar.length()+yyyymmdd.length()+4); + sprintf(point_fetch, AMEDAS_POINT_URL, this->metar.data(), yyyymmdd.data()); + + skt = new MySock(point_fetch); verbsth(VERB_ASTTO, "Open connection: "); http=skt->GetHTTPData(); delete skt; // We don't need this anymore + free(point_fetch); if (http!=NULL) { if (http->status==200) { time(&get_time); // We got the file at this moment - pos=0; verbsth(VERB_ASTTO, "Get Data: "); - do + this->long_location=this->location_name; + // Locate the "HHMM": { ... } block matching the latest + // reported observation time and keep it (and everything + // after it, it doesn't matter) in this->ob, mirroring the + // way the old METAR "ob:" line used to be stored. + size_t blockpos=http->data.find("\""+hhmm+"\":"); + if (blockpos==string::npos) { - pos2=http->data.find("\n",pos+1); - if (pos==0) // Extracts the first line (Name of the Station) - { - this->long_location=http->data.substr(0, http->data.find("(",0)-1); - verbsth(VERB_ASTTO, "Station name: "+this->long_location); - } - if (pos2!=string::npos) - { - tmp = http->data.substr(pos,pos2-pos); - datarl.key=tmp.substr(0, tmp.find(":")); - datarl.value=tmp.substr(tmp.find(":")+1); - if (datarl.key=="Temperature") - this->set_temp(datarl.value); - else if (datarl.key=="Relative Humidity") - this->set_humidity(datarl.value); - else if (datarl.key=="Sky conditions") - this->sky=datarl.value; - else if (datarl.key=="ob") - this->ob=datarl.value; - // Search interesting data - pos=pos2+1; - } - } while (pos2!=string::npos); - verbsth(VERB_ASTTO, "Get ob info: "); - - get_ob_info(); // Get info from the METAR string - this->loaded=true; - verbsth(VERB_ASTTO, "Close connection: "); - + blockpos=http->data.rfind("\":{"); + if (blockpos!=string::npos) + blockpos=http->data.rfind("\"", blockpos-1); + } + if (blockpos!=string::npos) + { + this->ob=http->data.substr(blockpos); + if (extract_json_number(this->ob, "temp", temp_c)) + this->set_temp(temp_c); + if (extract_json_number(this->ob, "humidity", hum)) + this->set_humidity((int)hum); + if (!extract_json_number(this->ob, "precipitation10m", precip10m)) + precip10m=0.0; + this->info_time=obs_utc; // Actual observation instant, in UTC + get_ob_info(precip10m); // Work out theme from precipitation/temperature + this->loaded=true; + verbsth(VERB_ASTTO, "Close connection: "); + } + else + { + this->error=1; + verbsth(VERB_WARNING, "Couldn't find observation data in Amedas response."); + } } else { |
