]> Pileus Git - aweather/commitdiff
Add warning/watch/alert plugin alert
authorAndy Spencer <andy753421@gmail.com>
Mon, 24 Jan 2011 04:53:27 +0000 (04:53 +0000)
committerAndy Spencer <andy753421@gmail.com>
Mon, 24 Jan 2011 05:12:47 +0000 (05:12 +0000)
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
src/plugins/alert-info.c [new file with mode: 0644]
src/plugins/alert-info.h [new file with mode: 0644]
src/plugins/alert.c [new file with mode: 0644]
src/plugins/alert.h [new file with mode: 0644]

index ce9f557551f3d6ce891017943f9c707c5aafb0ac..ab7d911661a4aca8b0da56c79750ce84cec5b686 100644 (file)
@@ -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 (file)
index 0000000..8f2a1ab
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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;
+}
diff --git a/src/plugins/alert-info.h b/src/plugins/alert-info.h
new file mode 100644 (file)
index 0000000..fcdd12d
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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/>.
+ */
+
+#ifndef __ALERT_INFO_H__
+#define __ALERT_INFO_H__
+
+#include <glib.h>
+#include <rsl.h>
+
+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 (file)
index 0000000..1641abd
--- /dev/null
@@ -0,0 +1,617 @@
+/*
+ * 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;
+}
diff --git a/src/plugins/alert.h b/src/plugins/alert.h
new file mode 100644 (file)
index 0000000..5cce3e8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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/>.
+ */
+
+#ifndef __ALERT_H__
+#define __ALERT_H__
+
+#include <glib-object.h>
+#include <grits.h>
+
+#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
+