summaryrefslogtreecommitdiff
path: root/src/localtemp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/localtemp.cpp')
-rw-r--r--src/localtemp.cpp340
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
{