--- /dev/null
+/*
+ * Copyright (C) 2010 Andy Spencer <andy753421@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* 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;
+}
--- /dev/null
+/*
+ * Copyright (C) 2010 Andy Spencer <andy753421@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <grits.h>
+#include <GL/gl.h>
+#include <stdio.h>
+#include <string.h>
+
+#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), "<b>%.5s</b>", 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;
+}