]> Pileus Git - aweather/blob - src/plugins/level2.c
d5566e1749ad8bd7717d13ce604c8126e2908232
[aweather] / src / plugins / level2.c
1 /*
2  * Copyright (C) 2009-2011 Andy Spencer <andy753421@gmail.com>
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #include <config.h>
19 #include <math.h>
20 #include <glib/gstdio.h>
21 #include <grits.h>
22 #include <rsl.h>
23
24 #include "level2.h"
25
26 #define ISO_MIN 30
27 #define ISO_MAX 80
28
29 /**************************
30  * Data loading functions *
31  **************************/
32 /* Convert a sweep to an 2d array of data points */
33 static void _bscan_sweep(Sweep *sweep, AWeatherColormap *colormap,
34                 guint8 **data, int *width, int *height)
35 {
36         g_debug("AWeatherLevel2: _bscan_sweep - %p, %p, %p",
37                         sweep, colormap, data);
38         /* Calculate max number of bins */
39         int max_bins = 0;
40         for (int i = 0; i < sweep->h.nrays; i++)
41                 max_bins = MAX(max_bins, sweep->ray[i]->h.nbins);
42
43         /* Allocate buffer using max number of bins for each ray */
44         guint8 *buf = g_malloc0(sweep->h.nrays * max_bins * 4);
45
46         /* Fill the data */
47         for (int ri = 0; ri < sweep->h.nrays; ri++) {
48                 Ray *ray  = sweep->ray[ri];
49                 for (int bi = 0; bi < ray->h.nbins; bi++) {
50                         guint  buf_i = (ri*max_bins+bi)*4;
51                         float  value = ray->h.f(ray->range[bi]);
52
53                         /* Check for bad values */
54                         if (value == BADVAL     || value == RFVAL      || value == APFLAG ||
55                             value == NOTFOUND_H || value == NOTFOUND_V || value == NOECHO) {
56                                 buf[buf_i+3] = 0x00; // transparent
57                                 continue;
58                         }
59
60                         /* Copy color to buffer */
61                         guint8 *data = colormap_get(colormap, value);
62                         buf[buf_i+0] = data[0];
63                         buf[buf_i+1] = data[1];
64                         buf[buf_i+2] = data[2];
65                         buf[buf_i+3] = data[3]*0.75; // TESTING
66                 }
67         }
68
69         /* set output */
70         *width  = max_bins;
71         *height = sweep->h.nrays;
72         *data   = buf;
73 }
74
75 /* Load a sweep into an OpenGL texture */
76 static void _load_sweep_gl(AWeatherLevel2 *level2)
77 {
78         g_debug("AWeatherLevel2: _load_sweep_gl");
79         guint8 *data;
80         gint width, height;
81         _bscan_sweep(level2->sweep, level2->sweep_colors, &data, &width, &height);
82         gint tex_width  = pow(2, ceil(log(width )/log(2)));
83         gint tex_height = pow(2, ceil(log(height)/log(2)));
84         level2->sweep_coords[0] = (double)width  / tex_width;
85         level2->sweep_coords[1] = (double)height / tex_height;
86
87         if (!level2->sweep_tex)
88                  glGenTextures(1, &level2->sweep_tex);
89         glBindTexture(GL_TEXTURE_2D, level2->sweep_tex);
90         glPixelStorei(GL_PACK_ALIGNMENT, 1);
91         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
92         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0,
93                         GL_RGBA, GL_UNSIGNED_BYTE, NULL);
94         glTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, width,height,
95                         GL_RGBA, GL_UNSIGNED_BYTE, data);
96         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
97         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
98
99         g_free(data);
100 }
101
102 /* Decompress a radar file using wsr88dec */
103 static gboolean _decompress_radar(const gchar *file, const gchar *raw)
104 {
105         g_debug("AWeatherLevel2: _decompress_radar - \n\t%s\n\t%s", file, raw);
106         char *argv[] = {"wsr88ddec", (gchar*)file, (gchar*)raw, NULL};
107         gint status;
108         GError *error = NULL;
109         g_spawn_sync(
110                 NULL,    // const gchar *working_directory
111                 argv,    // gchar **argv
112                 NULL,    // gchar **envp
113                 G_SPAWN_SEARCH_PATH, // GSpawnFlags flags
114                 NULL,    // GSpawnChildSetupFunc child_setup
115                 NULL,    // gpointer user_data
116                 NULL,    // gchar *standard_output
117                 NULL,    // gchar *standard_output
118                 &status, // gint *exit_status
119                 &error); // GError **error
120         if (error) {
121                 g_warning("AWeatherLevel2: _decompress_radar - %s", error->message);
122                 g_error_free(error);
123                 return FALSE;
124         }
125         if (status != 0) {
126                 gchar *msg = g_strdup_printf("wsr88ddec exited with status %d", status);
127                 g_warning("AWeatherLevel2: _decompress_radar - %s", msg);
128                 g_free(msg);
129                 return FALSE;
130         }
131         return TRUE;
132 }
133
134 /* Load the radar into a Grits Volume */
135 static void _cart_to_sphere(VolCoord *out, VolCoord *in)
136 {
137         gdouble angle = in->x;
138         gdouble dist  = in->y;
139         gdouble tilt  = in->z;
140         gdouble lx    = sin(angle);
141         gdouble ly    = cos(angle);
142         gdouble lz    = sin(tilt);
143         //out->x = (ly*dist)/20000;
144         //out->y = (lz*dist)/10000-0.5;
145         //out->z = (lx*dist)/20000-1.5;
146         out->x = (lx*dist);
147         out->y = (ly*dist);
148         out->z = (lz*dist);
149 }
150
151 static VolGrid *_load_grid(Volume *vol)
152 {
153         g_debug("AWeatherLevel2: _load_grid");
154
155         Sweep *sweep   = vol->sweep[0];
156         Ray   *ray     = sweep->ray[0];
157         gint nsweeps   = vol->h.nsweeps;
158         gint nrays     = sweep->h.nrays/(1/sweep->h.beam_width)+1;
159         gint nbins     = ray->h.nbins  /(1000/ray->h.gate_size);
160         nbins = MIN(nbins, 150);
161
162         VolGrid  *grid = vol_grid_new(nrays, nbins, nsweeps);
163
164         gint rs, bs, val;
165         gint si=0, ri=0, bi=0;
166         for (si = 0; si < nsweeps; si++) {
167                 sweep = vol->sweep[si];
168                 rs    = 1.0/sweep->h.beam_width;
169         for (ri = 0; ri < nrays; ri++) {
170                 /* TODO: missing rays, pick ri based on azmith */
171                 ray   = sweep->ray[(ri*rs) % sweep->h.nrays];
172                 bs    = 1000/ray->h.gate_size;
173         for (bi = 0; bi < nbins; bi++) {
174                 if (bi*bs >= ray->h.nbins)
175                         break;
176                 val   = ray->h.f(ray->range[bi*bs]);
177                 if (val == BADVAL     || val == RFVAL      ||
178                     val == APFLAG     || val == NOECHO     ||
179                     val == NOTFOUND_H || val == NOTFOUND_V ||
180                     val > 80)
181                         val = 0;
182                 VolPoint *point = vol_grid_get(grid, ri, bi, si);
183                 point->value = val;
184                 point->c.x = deg2rad(ray->h.azimuth);
185                 point->c.y = bi*bs*ray->h.gate_size + ray->h.range_bin1;
186                 point->c.z = deg2rad(ray->h.elev);
187         } } }
188
189         for (si = 0; si < nsweeps; si++)
190         for (ri = 0; ri < nrays; ri++)
191         for (bi = 0; bi < nbins; bi++) {
192                 VolPoint *point = vol_grid_get(grid, ri, bi, si);
193                 if (point->c.y == 0)
194                         point->value = nan("");
195                 else
196                         _cart_to_sphere(&point->c, &point->c);
197         }
198         return grid;
199 }
200
201
202 /*********************
203  * Drawing functions *
204  *********************/
205 void aweather_level2_draw(GritsObject *_level2, GritsOpenGL *opengl)
206 {
207         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
208         if (!level2->sweep || !level2->sweep_tex)
209                 return;
210
211         /* Draw wsr88d */
212         Sweep *sweep = level2->sweep;
213         //glDisable(GL_ALPHA_TEST);
214         glDisable(GL_CULL_FACE);
215         glDisable(GL_LIGHTING);
216         glEnable(GL_TEXTURE_2D);
217         glEnable(GL_POLYGON_OFFSET_FILL);
218         glPolygonOffset(1.0, -2.0);
219         glColor4f(1,1,1,1);
220
221         /* Draw the rays */
222         gdouble xscale = level2->sweep_coords[0];
223         gdouble yscale = level2->sweep_coords[1];
224         glBindTexture(GL_TEXTURE_2D, level2->sweep_tex);
225         glBegin(GL_TRIANGLE_STRIP);
226         for (int ri = 0; ri <= sweep->h.nrays; ri++) {
227                 Ray  *ray = NULL;
228                 double angle = 0;
229                 if (ri < sweep->h.nrays) {
230                         ray = sweep->ray[ri];
231                         angle = deg2rad(ray->h.azimuth - ((double)ray->h.beam_width/2.));
232                 } else {
233                         /* Do the right side of the last sweep */
234                         ray = sweep->ray[ri-1];
235                         angle = deg2rad(ray->h.azimuth + ((double)ray->h.beam_width/2.));
236                 }
237
238                 double lx = sin(angle);
239                 double ly = cos(angle);
240
241                 double near_dist = ray->h.range_bin1 - ((double)ray->h.gate_size/2.);
242                 double far_dist  = near_dist + (double)ray->h.nbins*ray->h.gate_size;
243
244                 /* (find middle of bin) / scale for opengl */
245                 // near left
246                 glTexCoord2f(0.0, ((double)ri/sweep->h.nrays)*yscale);
247                 glVertex3f(lx*near_dist, ly*near_dist, 2.0);
248
249                 // far  left
250                 // todo: correct range-height function
251                 double height = sin(deg2rad(ray->h.elev)) * far_dist;
252                 glTexCoord2f(xscale, ((double)ri/sweep->h.nrays)*yscale);
253                 glVertex3f(lx*far_dist,  ly*far_dist, height);
254         }
255         glEnd();
256         //g_print("ri=%d, nr=%d, bw=%f\n", _ri, sweep->h.nrays, sweep->h.beam_width);
257
258         /* Texture debug */
259         //glBegin(GL_QUADS);
260         //glTexCoord2d( 0.,  0.); glVertex3f(-500.,   0., 0.); // bot left
261         //glTexCoord2d( 0.,  1.); glVertex3f(-500., 500., 0.); // top left
262         //glTexCoord2d( 1.,  1.); glVertex3f( 0.,   500., 3.); // top right
263         //glTexCoord2d( 1.,  0.); glVertex3f( 0.,     0., 3.); // bot right
264         //glEnd();
265 }
266
267 void aweather_level2_hide(GritsObject *_level2, gboolean hidden)
268 {
269         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
270         if (level2->volume)
271                 grits_object_hide(GRITS_OBJECT(level2->volume), hidden);
272 }
273
274
275 /***********
276  * Methods *
277  ***********/
278 static gboolean _set_sweep_cb(gpointer _level2)
279 {
280         g_debug("AWeatherLevel2: _set_sweep_cb");
281         AWeatherLevel2 *level2 = _level2;
282         _load_sweep_gl(level2);
283         grits_object_queue_draw(_level2);
284         g_object_unref(level2);
285         return FALSE;
286 }
287 void aweather_level2_set_sweep(AWeatherLevel2 *level2,
288                 int type, float elev)
289 {
290         g_debug("AWeatherLevel2: set_sweep - %d %f", type, elev);
291
292         /* Find sweep */
293         Volume *volume = RSL_get_volume(level2->radar, type);
294         if (!volume) return;
295         level2->sweep = RSL_get_closest_sweep(volume, elev, 90);
296         if (!level2->sweep) return;
297
298         /* Find colormap */
299         level2->sweep_colors = NULL;
300         for (int i = 0; level2->colormap[i].file; i++)
301                 if (level2->colormap[i].type == type)
302                         level2->sweep_colors = &level2->colormap[i];
303         if (!level2->sweep_colors) {
304                 g_warning("AWeatherLevel2: set_sweep - missing colormap[%d]", type);
305                 level2->sweep_colors = &level2->colormap[0];
306         }
307
308         /* Load data */
309         g_object_ref(level2);
310         g_idle_add(_set_sweep_cb, level2);
311 }
312
313 void aweather_level2_set_iso(AWeatherLevel2 *level2, gfloat level)
314 {
315         g_debug("AWeatherLevel2: set_iso - %f", level);
316
317         if (!level2->volume) {
318                 g_debug("AWeatherLevel2: set_iso - creating new volume");
319                 Volume      *rvol = RSL_get_volume(level2->radar, DZ_INDEX);
320                 VolGrid     *grid = _load_grid(rvol);
321                 GritsVolume *vol  = grits_volume_new(grid);
322                 vol->proj = GRITS_VOLUME_CARTESIAN;
323                 vol->disp = GRITS_VOLUME_SURFACE;
324                 GRITS_OBJECT(vol)->center = GRITS_OBJECT(level2)->center;
325                 grits_viewer_add(GRITS_OBJECT(level2)->viewer,
326                                 GRITS_OBJECT(vol), GRITS_LEVEL_WORLD+5, TRUE);
327                 level2->volume = vol;
328         }
329         if (ISO_MIN < level && level < ISO_MAX) {
330                 guint8 *data = colormap_get(&level2->colormap[0], level);
331                 level2->volume->color[0] = data[0];
332                 level2->volume->color[1] = data[1];
333                 level2->volume->color[2] = data[2];
334                 level2->volume->color[3] = data[3];
335                 grits_volume_set_level(level2->volume, level);
336                 grits_object_hide(GRITS_OBJECT(level2->volume), FALSE);
337         } else {
338                 grits_object_hide(GRITS_OBJECT(level2->volume), TRUE);
339         }
340 }
341
342 AWeatherLevel2 *aweather_level2_new(Radar *radar, AWeatherColormap *colormap)
343 {
344         g_debug("AWeatherLevel2: new - %s", radar->h.radar_name);
345         RSL_sort_radar(radar);
346         AWeatherLevel2 *level2 = g_object_new(AWEATHER_TYPE_LEVEL2, NULL);
347         level2->radar    = radar;
348         level2->colormap = colormap;
349         aweather_level2_set_sweep(level2, DZ_INDEX, 0);
350
351         GritsPoint center;
352         Radar_header *h = &radar->h;
353         center.lat  = (double)h->latd + (double)h->latm/60 + (double)h->lats/(60*60);
354         center.lon  = (double)h->lond + (double)h->lonm/60 + (double)h->lons/(60*60);
355         center.elev = h->height;
356         GRITS_OBJECT(level2)->center = center;
357         return level2;
358 }
359
360 AWeatherLevel2 *aweather_level2_new_from_file(const gchar *file, const gchar *site,
361                 AWeatherColormap *colormap)
362 {
363         g_debug("AWeatherLevel2: new_from_file %s %s", site, file);
364
365         /* Decompress radar */
366         gchar *raw = g_strconcat(file, ".raw", NULL);
367         if (g_file_test(raw, G_FILE_TEST_EXISTS)) {
368                 struct stat files, raws;
369                 g_stat(file, &files);
370                 g_stat(raw,  &raws);
371                 if (files.st_mtime > raws.st_mtime)
372                         if (!_decompress_radar(file, raw))
373                                 return NULL;
374         } else {
375                 if (!_decompress_radar(file, raw))
376                         return NULL;
377         }
378
379         /* Load the radar file */
380         RSL_read_these_sweeps("all", NULL);
381         g_debug("AWeatherLevel2: rsl read start");
382         Radar *radar = RSL_wsr88d_to_radar(raw, (gchar*)site);
383         g_debug("AWeatherLevel2: rsl read done");
384         g_free(raw);
385         if (!radar)
386                 return NULL;
387
388         return aweather_level2_new(radar, colormaps);
389 }
390
391 static void _on_sweep_clicked(GtkRadioButton *button, gpointer _level2)
392 {
393         AWeatherLevel2 *level2 = _level2;
394         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
395                 gint type = (glong)g_object_get_data(G_OBJECT(button), "type");
396                 gint elev = (glong)g_object_get_data(G_OBJECT(button), "elev");
397                 aweather_level2_set_sweep(level2, type, (float)elev/100);
398                 //level2->colormap = level2->sweep_colors;
399         }
400 }
401
402 static void _on_iso_changed(GtkRange *range, gpointer _level2)
403 {
404         AWeatherLevel2 *level2 = _level2;
405         gfloat level = gtk_range_get_value(range);
406         aweather_level2_set_iso(level2, level);
407 }
408
409 GtkWidget *aweather_level2_get_config(AWeatherLevel2 *level2)
410 {
411         Radar *radar = level2->radar;
412         g_debug("AWeatherLevel2: get_config - %p, %p", level2, radar);
413         /* Clear existing items */
414         gfloat elev;
415         guint rows = 1, cols = 1, cur_cols;
416         gchar row_label_str[64], col_label_str[64], button_str[64];
417         GtkWidget *row_label, *col_label, *button = NULL, *elev_box = NULL;
418         GtkWidget *table = gtk_table_new(rows, cols, FALSE);
419
420         /* Add date */
421         gchar *date_str = g_strdup_printf("<b><i>%04d-%02d-%02d %02d:%02d</i></b>",
422                         radar->h.year, radar->h.month, radar->h.day,
423                         radar->h.hour, radar->h.minute);
424         GtkWidget *date_label = gtk_label_new(date_str);
425         gtk_label_set_use_markup(GTK_LABEL(date_label), TRUE);
426         gtk_table_attach(GTK_TABLE(table), date_label,
427                         0,1, 0,1, GTK_FILL,GTK_FILL, 5,0);
428         g_free(date_str);
429
430         /* Add sweeps */
431         for (guint vi = 0; vi < radar->h.nvolumes; vi++) {
432                 Volume *vol = radar->v[vi];
433                 if (vol == NULL) continue;
434                 rows++; cols = 1; elev = 0;
435
436                 /* Row label */
437                 g_snprintf(row_label_str, 64, "<b>%s:</b>", vol->h.type_str);
438                 row_label = gtk_label_new(row_label_str);
439                 gtk_label_set_use_markup(GTK_LABEL(row_label), TRUE);
440                 gtk_misc_set_alignment(GTK_MISC(row_label), 1, 0.5);
441                 gtk_table_attach(GTK_TABLE(table), row_label,
442                                 0,1, rows-1,rows, GTK_FILL,GTK_FILL, 5,0);
443
444                 for (guint si = 0; si < vol->h.nsweeps; si++) {
445                         Sweep *sweep = vol->sweep[si];
446                         if (sweep == NULL || sweep->h.elev == 0) continue;
447                         if (sweep->h.elev != elev) {
448                                 cols++;
449                                 elev = sweep->h.elev;
450
451                                 /* Column label */
452                                 g_object_get(table, "n-columns", &cur_cols, NULL);
453                                 if (cols >  cur_cols) {
454                                         g_snprintf(col_label_str, 64, "<b>%.2f°</b>", elev);
455                                         col_label = gtk_label_new(col_label_str);
456                                         gtk_label_set_use_markup(GTK_LABEL(col_label), TRUE);
457                                         gtk_widget_set_size_request(col_label, 50, -1);
458                                         gtk_table_attach(GTK_TABLE(table), col_label,
459                                                         cols-1,cols, 0,1, GTK_FILL,GTK_FILL, 0,0);
460                                 }
461
462                                 elev_box = gtk_hbox_new(TRUE, 0);
463                                 gtk_table_attach(GTK_TABLE(table), elev_box,
464                                                 cols-1,cols, rows-1,rows, GTK_FILL,GTK_FILL, 0,0);
465                         }
466
467
468                         /* Button */
469                         g_snprintf(button_str, 64, "%3.2f", elev);
470                         button = gtk_radio_button_new_with_label_from_widget(
471                                         GTK_RADIO_BUTTON(button), button_str);
472                         gtk_widget_set_size_request(button, -1, 26);
473                         //button = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(button));
474                         //gtk_widget_set_size_request(button, -1, 22);
475                         g_object_set(button, "draw-indicator", FALSE, NULL);
476                         gtk_box_pack_end(GTK_BOX(elev_box), button, TRUE, TRUE, 0);
477
478                         g_object_set_data(G_OBJECT(button), "level2", level2);
479                         g_object_set_data(G_OBJECT(button), "type", (gpointer)(guintptr)vi);
480                         g_object_set_data(G_OBJECT(button), "elev", (gpointer)(guintptr)(elev*100));
481                         g_signal_connect(button, "clicked", G_CALLBACK(_on_sweep_clicked), level2);
482                 }
483         }
484
485         /* Add Iso-surface volume */
486         g_object_get(table, "n-columns", &cols, NULL);
487         row_label = gtk_label_new("<b>Isosurface:</b>");
488         gtk_label_set_use_markup(GTK_LABEL(row_label), TRUE);
489         gtk_misc_set_alignment(GTK_MISC(row_label), 1, 0.5);
490         gtk_table_attach(GTK_TABLE(table), row_label,
491                         0,1, rows,rows+1, GTK_FILL,GTK_FILL, 5,0);
492         GtkWidget *scale = gtk_hscale_new_with_range(ISO_MIN, ISO_MAX, 0.5);
493         gtk_widget_set_size_request(scale, -1, 26);
494         gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_LEFT);
495         gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
496         gtk_range_set_value(GTK_RANGE(scale), ISO_MAX);
497         g_signal_connect(scale, "value-changed", G_CALLBACK(_on_iso_changed), level2);
498         gtk_table_attach(GTK_TABLE(table), scale,
499                         1,cols+1, rows,rows+1, GTK_FILL|GTK_EXPAND,GTK_FILL, 0,0);
500         /* Shove all the buttons to the left, but keep the slider expanded */
501         gtk_table_attach(GTK_TABLE(table), gtk_label_new(""),
502                         cols,cols+1, 0,1, GTK_FILL|GTK_EXPAND,GTK_FILL, 0,0);
503         return table;
504 }
505
506 /****************
507  * GObject code *
508  ****************/
509 G_DEFINE_TYPE(AWeatherLevel2, aweather_level2, GRITS_TYPE_OBJECT);
510 static void aweather_level2_init(AWeatherLevel2 *level2)
511 {
512 }
513 static void aweather_level2_dispose(GObject *_level2)
514 {
515         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
516         g_debug("AWeatherLevel2: dispose - %p", _level2);
517         if (level2->volume) {
518                 grits_viewer_remove(GRITS_OBJECT(level2->volume)->viewer,
519                                 GRITS_OBJECT(level2->volume));
520                 level2->volume = NULL;
521         }
522         G_OBJECT_CLASS(aweather_level2_parent_class)->dispose(_level2);
523 }
524 static void aweather_level2_finalize(GObject *_level2)
525 {
526         AWeatherLevel2 *level2 = AWEATHER_LEVEL2(_level2);
527         g_debug("AWeatherLevel2: finalize - %p", _level2);
528         RSL_free_radar(level2->radar);
529         if (level2->sweep_tex)
530                 glDeleteTextures(1, &level2->sweep_tex);
531         G_OBJECT_CLASS(aweather_level2_parent_class)->finalize(_level2);
532 }
533 static void aweather_level2_class_init(AWeatherLevel2Class *klass)
534 {
535         G_OBJECT_CLASS(klass)->dispose  = aweather_level2_dispose;
536         G_OBJECT_CLASS(klass)->finalize = aweather_level2_finalize;
537         GRITS_OBJECT_CLASS(klass)->draw = aweather_level2_draw;
538         GRITS_OBJECT_CLASS(klass)->hide = aweather_level2_hide;
539 }