From 2abc460f4ff4848f908e022103ec7f71aaa2ecba Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Mon, 24 Jan 2011 04:53:27 +0000 Subject: [PATCH] Add warning/watch/alert plugin Note: this is development code and will not run without the following: - An xml alerts file. The alerts feed [1] has some issues which need to be resolved before much more development is done. These include missing data (hazard's weather outlook), missing lat-lon polygons (for SVR/TOR warnings), unknown sorting order, and inconsistent use of fips and NWS warning area codes. - A fips polygons file. The original was found as a link from the CAP cookbook/wiki [2]. However, this contained a convex hull of each county, which is not entirely useful. Andy Spencer created two more which are hopefully a little more useful but contain a larger number of points and require tessellation to use [3]. [1] http://www.weather.gov/alerts-beta/us.php?x=0 [2] http://www.incident.com/cap/fips_polys.txt [3] http://andy753421.ath.cx/linked/fips_polys_{simp,full}.txt --- src/plugins/Makefile.am | 8 +- src/plugins/alert-info.c | 177 +++++++++++ src/plugins/alert-info.h | 38 +++ src/plugins/alert.c | 617 +++++++++++++++++++++++++++++++++++++++ src/plugins/alert.h | 56 ++++ 5 files changed, 895 insertions(+), 1 deletion(-) create mode 100644 src/plugins/alert-info.c create mode 100644 src/plugins/alert-info.h create mode 100644 src/plugins/alert.c create mode 100644 src/plugins/alert.h diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index ce9f557..ab7d911 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -5,7 +5,13 @@ LIBS = $(GRITS_LIBS) pluginsdir = $(pkglibdir) -plugins_LTLIBRARIES = +plugins_LTLIBRARIES = alert.la + +alert_la_SOURCES = \ + alert.c alert.h \ + alert-info.c alert-info.h +alert_la_LIBADD = $(GRITS_LIBS) \ + $(addprefix $(top_srcdir)/lib/,$(gl_LTLIBOBJS)) if HAVE_RSL plugins_LTLIBRARIES += radar.la diff --git a/src/plugins/alert-info.c b/src/plugins/alert-info.c new file mode 100644 index 0000000..8f2a1ab --- /dev/null +++ b/src/plugins/alert-info.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Taken from: http://www.weather.gov/wwamap-prd/faq.php */ + +#include "alert-info.h" + +AlertInfo alert_info[] = { + // title category abbr prio enab cur color + /* Warnings */ + {"Tornado Warning" , "warning", "", 0, 1, 0, {255, 0 , 0 }}, // red + {"Severe Thunderstorm Warning" , "warning", "", 1, 1, 0, {255, 165, 0 }}, // orange + {"Flash Flood Warning" , "warning", "", 2, 1, 0, {139, 0 , 0 }}, // darkred + {"Ashfall Warning" , "warning", "", 1000, 1, 0, {169, 169, 169}}, // darkgray + {"Avalanche Warning" , "warning", "", 1000, 1, 0, {30 , 144, 255}}, // dodgerblue + {"Blizzard Warning" , "warning", "", 1000, 1, 0, {255, 69 , 0 }}, // orangered + {"Civil Danger Warning" , "warning", "", 1000, 1, 0, {255, 182, 193}}, // lightpink + {"Coastal Flood Warning" , "warning", "", 1000, 1, 0, {34 , 139, 34 }}, // forestgreen + {"Dust Storm Warning" , "warning", "", 1000, 1, 0, {255, 228, 196}}, // bisque + {"Earthquake Warning" , "warning", "", 1000, 1, 0, {139, 69 , 19 }}, // saddlebrown + {"Excessive Heat Warning" , "warning", "", 1000, 1, 0, {199, 21 , 133}}, // mediumvioletred + {"Extreme Cold Warning" , "warning", "", 1000, 1, 0, {0 , 0 , 255}}, // blue + {"Extreme Wind Warning" , "warning", "", 1000, 1, 0, {255, 20 , 147}}, // deeppink + {"Fire Warning" , "warning", "", 1000, 1, 0, {160, 82 , 45 }}, // sienna + {"Flood Warning" , "warning", "", 1000, 1, 0, {0 , 255, 0 }}, // lime + {"Freeze Warning" , "warning", "", 1000, 1, 0, {0 , 255, 255}}, // cyan + {"Gale Warning" , "warning", "", 1000, 1, 0, {221, 160, 221}}, // plum + {"Hard Freeze Warning" , "warning", "", 1000, 1, 0, {0 , 0 , 255}}, // blue + {"Hazardous Materials Warning" , "warning", "", 1000, 1, 0, {75 , 0 , 130}}, // indigo + {"Hazardous Seas Warning" , "warning", "", 1000, 1, 0, {221, 160, 221}}, // plum + {"Heavy Freezing Spray Warning" , "warning", "", 1000, 1, 0, {0 , 191, 255}}, // deepskyblue + {"Heavy Snow Warning" , "warning", "", 1000, 1, 0, {138, 43 , 226}}, // blueviolet + {"High Surf Warning" , "warning", "", 1000, 1, 0, {34 , 139, 34 }}, // forestgreen + {"High Wind Warning" , "warning", "", 1000, 1, 0, {218, 165, 32 }}, // goldenrod + {"Hurricane Force Wind Warning" , "warning", "", 1000, 1, 0, {205, 92 , 92 }}, // westernred + {"Hurricane Warning" , "warning", "", 1000, 1, 0, {220, 20 , 60 }}, // crimson + {"Hurricane Wind Warning" , "warning", "", 1000, 1, 0, {205, 92 , 92 }}, // westernred + {"Ice Storm Warning" , "warning", "", 1000, 1, 0, {139, 0 , 139}}, // darkmagenta + {"Lake Effect Snow Warning" , "warning", "", 1000, 1, 0, {0 , 139, 139}}, // darkcyan + {"Lakeshore Flood Warning" , "warning", "", 1000, 1, 0, {34 , 139, 34 }}, // forestgreen + {"Law Enforcement Warning" , "warning", "", 1000, 1, 0, {192, 192, 192}}, // silver + {"Nuclear Power Plant Warning" , "warning", "", 1000, 1, 0, {75 , 0 , 130}}, // indigo + {"Radiological Hazard Warning" , "warning", "", 1000, 1, 0, {75 , 0 , 130}}, // indigo + {"Red Flag Warning" , "warning", "", 1000, 1, 0, {255, 20 , 147}}, // deeppink + {"Shelter In Place Warning" , "warning", "", 1000, 1, 0, {250, 128, 114}}, // salmon + {"Sleet Warning" , "warning", "", 1000, 1, 0, {135, 206, 235}}, // skyblue + {"Special Marine Warning" , "warning", "", 1000, 1, 0, {255, 165, 0 }}, // orange + {"Storm Warning" , "warning", "", 1000, 1, 0, {148, 0 , 211}}, // darkviolet + {"Tropical Storm Warning" , "warning", "", 1000, 1, 0, {178, 34 , 34 }}, // firebrick + {"Tropical Storm Wind Warning" , "warning", "", 1000, 1, 0, {178, 34 , 34 }}, // firebrick + {"Tsunami Warning" , "warning", "", 1000, 1, 0, {253, 99 , 71 }}, // tomato + {"Typhoon Warning" , "warning", "", 1000, 1, 0, {220, 20 , 60 }}, // crimson + {"Volcano Warning" , "warning", "", 1000, 1, 0, {47 , 79 , 79 }}, // darkslategray + {"Wind Chill Warning" , "warning", "", 1000, 1, 0, {176, 196, 222}}, // lightsteelblue + {"Winter Storm Warning" , "warning", "", 1000, 1, 0, {255, 105, 180}}, // hotpink + + /* Advisorys */ + {"Air Stagnation Advisory" , "advisory", "", 1000, 1, 0, {128, 128, 128}}, // gray + {"Ashfall Advisory" , "advisory", "", 1000, 1, 0, {105, 105, 105}}, // dimgray + {"Blowing Dust Advisory" , "advisory", "", 1000, 1, 0, {189, 183, 107}}, // darkkhaki + {"Blowing Snow Advisory" , "advisory", "", 1000, 1, 0, {173, 216, 230}}, // lightblue + {"Brisk Wind Advisory" , "advisory", "", 1000, 1, 0, {216, 191, 216}}, // thistle + {"Coastal Flood Advisory" , "advisory", "", 1000, 1, 0, {124, 252, 0 }}, // lawngreen + {"Dense Fog Advisory" , "advisory", "", 1000, 1, 0, {112, 128, 144}}, // slategray + {"Dense Smoke Advisory" , "advisory", "", 1000, 1, 0, {240, 230, 140}}, // khaki + {"Flood Advisory" , "advisory", "", 1000, 1, 0, {0 , 255, 127}}, // springgreen + {"Freezing Drizzle Advisory" , "advisory", "", 1000, 1, 0, {218, 112, 214}}, // orchid + {"Freezing Fog Advisory" , "advisory", "", 1000, 1, 0, {0 , 128, 128}}, // teal + {"Freezing Rain Advisory" , "advisory", "", 1000, 1, 0, {218, 112, 214}}, // orchid + {"Freezing Spray Advisory" , "advisory", "", 1000, 1, 0, {0 , 191, 255}}, // deepskyblue + {"Frost Advisory" , "advisory", "", 1000, 1, 0, {100, 149, 237}}, // cornflowerblue + {"Heat Advisory" , "advisory", "", 1000, 1, 0, {255, 127, 80 }}, // coral + {"High Surf Advisory" , "advisory", "", 1000, 1, 0, {186, 85 , 211}}, // mediumorchid + {"Hydrologic Advisory" , "advisory", "", 1000, 1, 0, {0 , 255, 127}}, // springgreen + {"Lake Effect Snow Advisory" , "advisory", "", 1000, 1, 0, {72 , 209, 204}}, // mediumturquoise + {"Lake Effect Snow and Blowing Snow Advisory" , + "advisory", "", 1000, 1, 0, {72 , 209, 204}}, // mediumturquoise + {"Lake Wind Advisory" , "advisory", "", 1000, 1, 0, {210, 180, 140}}, // tan + {"Lakeshore Flood Advisory" , "advisory", "", 1000, 1, 0, {124, 252, 0 }}, // lawngreen + {"Low Water Advisory" , "advisory", "", 1000, 1, 0, {165, 42 , 42 }}, // brown + {"Sleet Advisory" , "advisory", "", 1000, 1, 0, {123, 104, 238}}, // mediumslateblue + {"Small Craft Advisory" , "advisory", "", 1000, 1, 0, {216, 191, 216}}, // thistle + {"Snow and Blowing Snow Advisory" , "advisory", "", 1000, 1, 0, {176, 224, 230}}, // powderblue + {"Tsunami Advisory" , "advisory", "", 1000, 1, 0, {210, 105, 30 }}, // chocolate + {"Wind Advisory" , "advisory", "", 1000, 1, 0, {210, 180, 140}}, // tan + {"Wind Chill Advisory" , "advisory", "", 1000, 1, 0, {175, 238, 238}}, // paleturquoise + {"Winter Weather Advisory" , "advisory", "", 1000, 1, 0, {123, 104, 238}}, // mediumslateblue + + + /* Watches */ + {"Tornado Watch" , "watch", "", 1000, 1, 0, {255, 255, 0 }}, // yellow + {"Severe Thunderstorm Watch" , "watch", "", 1000, 1, 0, {219, 112, 147}}, // palevioletred + {"Avalanche Watch" , "watch", "", 1000, 1, 0, {244, 164, 96 }}, // sandybrown + {"Blizzard Watch" , "watch", "", 1000, 1, 0, {173, 255, 47 }}, // greenyellow + {"Coastal Flood Watch" , "watch", "", 1000, 1, 0, {102, 205, 170}}, // mediumaquamarine + {"Excessive Heat Watch" , "watch", "", 1000, 1, 0, {128, 0 , 0 }}, // maroon + {"Extreme Cold Watch" , "watch", "", 1000, 1, 0, {0 , 0 , 255}}, // blue + {"Fire Weather Watch" , "watch", "", 1000, 1, 0, {255, 222, 173}}, // navajowhite + {"Flash Flood Watch" , "watch", "", 1000, 1, 0, {50 , 205, 50 }}, // limegreen + {"Flood Watch" , "watch", "", 1000, 1, 0, {46 , 139, 87 }}, // seagreen + {"Freeze Watch" , "watch", "", 1000, 1, 0, {65 , 105, 225}}, // royalblue + {"Gale Watch" , "watch", "", 1000, 1, 0, {255, 192, 203}}, // pink + {"Hard Freeze Watch" , "watch", "", 1000, 1, 0, {65 , 105, 225}}, // royalblue + {"Hazardous Seas Watch" , "watch", "", 1000, 1, 0, {72 , 61 , 139}}, // darkslateblue + {"Heavy Freezing Spray Watch" , "watch", "", 1000, 1, 0, {188, 143, 143}}, // rosybrown + {"High Wind Watch" , "watch", "", 1000, 1, 0, {184, 134, 11 }}, // darkgoldenrod + {"Hurricane Force Wind Watch" , "watch", "", 1000, 1, 0, {153, 50 , 204}}, // darkorchid + {"Hurricane Watch" , "watch", "", 1000, 1, 0, {255, 0 , 255}}, // magenta + {"Hurricane Wind Watch" , "watch", "", 1000, 1, 0, {255, 160, 122}}, // lightsalmon + {"Lake Effect Snow Watch" , "watch", "", 1000, 1, 0, {135, 206, 250}}, // lightskyblue + {"Lakeshore Flood Watch" , "watch", "", 1000, 1, 0, {102, 205, 170}}, // mediumaquamarine + {"Storm Watch" , "watch", "", 1000, 1, 0, {238, 130, 238}}, // violet + {"Tropical Storm Watch" , "watch", "", 1000, 1, 0, {240, 128, 128}}, // lightcoral + {"Tropical Storm Wind Watch" , "watch", "", 1000, 1, 0, {240, 128, 128}}, // lightcoral + {"Typhoon Watch" , "watch", "", 1000, 1, 0, {255, 0 , 255}}, // magenta + {"Wind Chill Watch" , "watch", "", 1000, 1, 0, {95 , 158, 160}}, // cadetblue + {"Winter Storm Watch" , "watch", "", 1000, 1, 0, {70 , 130, 180}}, // steelblue + + /* Statements */ + {"Coastal Flood Statement" , "other", "", 1000, 1, 0, {107, 142, 35 }}, // olivedrab + {"Flash Flood Statement" , "other", "", 1000, 1, 0, {154, 205, 50 }}, // yellowgreen + {"Flood Statement" , "other", "", 1000, 1, 0, {0 , 255, 0 }}, // lime + {"Hurricane Statement" , "other", "", 1000, 1, 0, {147, 112, 219}}, // mediumpurple + {"Lakeshore Flood Statement" , "other", "", 1000, 1, 0, {107, 142, 35 }}, // olivedrab + {"Marine Weather Statement" , "other", "", 1000, 1, 0, {255, 239, 213}}, // peachpuff + {"Rip Current Statement" , "other", "", 1000, 1, 0, {64 , 224, 208}}, // turquoise + {"Severe Weather Statement" , "other", "", 1000, 1, 0, {0 , 255, 255}}, // aqua + {"Special Weather Statement" , "other", "", 1000, 1, 0, {255, 228, 181}}, // moccasin + {"Typhoon Statement" , "other", "", 1000, 1, 0, {147, 112, 219}}, // mediumpurple + + /* Misc */ + {"Air Quality Alert" , "other", "", 1000, 1, 0, {128, 128, 128}}, // gray + + {"Extreme Fire Danger" , "other", "", 1000, 1, 0, {233, 150, 122}}, // darksalmon + + {"Child Abduction Emergency" , "other", "", 1000, 1, 0, {255, 215, 0 }}, // gold + {"Local Area Emergency" , "other", "", 1000, 1, 0, {192, 192, 192}}, // silver + + {"Short Term Forecast" , "other", "", 1000, 1, 0, {152, 251, 152}}, // palegreen + + {"Evacuation Immediate" , "other", "", 1000, 1, 0, {127, 255, 0 }}, // chartreuse + + {"Administrative Message" , "other", "", 1000, 1, 0, {255, 255, 255}}, // white + {"Civil Emergency Message" , "other", "", 1000, 1, 0, {255, 182, 193}}, // lightpink + + {"911 Telephone Outage" , "other", "", 1000, 1, 0, {192, 192, 192}}, // silver + + {"Hazardous Weather Outlook" , "other", "", 1000, 1, 0, {238, 232, 170}}, // palegoldenrod + {"Hydrologic Outlook" , "other", "", 1000, 1, 0, {144, 238, 144}}, // lightgreen + + {"Test" , "other", "", 1000, 1, 0, {240, 255, 255}}, // azure + + /* End of list */ + {NULL, NULL, NULL, 0, 0, 0, {0, 0, 0}}, +}; + +AlertInfo *alert_info_find(gchar *title) +{ + for (int i = 0; alert_info[i].title; i++) + if (g_str_has_prefix(title, alert_info[i].title)) + return &alert_info[i]; + return NULL; +} diff --git a/src/plugins/alert-info.h b/src/plugins/alert-info.h new file mode 100644 index 0000000..fcdd12d --- /dev/null +++ b/src/plugins/alert-info.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __ALERT_INFO_H__ +#define __ALERT_INFO_H__ + +#include +#include + +typedef struct { + char *title; // Title, "Tornado Warning" + char *category; // Category, warning/watch/etc + char *abbr; // Abbreviation, for button label + int prior; // Priority, for county color picking + gboolean enabled; // Show this alert type? + gboolean current; // Are the currently alerts for this type? + guint8 color[3]; // Color to use for drawing alert +} AlertInfo; + +extern AlertInfo alert_info[]; + +AlertInfo *alert_info_find(gchar *title); + +#endif diff --git a/src/plugins/alert.c b/src/plugins/alert.c new file mode 100644 index 0000000..1641abd --- /dev/null +++ b/src/plugins/alert.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2010 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include +#include +#include + +#include "alert.h" +#include "alert-info.h" + +/********** + * Alerts * + **********/ +/* Data types */ +typedef struct { + char sep0; + char class [1 ]; char sep1; + char action[3 ]; char sep2; + char office[4 ]; char sep3; + char phenom[2 ]; char sep4; + char signif[1 ]; char sep5; + char event [4 ]; char sep6; + char begin [12]; char sep7; + char end [12]; char sep8; +} AWeatherVtec; + +typedef struct { + char *title; // Winter Weather Advisory issued December 19 at 8:51PM + char *link; // http://www.weather.gov/alerts-beta/wwacapget.php?x=AK20101219205100AFGWinterWeatherAdvisoryAFG20101220030000AK + char *summary; // ...WINTER WEATHER ADVISORY REMAINS IN EFFECT UNTIL 6 + struct { + time_t effective; // 2010-12-19T20:51:00-09:00 + time_t expires; // 2010-12-20T03:00:00-09:00 + char *status; // Actual + char *urgency; // Expected + char *severity; // Minor + char *certainty; // Likely + char *area_desc; // Northeastern Brooks Range; Northwestern Brooks Range + char *fips6; // 006015 006023 006045 006105 + AWeatherVtec *vtec; // /X.CON.PAFG.WW.Y.0064.000000T0000Z-101220T0300Z/ + } cap; +} AWeatherAlert; + +/* Alert parsing */ +typedef struct { + AWeatherAlert *alert; + GList *alerts; + gchar *text; + gchar *value_name; +} ParseData; +time_t parse_time(gchar *iso8601) +{ + GTimeVal tv = {}; + g_time_val_from_iso8601(iso8601, &tv); + return tv.tv_sec; +} +AWeatherVtec *parse_vtec(char *buf) +{ + AWeatherVtec *vtec = g_new0(AWeatherVtec, 1); + strncpy((char*)vtec, buf, sizeof(AWeatherVtec)); + vtec->sep0 = vtec->sep1 = vtec->sep2 = '\0'; + vtec->sep3 = vtec->sep4 = vtec->sep5 = '\0'; + vtec->sep6 = vtec->sep7 = vtec->sep8 = '\0'; + return vtec; +} +void alert_start(GMarkupParseContext *context, const gchar *name, + const gchar **keys, const gchar **vals, + gpointer user_data, GError **error) +{ + //g_debug("start %s", name); + ParseData *data = user_data; + if (g_str_equal(name, "entry")) + data->alert = g_new0(AWeatherAlert, 1); +} +void alert_end(GMarkupParseContext *context, const gchar *name, + gpointer user_data, GError **error) +{ + //g_debug("end %s", name); + ParseData *data = user_data; + AWeatherAlert *alert = data->alert; + char *text = data->text; + + if (g_str_equal(name, "entry")) + data->alerts = g_list_prepend(data->alerts, data->alert); + + if (!text || !alert) return; + if (g_str_equal(name, "title")) alert->title = g_strdup(text); + else if (g_str_equal(name, "id")) alert->link = g_strdup(text); // hack + else if (g_str_equal(name, "summary")) alert->summary = g_strdup(text); + else if (g_str_equal(name, "cap:effective")) alert->cap.effective = parse_time(text); + else if (g_str_equal(name, "cap:expires")) alert->cap.expires = parse_time(text); + else if (g_str_equal(name, "cap:status")) alert->cap.status = g_strdup(text); + else if (g_str_equal(name, "cap:urgency")) alert->cap.urgency = g_strdup(text); + else if (g_str_equal(name, "cap:severity")) alert->cap.severity = g_strdup(text); + else if (g_str_equal(name, "cap:certainty")) alert->cap.certainty = g_strdup(text); + else if (g_str_equal(name, "cap:areaDesc")) alert->cap.area_desc = g_strdup(text); + + if (g_str_equal(name, "valueName")) { + if (data->value_name) + g_free(data->value_name); + data->value_name = g_strdup(text); + } + + if (g_str_equal(name, "value") && data->value_name) { + if (g_str_equal(data->value_name, "FIPS6")) alert->cap.fips6 = g_strdup(text); + if (g_str_equal(data->value_name, "VTEC")) alert->cap.vtec = parse_vtec(text); + } +} +void alert_text(GMarkupParseContext *context, const gchar *text, + gsize len, gpointer user_data, GError **error) +{ + //g_debug("text %s", text); + ParseData *data = user_data; + if (data->text) + g_free(data->text); + data->text = strndup(text, len); +} + +/* Alert methods */ +GList *alert_parse(gchar *text, gsize len) +{ + g_debug("GritsPluginAlert: alert_parse"); + GMarkupParser parser = { + .start_element = alert_start, + .end_element = alert_end, + .text = alert_text, + }; + ParseData data = {}; + GMarkupParseContext *context = + g_markup_parse_context_new(&parser, 0, &data, NULL); + g_markup_parse_context_parse(context, text, len, NULL); + g_markup_parse_context_free(context); + if (data.text) + g_free(data.text); + if (data.value_name) + g_free(data.value_name); + return data.alerts; +} + +void alert_free(AWeatherAlert *alert) +{ + g_free(alert->title); + g_free(alert->link); + g_free(alert->summary); + g_free(alert->cap.status); + g_free(alert->cap.urgency); + g_free(alert->cap.severity); + g_free(alert->cap.certainty); + g_free(alert->cap.area_desc); + g_free(alert->cap.fips6); + g_free(alert->cap.vtec); + g_free(alert); +} + +void alert_test(char *file) +{ + gchar *text; gsize len; + g_file_get_contents(file, &text, &len, NULL); + GList *alerts = alert_parse(text, len); + g_free(text); + for (GList *cur = alerts; cur; cur = cur->next) { + AWeatherAlert *alert = cur->data; + g_message("alert:"); + g_message(" title = %s", alert->title ); + g_message(" link = %s", alert->link ); + g_message(" summary = %s", alert->summary ); + g_message(" cat.effective = %lu", alert->cap.effective); + g_message(" cat.expires = %lu", alert->cap.expires ); + g_message(" cat.status = %s", alert->cap.status ); + g_message(" cat.urgency = %s", alert->cap.urgency ); + g_message(" cat.severity = %s", alert->cap.severity ); + g_message(" cat.certainty = %s", alert->cap.certainty); + g_message(" cat.area_desc = %s", alert->cap.area_desc); + g_message(" cat.fips6 = %s", alert->cap.fips6 ); + g_message(" cat.vtec = %p", alert->cap.vtec ); + } + g_list_foreach(alerts, (GFunc)alert_free, NULL); + g_list_free(alerts); +} + + +/******** + * FIPS * + ********/ +int int_compare(int a, int b) +{ + return (a < b) ? -1 : + (a == b) ? 0 : 1; +} + +gdouble timed(void) +{ + GTimeVal tv; + g_get_current_time(&tv); + return (gdouble)tv.tv_sec + (gdouble)tv.tv_usec/G_USEC_PER_SEC; +} + +gdouble fips_area(gdouble *a, gdouble *b, gdouble *c) +{ + gdouble cross[3]; + crossd3(a, b, c, cross); + return lengthd(cross)/2; +} + +gdouble fips_remove_one(GList **poly, gdouble thresh) +{ + gdouble min_area = thresh; + GList *min_list = NULL; + if (g_list_length(*poly) <= 4) + return 0; + for (GList *prev = *poly; prev->next->next; prev = prev->next) { + GList *cur = prev->next; + GList *next = prev->next->next; + gdouble area = fips_area(prev->data, cur->data, next->data); + if (area < min_area) { + min_area = area; + min_list = cur; + } + } + if (min_list) { + *poly = g_list_delete_link(*poly, min_list); + return min_area; + } else { + return 0; + } +} + +gpointer fips_simplify(gdouble (*coords)[3], gdouble thresh, gint key) +{ + GList *poly = NULL; + + /* Add points to poly list */ + for (int i = 0; coords[i][0]; i++) + poly = g_list_prepend(poly, coords[i]); + int before = g_list_length(poly); + + /* Simplify poly list */ + //fprintf(stderr, "GritsPluginAlert: remove_one "); + gdouble area; + while ((area = fips_remove_one(&poly, thresh)) > 0) { + //fprintf(stderr, " %f", area); + } + //fprintf(stderr, "\n"); + int after = g_list_length(poly); + + /* Copy points back */ + gdouble (*simp)[3] = (gpointer)g_new0(gdouble, 3*(after+1)); + GList *cur = poly; + for (int i = 0; i < after; i++) { + gdouble *coord = cur->data; + simp[i][0] = coord[0]; + simp[i][1] = coord[1]; + simp[i][2] = coord[2]; + cur = cur->next; + } + + (void)before; + (void)after; + g_debug("GritsPluginAlert: fips_simplify %d: %d -> %d", + key, before, after); + g_list_free(poly); + g_free(coords); + return simp; +} + +void fips_print(FILE *fd, gchar *fips, gchar *name, gchar *state, + gdouble (**points)[3]) +{ + fprintf(fd, "%s\t%s\t%s", fips, name, state); + for (int i = 0; points[i]; i++) { + fprintf(fd, "\t"); + for (int j = 0; points[i][j][0]; j++) { + //fwrite(points[i][j], sizeof(double), 3, fd); + gdouble lat, lon; + xyz2ll(points[i][j][0], points[i][j][1], + points[i][j][2], &lat, &lon); + fprintf(fd, "%.7lf,%.7lf ", lat, lon); + } + } + fprintf(fd, "\n"); +} + +GTree *fips_parse(gchar *text) +{ + g_debug("GritsPluginAlert: fips_parse"); + //FILE *fd = fopen("/tmp/fips_polys_simp.txt", "w+"); + + GTree *tree = g_tree_new((GCompareFunc)int_compare); + gchar **lines = g_strsplit(text, "\n", -1); + for (gint li = 0; lines[li]; li++) { + /* Split and count parts */ + gchar **sparts = g_strsplit(lines[li], "\t", -1); + int nparts = g_strv_length(sparts); + if (nparts < 4) { + g_strfreev(sparts); + continue; + } + + GritsPoint center = {0,0,0}; + gdouble (**polys)[3] = (gpointer)g_new0(double*, nparts-3+1); + for (int pi = 3; pi < nparts; pi++) { + /* Split and count coordinates */ + gchar **scoords = g_strsplit(sparts[pi], " ", -1); + int ncoords = g_strv_length(scoords); + + /* Create binary coords */ + gdouble (*coords)[3] = (gpointer)g_new0(gdouble, 3*(ncoords+1)); + for (int ci = 0; ci < ncoords; ci++) { + gdouble lat, lon; + //sscanf(scoords[ci], "%lf,%lf", &lon, &lat); + sscanf(scoords[ci], "%lf,%lf", &lat, &lon); + if (ci == 0) { + center.lat = lat; + center.lon = lon; + center.elev = 0; + } + lle2xyz(lat, lon, 0, + &coords[ci][0], + &coords[ci][1], + &coords[ci][2]); + } + + /* Simplify coords/contour */ + //coords = fips_simplify(coords, 1000*1000*4, li); + + /* Insert coords into poly array */ + polys[pi-3] = coords; + g_strfreev(scoords); + } + + /* print simplified polys */ + //fips_print(fd, sparts[0], sparts[1], sparts[2], polys); + + /* Create GritsPoly */ + GritsPoly *poly = grits_poly_new(polys); + GRITS_OBJECT(poly)->center = center; + GRITS_OBJECT(poly)->skip |= GRITS_SKIP_CENTER; + GRITS_OBJECT(poly)->skip |= GRITS_SKIP_STATE; + + /* Insert polys into the tree */ + gint id = g_ascii_strtoll(sparts[0], NULL, 10); + g_tree_insert(tree, (gpointer)id, poly); + g_strfreev(sparts); + } + g_strfreev(lines); + + //fclose(fd); + return tree; +} + +/******************** + * GritsPluginAlert * + ********************/ +/* Setup helpers + * init: + * parse counties + * init county polys + * init config area + * refresh: + * parse alert + * update buttons + * update counties + * clicked: + * update counties + */ + +/* Update counties */ +static gboolean _hide_county(gchar *_, GritsObject *county) +{ + g_object_set_data(G_OBJECT(county), "info", NULL); + //county->hidden = TRUE; + return FALSE; +} +static void _update_counties(GritsPluginAlert *alert) +{ + /* Setup counties based on alerts: + * foreach alert in alerts: + * info = infotable.get(alert.type) + * foreach fips in alert.fipses: + * county = counties.get(fips) + * if info is higher priority + * county.color = info.color + */ + g_debug("GritsPluginAlert: _update_counties"); + g_tree_foreach(alert->counties, (GTraverseFunc)_hide_county, NULL); + for (GList *cur = alert->alerts; cur; cur = cur->next) { + AWeatherAlert *msg = cur->data; + + /* Find alert info */ + AlertInfo *info = alert_info_find(msg->title); + if (!info) { + g_warning("GritsPluginAlert: unknown warning - %s", msg->title); + continue; + } + if (!info->enabled) + continue; + + /* Set color for each county in alert */ + gchar **fipses = g_strsplit(msg->cap.fips6, " ", -1); + for (int i = 0; fipses[i]; i++) { + gint fips = g_ascii_strtoll(fipses[i], NULL, 10); + GritsPoly *county = g_tree_lookup(alert->counties, (gpointer)fips); + if (!county) + continue; + AlertInfo *old = g_object_get_data(G_OBJECT(county), "info"); + if (old != NULL && old < info) + continue; + g_object_set_data(G_OBJECT(county), "info", info); + county->color[0] = (float)info->color[0] / 256; + county->color[1] = (float)info->color[1] / 256; + county->color[2] = (float)info->color[2] / 256; + county->color[3] = 0.25; + GRITS_OBJECT(county)->hidden = FALSE; + } + g_strfreev(fipses); + } + gtk_widget_queue_draw(GTK_WIDGET(alert->viewer)); +} + +/* Update buttons */ +static void _alert_click(GtkRadioButton *button, gpointer alert) +{ + g_debug("GritsPluginAlert: _alert_click"); + AlertInfo *info = g_object_get_data(G_OBJECT(button), "info"); + info->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + _update_counties(alert); +} + +static GtkWidget *_make_button(AlertInfo *info) +{ + g_debug("GritsPluginAlert: _make_button - %s", info->title); + GdkColor black = {0, 0, 0, 0}; + GdkColor color = {0, info->color[0]<<8, info->color[1]<<8, info->color[2]<<8}; + + gchar text[6+7]; + g_snprintf(text, sizeof(text), "%.5s", info->title); + + GtkWidget *button = gtk_toggle_button_new(); + GtkWidget *align = gtk_alignment_new(0.5, 0.5, 1, 1); + GtkWidget *cbox = gtk_event_box_new(); + GtkWidget *label = gtk_label_new(text); + for (int state = 0; state < GTK_STATE_INSENSITIVE; state++) { + gtk_widget_modify_fg(label, state, &black); + gtk_widget_modify_bg(cbox, state, &color); + } /* Yuck.. */ + g_object_set_data(G_OBJECT(button), "info", info); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 2, 2, 4, 4); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), info->enabled); + gtk_widget_set_tooltip_text(GTK_WIDGET(button), info->title); + gtk_container_add(GTK_CONTAINER(cbox), label); + gtk_container_add(GTK_CONTAINER(align), cbox); + gtk_container_add(GTK_CONTAINER(button), align); + return button; +} + +static void _update_buttons(GritsPluginAlert *alert) +{ + g_debug("GritsPluginAlert: _update_buttons"); + /* Delete old buttons */ + GList *frames = gtk_container_get_children(GTK_CONTAINER(alert->config)); + for (GList *frame = frames; frame; frame = frame->next) { + GtkWidget *table = gtk_bin_get_child(GTK_BIN(frame->data)); + GList *btns = gtk_container_get_children(GTK_CONTAINER(table)); + g_list_foreach(btns, (GFunc)gtk_widget_destroy, NULL); + } + + /* Add new buttons */ + for (int i = 0; alert_info[i].title; i++) { + if (!alert_info[i].current) + continue; + + GtkWidget *table = g_object_get_data(G_OBJECT(alert->config), + alert_info[i].category); + GList *kids = gtk_container_get_children(GTK_CONTAINER(table)); + int nkids = g_list_length(kids); + int x = nkids % 3; + int y = nkids / 3; + g_list_free(kids); + + GtkWidget *button = _make_button(&alert_info[i]); + gtk_table_attach(GTK_TABLE(table), button, x, x+1, y, y+1, + GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0); + g_signal_connect(button, "clicked", + G_CALLBACK(_alert_click), alert); + } +} + +/* Init helpers */ +static gboolean _add_county(gint key, GritsObject *poly, GritsViewer *viewer) +{ + grits_viewer_add(viewer, poly, GRITS_LEVEL_WORLD+1, TRUE); + return FALSE; +} + +static GtkWidget *_make_config(void) +{ + gchar *labels[] = {"Warnings", "Watches", "Advisories", "Other"}; + gchar *keys[] = {"warning", "watch", "advisory", "other"}; + gint cols[] = {3, 2, 2, 2}; + GtkWidget *config = gtk_hbox_new(FALSE, 10); + for (int i = 0; i < G_N_ELEMENTS(labels); i++) { + GtkWidget *frame = gtk_frame_new(labels[i]); + GtkWidget *table = gtk_table_new(1, cols[i], TRUE); + gtk_container_add(GTK_CONTAINER(frame), table); + gtk_box_pack_start(GTK_BOX(config), frame, TRUE, TRUE, 0); + g_object_set_data(G_OBJECT(config), keys[i], table); + } + return config; +} + +/* Methods */ +GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs) +{ + g_debug("GritsPluginAlert: new"); + GritsPluginAlert *alert = g_object_new(GRITS_TYPE_PLUGIN_ALERT, NULL); + g_tree_foreach(alert->counties, (GTraverseFunc)_add_county, viewer); + alert->viewer = viewer; + alert->prefs = prefs; + return alert; +} + +static GtkWidget *grits_plugin_alert_get_config(GritsPlugin *_alert) +{ + GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(_alert); + return alert->config; +} + + +/* GObject code */ +static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface); +G_DEFINE_TYPE_WITH_CODE(GritsPluginAlert, grits_plugin_alert, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN, + grits_plugin_alert_plugin_init)); +static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface) +{ + g_debug("GritsPluginAlert: plugin_init"); + /* Add methods to the interface */ + iface->get_config = grits_plugin_alert_get_config; +} +static void grits_plugin_alert_init(GritsPluginAlert *alert) +{ + g_debug("GritsPluginAlert: class_init"); + /* Set defaults */ + alert->config = _make_config(); + + /* Load counties */ + gchar *text; gsize len; + const gchar *fips_file = "/scratch/aweather/data/fips_polys_simp.txt"; + g_file_get_contents(fips_file, &text, &len, NULL); + alert->counties = fips_parse(text); + g_free(text); + + /* Load alerts (once for testing) */ + const gchar *alert_file = "/scratch/aweather/src/plugins/alert4.xml"; + g_file_get_contents(alert_file, &text, &len, NULL); + alert->alerts = alert_parse(text, len); + g_free(text); + + /* For now, set alert "current" at init */ + for (GList *cur = alert->alerts; cur; cur = cur->next) { + AWeatherAlert *msg = cur->data; + AlertInfo *info = alert_info_find(msg->title); + if (info) info->current = TRUE; + } + + /* For now, run updates */ + _update_buttons(alert); + _update_counties(alert); +} +static void grits_plugin_alert_dispose(GObject *gobject) +{ + g_debug("GritsPluginAlert: dispose"); + GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject); + (void)alert; // TODO + /* Drop references */ + G_OBJECT_CLASS(grits_plugin_alert_parent_class)->dispose(gobject); +} +static void grits_plugin_alert_finalize(GObject *gobject) +{ + g_debug("GritsPluginAlert: finalize"); + GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject); + /* Free data */ + gtk_widget_destroy(alert->config); + + /* Free countes */ + g_tree_destroy(alert->counties); + + /* Free alerts (once for testing) */ + g_list_foreach(alert->alerts, (GFunc)alert_free, NULL); + g_list_free(alert->alerts); + + G_OBJECT_CLASS(grits_plugin_alert_parent_class)->finalize(gobject); +} +static void grits_plugin_alert_class_init(GritsPluginAlertClass *klass) +{ + g_debug("GritsPluginAlert: class_init"); + GObjectClass *gobject_class = (GObjectClass*)klass; + gobject_class->dispose = grits_plugin_alert_dispose; + gobject_class->finalize = grits_plugin_alert_finalize; +} diff --git a/src/plugins/alert.h b/src/plugins/alert.h new file mode 100644 index 0000000..5cce3e8 --- /dev/null +++ b/src/plugins/alert.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __ALERT_H__ +#define __ALERT_H__ + +#include +#include + +#define GRITS_TYPE_PLUGIN_ALERT (grits_plugin_alert_get_type ()) +#define GRITS_PLUGIN_ALERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlert)) +#define GRITS_IS_PLUGIN_ALERT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GRITS_TYPE_PLUGIN_ALERT)) +#define GRITS_PLUGIN_ALERT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlertClass)) +#define GRITS_IS_PLUGIN_ALERT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GRITS_TYPE_PLUGIN_ALERT)) +#define GRITS_PLUGIN_ALERT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlertClass)) + +typedef struct _GritsPluginAlert GritsPluginAlert; +typedef struct _GritsPluginAlertClass GritsPluginAlertClass; + +struct _GritsPluginAlert { + GObject parent_instance; + + /* instance members */ + GritsViewer *viewer; + GritsPrefs *prefs; + GtkWidget *config; + + GList *alerts; + GTree *counties; +}; + +struct _GritsPluginAlertClass { + GObjectClass parent_class; +}; + +GType grits_plugin_alert_get_type(); + +/* Methods */ +GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs); + +#endif + -- 2.43.2