2 * Copyright (C) 2010 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "alert-info.h"
33 char class [1 ]; char sep1;
34 char action[3 ]; char sep2;
35 char office[4 ]; char sep3;
36 char phenom[2 ]; char sep4;
37 char signif[1 ]; char sep5;
38 char event [4 ]; char sep6;
39 char begin [12]; char sep7;
40 char end [12]; char sep8;
44 char *title; // Winter Weather Advisory issued December 19 at 8:51PM
45 char *link; // http://www.weather.gov/alerts-beta/wwacapget.php?x=AK20101219205100AFGWinterWeatherAdvisoryAFG20101220030000AK
46 char *summary; // ...WINTER WEATHER ADVISORY REMAINS IN EFFECT UNTIL 6
48 time_t effective; // 2010-12-19T20:51:00-09:00
49 time_t expires; // 2010-12-20T03:00:00-09:00
50 char *status; // Actual
51 char *urgency; // Expected
52 char *severity; // Minor
53 char *certainty; // Likely
54 char *area_desc; // Northeastern Brooks Range; Northwestern Brooks Range
55 char *fips6; // 006015 006023 006045 006105
56 AWeatherVtec *vtec; // /X.CON.PAFG.WW.Y.0064.000000T0000Z-101220T0300Z/
67 time_t parse_time(gchar *iso8601)
70 g_time_val_from_iso8601(iso8601, &tv);
73 AWeatherVtec *parse_vtec(char *buf)
75 AWeatherVtec *vtec = g_new0(AWeatherVtec, 1);
76 strncpy((char*)vtec, buf, sizeof(AWeatherVtec));
77 vtec->sep0 = vtec->sep1 = vtec->sep2 = '\0';
78 vtec->sep3 = vtec->sep4 = vtec->sep5 = '\0';
79 vtec->sep6 = vtec->sep7 = vtec->sep8 = '\0';
82 void alert_start(GMarkupParseContext *context, const gchar *name,
83 const gchar **keys, const gchar **vals,
84 gpointer user_data, GError **error)
86 //g_debug("start %s", name);
87 ParseData *data = user_data;
88 if (g_str_equal(name, "entry"))
89 data->alert = g_new0(AWeatherAlert, 1);
91 void alert_end(GMarkupParseContext *context, const gchar *name,
92 gpointer user_data, GError **error)
94 //g_debug("end %s", name);
95 ParseData *data = user_data;
96 AWeatherAlert *alert = data->alert;
97 char *text = data->text;
99 if (g_str_equal(name, "entry"))
100 data->alerts = g_list_prepend(data->alerts, data->alert);
102 if (!text || !alert) return;
103 if (g_str_equal(name, "title")) alert->title = g_strdup(text);
104 else if (g_str_equal(name, "id")) alert->link = g_strdup(text); // hack
105 else if (g_str_equal(name, "summary")) alert->summary = g_strdup(text);
106 else if (g_str_equal(name, "cap:effective")) alert->cap.effective = parse_time(text);
107 else if (g_str_equal(name, "cap:expires")) alert->cap.expires = parse_time(text);
108 else if (g_str_equal(name, "cap:status")) alert->cap.status = g_strdup(text);
109 else if (g_str_equal(name, "cap:urgency")) alert->cap.urgency = g_strdup(text);
110 else if (g_str_equal(name, "cap:severity")) alert->cap.severity = g_strdup(text);
111 else if (g_str_equal(name, "cap:certainty")) alert->cap.certainty = g_strdup(text);
112 else if (g_str_equal(name, "cap:areaDesc")) alert->cap.area_desc = g_strdup(text);
114 if (g_str_equal(name, "valueName")) {
115 if (data->value_name)
116 g_free(data->value_name);
117 data->value_name = g_strdup(text);
120 if (g_str_equal(name, "value") && data->value_name) {
121 if (g_str_equal(data->value_name, "FIPS6")) alert->cap.fips6 = g_strdup(text);
122 if (g_str_equal(data->value_name, "VTEC")) alert->cap.vtec = parse_vtec(text);
125 void alert_text(GMarkupParseContext *context, const gchar *text,
126 gsize len, gpointer user_data, GError **error)
128 //g_debug("text %s", text);
129 ParseData *data = user_data;
132 data->text = strndup(text, len);
136 GList *alert_parse(gchar *text, gsize len)
138 g_debug("GritsPluginAlert: alert_parse");
139 GMarkupParser parser = {
140 .start_element = alert_start,
141 .end_element = alert_end,
145 GMarkupParseContext *context =
146 g_markup_parse_context_new(&parser, 0, &data, NULL);
147 g_markup_parse_context_parse(context, text, len, NULL);
148 g_markup_parse_context_free(context);
152 g_free(data.value_name);
156 void alert_free(AWeatherAlert *alert)
158 g_free(alert->title);
160 g_free(alert->summary);
161 g_free(alert->cap.status);
162 g_free(alert->cap.urgency);
163 g_free(alert->cap.severity);
164 g_free(alert->cap.certainty);
165 g_free(alert->cap.area_desc);
166 g_free(alert->cap.fips6);
167 g_free(alert->cap.vtec);
171 void alert_test(char *file)
173 gchar *text; gsize len;
174 g_file_get_contents(file, &text, &len, NULL);
175 GList *alerts = alert_parse(text, len);
177 for (GList *cur = alerts; cur; cur = cur->next) {
178 AWeatherAlert *alert = cur->data;
180 g_message(" title = %s", alert->title );
181 g_message(" link = %s", alert->link );
182 g_message(" summary = %s", alert->summary );
183 g_message(" cat.effective = %lu", alert->cap.effective);
184 g_message(" cat.expires = %lu", alert->cap.expires );
185 g_message(" cat.status = %s", alert->cap.status );
186 g_message(" cat.urgency = %s", alert->cap.urgency );
187 g_message(" cat.severity = %s", alert->cap.severity );
188 g_message(" cat.certainty = %s", alert->cap.certainty);
189 g_message(" cat.area_desc = %s", alert->cap.area_desc);
190 g_message(" cat.fips6 = %s", alert->cap.fips6 );
191 g_message(" cat.vtec = %p", alert->cap.vtec );
193 g_list_foreach(alerts, (GFunc)alert_free, NULL);
201 int int_compare(int a, int b)
203 return (a < b) ? -1 :
210 g_get_current_time(&tv);
211 return (gdouble)tv.tv_sec + (gdouble)tv.tv_usec/G_USEC_PER_SEC;
214 gdouble fips_area(gdouble *a, gdouble *b, gdouble *c)
217 crossd3(a, b, c, cross);
218 return lengthd(cross)/2;
221 gdouble fips_remove_one(GList **poly, gdouble thresh)
223 gdouble min_area = thresh;
224 GList *min_list = NULL;
225 if (g_list_length(*poly) <= 4)
227 for (GList *prev = *poly; prev->next->next; prev = prev->next) {
228 GList *cur = prev->next;
229 GList *next = prev->next->next;
230 gdouble area = fips_area(prev->data, cur->data, next->data);
231 if (area < min_area) {
237 *poly = g_list_delete_link(*poly, min_list);
244 gpointer fips_simplify(gdouble (*coords)[3], gdouble thresh, gint key)
248 /* Add points to poly list */
249 for (int i = 0; coords[i][0]; i++)
250 poly = g_list_prepend(poly, coords[i]);
251 int before = g_list_length(poly);
253 /* Simplify poly list */
254 //fprintf(stderr, "GritsPluginAlert: remove_one ");
256 while ((area = fips_remove_one(&poly, thresh)) > 0) {
257 //fprintf(stderr, " %f", area);
259 //fprintf(stderr, "\n");
260 int after = g_list_length(poly);
262 /* Copy points back */
263 gdouble (*simp)[3] = (gpointer)g_new0(gdouble, 3*(after+1));
265 for (int i = 0; i < after; i++) {
266 gdouble *coord = cur->data;
267 simp[i][0] = coord[0];
268 simp[i][1] = coord[1];
269 simp[i][2] = coord[2];
275 g_debug("GritsPluginAlert: fips_simplify %d: %d -> %d",
282 void fips_print(FILE *fd, gchar *fips, gchar *name, gchar *state,
283 gdouble (**points)[3])
285 fprintf(fd, "%s\t%s\t%s", fips, name, state);
286 for (int i = 0; points[i]; i++) {
288 for (int j = 0; points[i][j][0]; j++) {
289 //fwrite(points[i][j], sizeof(double), 3, fd);
291 xyz2ll(points[i][j][0], points[i][j][1],
292 points[i][j][2], &lat, &lon);
293 fprintf(fd, "%.7lf,%.7lf ", lat, lon);
299 GTree *fips_parse(gchar *text)
301 g_debug("GritsPluginAlert: fips_parse");
302 //FILE *fd = fopen("/tmp/fips_polys_simp.txt", "w+");
304 GTree *tree = g_tree_new((GCompareFunc)int_compare);
305 gchar **lines = g_strsplit(text, "\n", -1);
306 for (gint li = 0; lines[li]; li++) {
307 /* Split and count parts */
308 gchar **sparts = g_strsplit(lines[li], "\t", -1);
309 int nparts = g_strv_length(sparts);
315 GritsPoint center = {0,0,0};
316 gdouble (**polys)[3] = (gpointer)g_new0(double*, nparts-3+1);
317 for (int pi = 3; pi < nparts; pi++) {
318 /* Split and count coordinates */
319 gchar **scoords = g_strsplit(sparts[pi], " ", -1);
320 int ncoords = g_strv_length(scoords);
322 /* Create binary coords */
323 gdouble (*coords)[3] = (gpointer)g_new0(gdouble, 3*(ncoords+1));
324 for (int ci = 0; ci < ncoords; ci++) {
326 //sscanf(scoords[ci], "%lf,%lf", &lon, &lat);
327 sscanf(scoords[ci], "%lf,%lf", &lat, &lon);
339 /* Simplify coords/contour */
340 //coords = fips_simplify(coords, 1000*1000*4, li);
342 /* Insert coords into poly array */
343 polys[pi-3] = coords;
347 /* print simplified polys */
348 //fips_print(fd, sparts[0], sparts[1], sparts[2], polys);
350 /* Create GritsPoly */
351 GritsPoly *poly = grits_poly_new(polys);
352 GRITS_OBJECT(poly)->center = center;
353 GRITS_OBJECT(poly)->skip |= GRITS_SKIP_CENTER;
354 GRITS_OBJECT(poly)->skip |= GRITS_SKIP_STATE;
356 /* Insert polys into the tree */
357 gint id = g_ascii_strtoll(sparts[0], NULL, 10);
358 g_tree_insert(tree, (gpointer)id, poly);
367 /********************
369 ********************/
383 /* Update counties */
384 static gboolean _hide_county(gchar *_, GritsObject *county)
386 g_object_set_data(G_OBJECT(county), "info", NULL);
387 //county->hidden = TRUE;
390 static void _update_counties(GritsPluginAlert *alert)
392 /* Setup counties based on alerts:
393 * foreach alert in alerts:
394 * info = infotable.get(alert.type)
395 * foreach fips in alert.fipses:
396 * county = counties.get(fips)
397 * if info is higher priority
398 * county.color = info.color
400 g_debug("GritsPluginAlert: _update_counties");
401 g_tree_foreach(alert->counties, (GTraverseFunc)_hide_county, NULL);
402 for (GList *cur = alert->alerts; cur; cur = cur->next) {
403 AWeatherAlert *msg = cur->data;
405 /* Find alert info */
406 AlertInfo *info = alert_info_find(msg->title);
408 g_warning("GritsPluginAlert: unknown warning - %s", msg->title);
414 /* Set color for each county in alert */
415 gchar **fipses = g_strsplit(msg->cap.fips6, " ", -1);
416 for (int i = 0; fipses[i]; i++) {
417 gint fips = g_ascii_strtoll(fipses[i], NULL, 10);
418 GritsPoly *county = g_tree_lookup(alert->counties, (gpointer)fips);
421 AlertInfo *old = g_object_get_data(G_OBJECT(county), "info");
422 if (old != NULL && old < info)
424 g_object_set_data(G_OBJECT(county), "info", info);
425 county->color[0] = (float)info->color[0] / 256;
426 county->color[1] = (float)info->color[1] / 256;
427 county->color[2] = (float)info->color[2] / 256;
428 county->color[3] = 0.25;
429 GRITS_OBJECT(county)->hidden = FALSE;
433 gtk_widget_queue_draw(GTK_WIDGET(alert->viewer));
437 static void _alert_click(GtkRadioButton *button, gpointer alert)
439 g_debug("GritsPluginAlert: _alert_click");
440 AlertInfo *info = g_object_get_data(G_OBJECT(button), "info");
441 info->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
442 _update_counties(alert);
445 static GtkWidget *_make_button(AlertInfo *info)
447 g_debug("GritsPluginAlert: _make_button - %s", info->title);
448 GdkColor black = {0, 0, 0, 0};
449 GdkColor color = {0, info->color[0]<<8, info->color[1]<<8, info->color[2]<<8};
452 g_snprintf(text, sizeof(text), "<b>%.5s</b>", info->title);
454 GtkWidget *button = gtk_toggle_button_new();
455 GtkWidget *align = gtk_alignment_new(0.5, 0.5, 1, 1);
456 GtkWidget *cbox = gtk_event_box_new();
457 GtkWidget *label = gtk_label_new(text);
458 for (int state = 0; state < GTK_STATE_INSENSITIVE; state++) {
459 gtk_widget_modify_fg(label, state, &black);
460 gtk_widget_modify_bg(cbox, state, &color);
462 g_object_set_data(G_OBJECT(button), "info", info);
463 gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
464 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 2, 2, 4, 4);
465 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), info->enabled);
466 gtk_widget_set_tooltip_text(GTK_WIDGET(button), info->title);
467 gtk_container_add(GTK_CONTAINER(cbox), label);
468 gtk_container_add(GTK_CONTAINER(align), cbox);
469 gtk_container_add(GTK_CONTAINER(button), align);
473 static void _update_buttons(GritsPluginAlert *alert)
475 g_debug("GritsPluginAlert: _update_buttons");
476 /* Delete old buttons */
477 GList *frames = gtk_container_get_children(GTK_CONTAINER(alert->config));
478 for (GList *frame = frames; frame; frame = frame->next) {
479 GtkWidget *table = gtk_bin_get_child(GTK_BIN(frame->data));
480 GList *btns = gtk_container_get_children(GTK_CONTAINER(table));
481 g_list_foreach(btns, (GFunc)gtk_widget_destroy, NULL);
484 /* Add new buttons */
485 for (int i = 0; alert_info[i].title; i++) {
486 if (!alert_info[i].current)
489 GtkWidget *table = g_object_get_data(G_OBJECT(alert->config),
490 alert_info[i].category);
491 GList *kids = gtk_container_get_children(GTK_CONTAINER(table));
492 int nkids = g_list_length(kids);
497 GtkWidget *button = _make_button(&alert_info[i]);
498 gtk_table_attach(GTK_TABLE(table), button, x, x+1, y, y+1,
499 GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
500 g_signal_connect(button, "clicked",
501 G_CALLBACK(_alert_click), alert);
506 static gboolean _add_county(gint key, GritsObject *poly, GritsViewer *viewer)
508 grits_viewer_add(viewer, poly, GRITS_LEVEL_WORLD+1, TRUE);
512 static GtkWidget *_make_config(void)
514 gchar *labels[] = {"Warnings", "Watches", "Advisories", "Other"};
515 gchar *keys[] = {"warning", "watch", "advisory", "other"};
516 gint cols[] = {3, 2, 2, 2};
517 GtkWidget *config = gtk_hbox_new(FALSE, 10);
518 for (int i = 0; i < G_N_ELEMENTS(labels); i++) {
519 GtkWidget *frame = gtk_frame_new(labels[i]);
520 GtkWidget *table = gtk_table_new(1, cols[i], TRUE);
521 gtk_container_add(GTK_CONTAINER(frame), table);
522 gtk_box_pack_start(GTK_BOX(config), frame, TRUE, TRUE, 0);
523 g_object_set_data(G_OBJECT(config), keys[i], table);
529 GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs)
531 g_debug("GritsPluginAlert: new");
532 GritsPluginAlert *alert = g_object_new(GRITS_TYPE_PLUGIN_ALERT, NULL);
533 g_tree_foreach(alert->counties, (GTraverseFunc)_add_county, viewer);
534 alert->viewer = viewer;
535 alert->prefs = prefs;
539 static GtkWidget *grits_plugin_alert_get_config(GritsPlugin *_alert)
541 GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(_alert);
542 return alert->config;
547 static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface);
548 G_DEFINE_TYPE_WITH_CODE(GritsPluginAlert, grits_plugin_alert, G_TYPE_OBJECT,
549 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
550 grits_plugin_alert_plugin_init));
551 static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface)
553 g_debug("GritsPluginAlert: plugin_init");
554 /* Add methods to the interface */
555 iface->get_config = grits_plugin_alert_get_config;
557 static void grits_plugin_alert_init(GritsPluginAlert *alert)
559 g_debug("GritsPluginAlert: class_init");
561 alert->config = _make_config();
564 gchar *text; gsize len;
565 const gchar *fips_file = "/scratch/aweather/data/fips_polys_simp.txt";
566 g_file_get_contents(fips_file, &text, &len, NULL);
567 alert->counties = fips_parse(text);
570 /* Load alerts (once for testing) */
571 const gchar *alert_file = "/scratch/aweather/src/plugins/alert4.xml";
572 g_file_get_contents(alert_file, &text, &len, NULL);
573 alert->alerts = alert_parse(text, len);
576 /* For now, set alert "current" at init */
577 for (GList *cur = alert->alerts; cur; cur = cur->next) {
578 AWeatherAlert *msg = cur->data;
579 AlertInfo *info = alert_info_find(msg->title);
580 if (info) info->current = TRUE;
583 /* For now, run updates */
584 _update_buttons(alert);
585 _update_counties(alert);
587 static void grits_plugin_alert_dispose(GObject *gobject)
589 g_debug("GritsPluginAlert: dispose");
590 GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
592 /* Drop references */
593 G_OBJECT_CLASS(grits_plugin_alert_parent_class)->dispose(gobject);
595 static void grits_plugin_alert_finalize(GObject *gobject)
597 g_debug("GritsPluginAlert: finalize");
598 GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
600 gtk_widget_destroy(alert->config);
603 g_tree_destroy(alert->counties);
605 /* Free alerts (once for testing) */
606 g_list_foreach(alert->alerts, (GFunc)alert_free, NULL);
607 g_list_free(alert->alerts);
609 G_OBJECT_CLASS(grits_plugin_alert_parent_class)->finalize(gobject);
611 static void grits_plugin_alert_class_init(GritsPluginAlertClass *klass)
613 g_debug("GritsPluginAlert: class_init");
614 GObjectClass *gobject_class = (GObjectClass*)klass;
615 gobject_class->dispose = grits_plugin_alert_dispose;
616 gobject_class->finalize = grits_plugin_alert_finalize;