/* * 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; }