From d01fb11de5bcf91224348bf41abb6788d0af2545 Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Wed, 2 Sep 2009 06:06:59 +0000 Subject: [PATCH] Lots of work on libGIS - Added a `virtual globe' - Using Blue Marble NG for ground textures - Using SRTM30 Plus for elevation data --- README | 4 + TODO | 18 +- configure.ac | 2 +- data/defaults.ini | 2 +- data/main.ui | 53 ++- docs/api/Makefile.am | 2 +- src/Makefile.am | 4 +- src/aweather-gui.c | 242 +++++++------- src/aweather-location.c | 458 +++++++++++++------------- src/aweather-location.h | 13 +- src/examples/mkfile | 6 + src/examples/tex.c | 143 +++++++++ src/examples/tex_png/tex.png | Bin 0 -> 187 bytes src/examples/tex_png/texl.png | Bin 0 -> 182 bytes src/examples/tex_png/texls.png | Bin 0 -> 175 bytes src/examples/tex_png/texr.png | Bin 0 -> 178 bytes src/examples/tex_png/texrs.png | Bin 0 -> 174 bytes src/gis/.vimrc | 2 + src/gis/Makefile.am | 51 ++- src/gis/gis-opengl.c | 543 +++++++++++++++++++++++-------- src/gis/gis-opengl.h | 33 +- src/gis/gis-plugin.c | 8 +- src/gis/gis-prefs.c | 119 +++---- src/gis/gis-view.c | 276 ++++++++-------- src/gis/gis-view.h | 6 +- src/gis/gis-world.c | 109 +++++-- src/gis/gis-world.h | 74 ++++- src/gis/gis_test.c | 72 +++++ src/gis/gpqueue.c | 567 +++++++++++++++++++++++++++++++++ src/gis/gpqueue.h | 61 ++++ src/gis/roam.c | 521 ++++++++++++++++++++++++++++++ src/gis/roam.h | 104 ++++++ src/gis/wms.c | 509 +++++++++++++++++++++++++++++ src/gis/wms.h | 117 +++++++ src/gis/wms_test.c | 70 ++++ src/plugins/example.c | 129 ++++---- src/plugins/radar.c | 157 ++++----- src/plugins/ridge.c | 94 +++--- 38 files changed, 3579 insertions(+), 990 deletions(-) create mode 100644 src/examples/mkfile create mode 100644 src/examples/tex.c create mode 100644 src/examples/tex_png/tex.png create mode 100644 src/examples/tex_png/texl.png create mode 100644 src/examples/tex_png/texls.png create mode 100644 src/examples/tex_png/texr.png create mode 100644 src/examples/tex_png/texrs.png create mode 100644 src/gis/.vimrc create mode 100644 src/gis/gis_test.c create mode 100644 src/gis/gpqueue.c create mode 100644 src/gis/gpqueue.h create mode 100644 src/gis/roam.c create mode 100644 src/gis/roam.h create mode 100644 src/gis/wms.c create mode 100644 src/gis/wms.h create mode 100644 src/gis/wms_test.c diff --git a/README b/README index 0501bb1..8e2fb59 100644 --- a/README +++ b/README @@ -3,6 +3,10 @@ Advanced Weather This may eventually become free software program/library/something used to access/process/visualize weather related data. +Data: +Ground (WMS) - http://www.nasa.network.com/wms +Elevation (WMS) - http://www.nasa.network.com/elev + See also: WDSS II - http://www.wdssii.org/ AWIPS - http://www.nws.noaa.gov/ops2/ops24/awips.htm diff --git a/TODO b/TODO index 1ed0735..a77a5d6 100644 --- a/TODO +++ b/TODO @@ -3,25 +3,24 @@ Road plan 0.x - Misc * Fix all memory leaks * Pre-load textures and polys in OpenGL - * Configuration file - * Default site - * Keybindings? 0.x - Docs * Man page with key commands * UI docs?? * GTK docs +0.x - GIS + * States/cities/roads overlays + * NEXRAD LLE listings, click to center + 0.x - Volume scans * Display iso surfaces of volume scans -0.x - GIS - * Replace RIDGE overlays with GIS images - * Set up LLH coordinate system - * Generic Time/Location selection - (not dependent on level 2 listings) +0.x - Animation + * Loop though times 0.x - More data + * CONUS (composite) radar images * Derived from Level 2: * Composite reflectivity * Storm relative motion @@ -29,6 +28,3 @@ Road plan * Warning/watch boxes * Fronts * Air pressure - -0.x - Animation - * Loop though times diff --git a/configure.ac b/configure.ac index 583b00d..58a1103 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ # Init and options -AC_INIT([aweather], [0.2.1], [spenceal@rose-hulman.edu]) +AC_INIT([aweather], [0.3-p0], [spenceal@rose-hulman.edu]) AM_INIT_AUTOMAKE([-Wall -Werror -Wno-portability foreign]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/data/defaults.ini b/data/defaults.ini index 085cd4f..9c669b2 100644 --- a/data/defaults.ini +++ b/data/defaults.ini @@ -10,7 +10,7 @@ offline=false enabled=true [ridge] -enabled=true +enabled=false [radar] enabled=true diff --git a/data/main.ui b/data/main.ui index 3511db0..f37784b 100644 --- a/data/main.ui +++ b/data/main.ui @@ -44,15 +44,15 @@ True Work offline - offline True + offline True - prefs True + prefs True True @@ -65,8 +65,8 @@ True - quit True + quit True True @@ -86,8 +86,8 @@ True - zoomin True + zoomin True True @@ -95,8 +95,8 @@ True - zoomout True + zoomout True True @@ -109,8 +109,8 @@ True - refresh True + refresh True True @@ -130,8 +130,8 @@ True - about True + about True True @@ -153,8 +153,8 @@ True - zoomin True + zoomin False @@ -164,8 +164,8 @@ True - zoomout True + zoomout False @@ -184,8 +184,8 @@ True - offline True + offline toolbutton3 True @@ -197,8 +197,8 @@ True - refresh True + refresh True @@ -218,8 +218,8 @@ True - prefs True + prefs toolbutton1 True @@ -239,25 +239,19 @@ True True - + True True - - 600 - 550 + True - True - True - True - True - True - True - GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_STRUCTURE_MASK + placeholder +(it's buggy with blank placeholders) + center - True - False + False + True @@ -631,14 +625,13 @@ 5 About - AWeather - False center-on-parent dialog main_window False AWeather - 0.1 - Copyright ©2008-2008 Andy Spencer + (beta) + Copyright ©2008-2009 Andy Spencer A semi-advanced weather viewer http://lug.rose-hulman.edu/wiki/AWeather GNU GENERAL PUBLIC LICENSE @@ -912,9 +905,9 @@ The hypothetical commands `show w' and `show c' should show the appropriate part - - + + diff --git a/docs/api/Makefile.am b/docs/api/Makefile.am index ac63adc..bf6b877 100644 --- a/docs/api/Makefile.am +++ b/docs/api/Makefile.am @@ -1,7 +1,7 @@ AM_CPPFLAGS= $(RSL_CFLAGS) $(SOUP_CFLAGS) $(GLIB_CFLAGS) $(GTK_CFLAGS) GTKDOC_LIBS=$(RSL_LIBS) $(SOUP_LIBS) $(GLIB_LIBS) $(GTK_LIBS) \ $(top_srcdir)/src/aweather-aweather-*.o \ - $(top_srcdir)/src/gis/*.o \ + $(top_srcdir)/src/gis/libgis*.o \ $(top_srcdir)/src/plugins/*.o DOC_MODULE=aweather DOC_SOURCE_DIR=$(top_srcdir)/src/ diff --git a/src/Makefile.am b/src/Makefile.am index 9f317e6..d5e5fac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,8 +24,8 @@ gdb: all ddd: all LD_LIBRARY_PATH=gis/.libs ddd .libs/aweather -memcheck: - LD_LIBRARY_PATH=gis/.libs/ \ +memcheck: all + LD_LIBRARY_PATH=gis/.libs \ G_SLICE=always-malloc \ G_DEBUG=gc-friendly,resident-modules \ valgrind --leak-check=full \ diff --git a/src/aweather-gui.c b/src/aweather-gui.c index dcb2b2c..0c3204d 100644 --- a/src/aweather-gui.c +++ b/src/aweather-gui.c @@ -25,126 +25,6 @@ #include "aweather-gui.h" #include "aweather-location.h" -/* Needed prototpes */ -gboolean on_gui_key_press(GtkWidget *widget, GdkEventKey *event, AWeatherGui *self); -static void on_gis_refresh(GisWorld *world, gpointer _self); -static void on_gis_site_changed(GisView *view, char *site, gpointer _self); -static void site_setup(AWeatherGui *self); -static void time_setup(AWeatherGui *self); -static void prefs_setup(AWeatherGui *self); - -/**************** - * GObject code * - ****************/ -G_DEFINE_TYPE(AWeatherGui, aweather_gui, GTK_TYPE_WINDOW); -static void aweather_gui_init(AWeatherGui *self) -{ - g_debug("AWeatherGui: init"); - - /* Simple things */ - self->prefs = gis_prefs_new("aweather"); - self->plugins = gis_plugins_new(); - self->world = gis_world_new(); - self->view = gis_view_new(); - - /* Setup window */ - self->builder = gtk_builder_new(); - GError *error = NULL; - if (!gtk_builder_add_from_file(self->builder, DATADIR "/aweather/main.ui", &error)) - g_error("Failed to create gtk builder: %s", error->message); - gtk_widget_reparent(aweather_gui_get_widget(self, "body"), GTK_WIDGET(self)); - - /* GIS things */ - GtkWidget *drawing = aweather_gui_get_widget(self, "drawing"); - self->opengl = gis_opengl_new(self->world, self->view, GTK_DRAWING_AREA(drawing)); - self->opengl->plugins = self->plugins; - //gtk_widget_show_all(GTK_WIDGET(self)); - - /* Plugins */ - GtkTreeIter iter; - self->gtk_plugins = GTK_LIST_STORE(aweather_gui_get_object(self, "plugins")); - for (GList *cur = gis_plugins_available(); cur; cur = cur->next) { - gchar *name = cur->data; - gboolean enabled = gis_prefs_get_boolean_v(self->prefs, cur->data, "enabled"); - gtk_list_store_append(self->gtk_plugins, &iter); - gtk_list_store_set(self->gtk_plugins, &iter, 0, name, 1, enabled, -1); - if (enabled) - aweather_gui_attach_plugin(self, name); - } - - /* Misc, helpers */ - site_setup(self); - time_setup(self); - prefs_setup(self); - - /* Connect signals */ - gtk_builder_connect_signals(self->builder, self); - g_signal_connect(self, "key-press-event", - G_CALLBACK(on_gui_key_press), self); - g_signal_connect_swapped(self->world, "offline", - G_CALLBACK(gtk_toggle_action_set_active), - aweather_gui_get_object(self, "offline")); - - /* deprecated site stuff */ - g_signal_connect(self->view, "site-changed", G_CALLBACK(on_gis_site_changed), self); - g_signal_connect(self->world, "refresh", G_CALLBACK(on_gis_refresh), self); -} -static GObject *aweather_gui_constructor(GType gtype, guint n_properties, - GObjectConstructParam *properties) -{ - g_debug("aweather_gui: constructor"); - GObjectClass *parent_class = G_OBJECT_CLASS(aweather_gui_parent_class); - return parent_class->constructor(gtype, n_properties, properties); -} -static void aweather_gui_dispose(GObject *_self) -{ - g_debug("AWeatherGui: dispose"); - AWeatherGui *self = AWEATHER_GUI(_self); - if (self->builder) { - /* Reparent to avoid double unrefs */ - GtkWidget *body = aweather_gui_get_widget(self, "body"); - GtkWidget *window = aweather_gui_get_widget(self, "main_window"); - gtk_widget_reparent(body, window); - g_object_unref(self->builder); - self->builder = NULL; - } - if (self->world) { - g_object_unref(self->world); - self->world = NULL; - } - if (self->view) { - g_object_unref(self->view); - self->view = NULL; - } - if (self->opengl) { - g_object_unref(self->opengl); - self->opengl = NULL; - } - if (self->plugins) { - gis_plugins_free(self->plugins); - self->plugins = NULL; - } - if (self->prefs) { - g_object_unref(self->prefs); - self->prefs = NULL; - } - G_OBJECT_CLASS(aweather_gui_parent_class)->dispose(_self); -} -static void aweather_gui_finalize(GObject *_self) -{ - g_debug("AWeatherGui: finalize"); - G_OBJECT_CLASS(aweather_gui_parent_class)->finalize(_self); - gtk_main_quit(); - -} -static void aweather_gui_class_init(AWeatherGuiClass *klass) -{ - g_debug("AWeatherGui: class_init"); - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - gobject_class->constructor = aweather_gui_constructor; - gobject_class->dispose = aweather_gui_dispose; - gobject_class->finalize = aweather_gui_finalize; -} /************* * Callbacks * @@ -189,7 +69,6 @@ void on_refresh(GtkAction *action, AWeatherGui *self) gis_world_refresh(self->world); } - void on_plugin_toggled(GtkCellRendererToggle *cell, gchar *path_str, AWeatherGui *self) { GtkWidget *tview = aweather_gui_get_widget(self, "plugins_view"); @@ -254,6 +133,7 @@ static gboolean gtk_tree_model_find_string(GtkTreeModel *model, } return FALSE; } + static void update_time_widget(GisView *view, const char *time, AWeatherGui *self) { g_debug("AWeatherGui: update_time_widget - time=%s", time); @@ -270,6 +150,7 @@ static void update_time_widget(GisView *view, const char *time, AWeatherGui *sel gtk_tree_path_free(path); } } + static void update_site_widget(GisView *view, char *site, AWeatherGui *self) { g_debug("AWeatherGui: update_site_widget - site=%s", site); @@ -284,6 +165,7 @@ static void update_site_widget(GisView *view, char *site, AWeatherGui *self) G_CALLBACK(on_site_changed), self); } } + /* Prefs callbacks */ void on_offline(GtkToggleAction *action, AWeatherGui *self) { @@ -292,6 +174,7 @@ void on_offline(GtkToggleAction *action, AWeatherGui *self) gis_prefs_set_boolean(self->prefs, "gis/offline", value); gis_world_set_offline(self->world, value); } + void on_initial_site_changed(GtkComboBox *combo, AWeatherGui *self) { gchar *site; @@ -303,12 +186,14 @@ void on_initial_site_changed(GtkComboBox *combo, AWeatherGui *self) gis_prefs_set_string(self->prefs, "aweather/initial_site", site); g_free(site); } + void on_nexrad_url_changed(GtkEntry *entry, AWeatherGui *self) { const gchar *text = gtk_entry_get_text(entry); g_debug("AWeatherGui: on_nexrad_url_changed - url=%s", text); gis_prefs_set_string(self->prefs, "aweather/nexrad_url", text); } + int on_log_level_changed(GtkSpinButton *spinner, AWeatherGui *self) { gint value = gtk_spin_button_get_value_as_int(spinner); @@ -316,7 +201,7 @@ int on_log_level_changed(GtkSpinButton *spinner, AWeatherGui *self) gis_prefs_set_integer(self->prefs, "aweather/log_level", value); return TRUE; } -// plugins + /***************** * Setup helpers * @@ -483,6 +368,7 @@ static void on_gis_refresh(GisWorld *world, gpointer _self) update_times(self, self->view, site); } + /*********** * Methods * ***********/ @@ -555,3 +441,115 @@ void aweather_gui_deattach_plugin(AWeatherGui *self, const gchar *name) gis_plugins_unload(self->plugins, name); gis_opengl_redraw(self->opengl); } + + +/**************** + * GObject code * + ****************/ +G_DEFINE_TYPE(AWeatherGui, aweather_gui, GTK_TYPE_WINDOW); +static void aweather_gui_init(AWeatherGui *self) +{ + g_debug("AWeatherGui: init"); + + /* Simple things */ + self->prefs = gis_prefs_new("aweather"); + self->plugins = gis_plugins_new(); + self->world = gis_world_new(); + self->view = gis_view_new(); + self->opengl = gis_opengl_new(self->world, self->view, self->plugins); + + /* Setup window */ + self->builder = gtk_builder_new(); + GError *error = NULL; + if (!gtk_builder_add_from_file(self->builder, DATADIR "/aweather/main.ui", &error)) + g_error("Failed to create gtk builder: %s", error->message); + gtk_widget_reparent(aweather_gui_get_widget(self, "body"), GTK_WIDGET(self)); + GtkWidget *hpaned = aweather_gui_get_widget(self, "hpaned"); + gtk_widget_destroy(gtk_paned_get_child1(GTK_PANED(hpaned))); + gtk_paned_pack1(GTK_PANED(hpaned), GTK_WIDGET(self->opengl), TRUE, FALSE); + + /* Plugins */ + GtkTreeIter iter; + self->gtk_plugins = GTK_LIST_STORE(aweather_gui_get_object(self, "plugins")); + for (GList *cur = gis_plugins_available(); cur; cur = cur->next) { + gchar *name = cur->data; + gboolean enabled = gis_prefs_get_boolean_v(self->prefs, cur->data, "enabled"); + gtk_list_store_append(self->gtk_plugins, &iter); + gtk_list_store_set(self->gtk_plugins, &iter, 0, name, 1, enabled, -1); + if (enabled) + aweather_gui_attach_plugin(self, name); + } + + /* Misc, helpers */ + site_setup(self); + time_setup(self); + prefs_setup(self); + + /* Connect signals */ + gtk_builder_connect_signals(self->builder, self); + g_signal_connect(self, "key-press-event", + G_CALLBACK(on_gui_key_press), self); + g_signal_connect_swapped(self->world, "offline", + G_CALLBACK(gtk_toggle_action_set_active), + aweather_gui_get_object(self, "offline")); + + /* deprecated site stuff */ + g_signal_connect(self->view, "site-changed", G_CALLBACK(on_gis_site_changed), self); + g_signal_connect(self->world, "refresh", G_CALLBACK(on_gis_refresh), self); +} +static GObject *aweather_gui_constructor(GType gtype, guint n_properties, + GObjectConstructParam *properties) +{ + g_debug("aweather_gui: constructor"); + GObjectClass *parent_class = G_OBJECT_CLASS(aweather_gui_parent_class); + return parent_class->constructor(gtype, n_properties, properties); +} +static void aweather_gui_dispose(GObject *_self) +{ + g_debug("AWeatherGui: dispose"); + AWeatherGui *self = AWEATHER_GUI(_self); + if (self->builder) { + /* Reparent to avoid double unrefs */ + GtkWidget *body = aweather_gui_get_widget(self, "body"); + GtkWidget *window = aweather_gui_get_widget(self, "main_window"); + gtk_widget_reparent(body, window); + g_object_unref(self->builder); + self->builder = NULL; + } + if (self->world) { + g_object_unref(self->world); + self->world = NULL; + } + if (self->view) { + g_object_unref(self->view); + self->view = NULL; + } + if (self->opengl) { + g_object_unref(self->opengl); + self->opengl = NULL; + } + if (self->plugins) { + gis_plugins_free(self->plugins); + self->plugins = NULL; + } + if (self->prefs) { + g_object_unref(self->prefs); + self->prefs = NULL; + } + G_OBJECT_CLASS(aweather_gui_parent_class)->dispose(_self); +} +static void aweather_gui_finalize(GObject *_self) +{ + g_debug("AWeatherGui: finalize"); + G_OBJECT_CLASS(aweather_gui_parent_class)->finalize(_self); + gtk_main_quit(); + +} +static void aweather_gui_class_init(AWeatherGuiClass *klass) +{ + g_debug("AWeatherGui: class_init"); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->constructor = aweather_gui_constructor; + gobject_class->dispose = aweather_gui_dispose; + gobject_class->finalize = aweather_gui_finalize; +} diff --git a/src/aweather-location.c b/src/aweather-location.c index b893f39..c4952d0 100644 --- a/src/aweather-location.c +++ b/src/aweather-location.c @@ -21,233 +21,233 @@ #include "aweather-location.h" city_t cities[] = { - {LOCATION_STATE, NULL, "Alabama", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBMX", "Birmingham", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMXX", "E. Alabama", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEOX", "Fort Rucker", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMOB", "Mobile", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHTX", "Nrn. Alabama", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Alaska", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KABC", "Bethel", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KACG", "Biorka Is.", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAPD", "Fairbanks", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAHG", "Kenai", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAKC", "King Salmon", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAIH", "Middleton Is.", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAEC", "Nome", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAPD", "Pedro Dome", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KACG", "Sitka", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Arizona", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFSX", "Flagstaff", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KIWA", "Phoenix", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEMX", "Tucson", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KYUX", "Yuma", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Arkansas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLZK", "Little Rock", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSRX", "W. Ark./Ft. Smith", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "California", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBBX", "Beale AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEYX", "Edwards AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBHX", "Eureka", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHNX", "Hanford", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KVTX", "Los Angeles", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDAX", "Sacramento", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KNKX", "San Diego", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMUX", "San Francisco", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHNX", "San Joaquin Vly.", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSOX", "Santa Ana Mtns", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KVBX", "Vandenberg AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Colorado", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFTG", "Denver/Boulder", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGJX", "Grand Junction", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KPUX", "Pueblo", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Delaware", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDOX", "Dover AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Florida", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEVX", "Eglin AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KJAX", "Jacksonville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBYX", "Key West", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMLB", "Melbourne", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAMX", "Miami", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEVX", "NW Florida", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTLH", "Tallahassee", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTBW", "Tampa Bay Area", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Georgia", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFFC", "Atlanta", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KVAX", "Moody AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFFC", "Peachtree City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KJGX", "Robins AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Guam", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGUA", "Andersen AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Hawaii", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHKI", "Kauai", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHKM", "Kohala", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHMO", "Molokai", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHWA", "South Shore", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Idaho", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCBX", "Boise", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSFX", "Pocatello", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Illinois", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KILX", "Central IL", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLOT", "Chicago", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Indiana", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KVWX", "Evansville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KIND", "Indianapolis", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KIWX", "Nrn. Indiana", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Iowa", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDMX", "Des Moines", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDVN", "Quad Cities", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Kansas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDDC", "Dodge City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGLD", "Goodland", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTWX", "Topeka", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KICT", "Wichita", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Kentucky", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHPX", "Fort Cambell", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KJKL", "Jackson", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLVX", "Louisville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KPAH", "Paducah", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Louisiana", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KPOE", "Fort Polk", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLCH", "Lake Charles", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLIX", "New Orleans", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSHV", "Shreveport", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Maine", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCBW", "Caribou", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGYX", "Portland", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Maryland", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLWX", "Baltimore", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Massachusetts", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBOX", "Boston", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Michigan", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDTX", "Detroit", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAPX", "Gaylord", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGRR", "Grand Rapids", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMQT", "Marquette", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Minnesota", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDLH", "Duluth", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMPX", "Minneapolis", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Mississippi", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGWX", "Columbus AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDGX", "Jackson/Brandon", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Missouri", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEAX", "Kansas City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSGF", "Springfield", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLSX", "St. Louis", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Montana", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBLX", "Billings", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGGW", "Glasgow", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTFX", "Great Falls", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMSX", "Missoula", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Nebraska", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KUEX", "Hastings", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLNX", "North Platte", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KOAX", "Omaha", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Nevada", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLRX", "Elko", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KESX", "Las Vegas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRGX", "Reno", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "New Jersey", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDIX", "Mt. Holly", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "New Mexico", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KABX", "Albuquerque", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFDX", "Cannon AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHDX", "Holloman AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "New York", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KENX", "Albany", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBGM", "Binghamton", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBUF", "Buffalo", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTYX", "Montague", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KOKX", "New York City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KOKX", "Upton", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "North Carolina", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRAX", "Durham", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMHX", "Morehead City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRAX", "Raleigh", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLTX", "Wilmington", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "North Dakota", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBIS", "Bismarck", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMVX", "Grand Forks", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMBX", "Minot AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Ohio", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KILN", "Cincinnati", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCLE", "Cleveland", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KILN", "Dayton", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KILN", "Wilmington", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Oklahoma", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFDR", "Frederick", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KTLX", "Oklahoma City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KINX", "Tulsa", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KVNX", "Vance AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Oregon", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMAX", "Medford", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KPDT", "Pendleton", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRTX", "Portland", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Pennsylvania", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDIX", "Philadelphia", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KPBZ", "Pittsburgh", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCCX", "State College", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Puerto Rico", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KJUA", "Puerto Rico/V.I.", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "South Carolina", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCLX", "Charleston", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCAE", "Columbia", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGSP", "Greenville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGSP", "Spartanburg", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGSP", "Greer", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "South Dakota", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KABR", "Aberdeen", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KUDX", "Rapid City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFSD", "Sioux falls", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Tennessee", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMRX", "Knoxville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KNQA", "Memphis", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMRX", "Morristown", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KOHX", "Nashville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMRX", "Tri Cities", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Texas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAMA", "Amarillo", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEWX", "Austin", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KBRO", "Brownsville", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGRK", "Central Texas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCRP", "Corpus Christi", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFWS", "Dallas", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDYX", "Dyess AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEPZ", "El Paso", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFWS", "Fort Worth", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHGX", "Galveston", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KHGX", "Houston", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KDFX", "Laughlin AFB", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLBB", "Lubbock", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMAF", "Midland/Odessa", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KSJT", "San Angelo", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KEWX", "San Antonio", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Utah", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KICX", "Cedar City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMTX", "Salt Lake City", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Vermont", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCXX", "Burlington", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Virginia", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFCX", "Blacksburg", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAKQ", "Norfolk", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAKQ", "Richmond", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KFCX", "Roanoke", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLWX", "Sterling", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KAKQ", "Wakefield", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Washington", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KATX", "Seattle", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KOTX", "Spokane", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KATX", "Tacoma", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Washington DC", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KLWX", "Washington", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "West Virginia", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRLX", "Charleston", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Wisconsin", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KGRB", "Green Bay", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KARX", "La Crosse", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KMKX", "Milwaukee", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_STATE, NULL, "Wyoming", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KCYS", "Cheyenne", {0, 0, 0, 0, 0, 0, 0, 0}}, - {LOCATION_CITY, "KRIW", "Riverton", {0, 0, 0, 0, 0, 0, 0, 0}}, - {0, NULL, NULL, {0, 0, 0, 0, 0, 0, 0, 0}}, + {LOCATION_STATE, NULL, "Alabama", 0, 0, 0}, + {LOCATION_CITY, "KBMX", "Birmingham", 0, 0, 0}, + {LOCATION_CITY, "KMXX", "E. Alabama", 0, 0, 0}, + {LOCATION_CITY, "KEOX", "Fort Rucker", 0, 0, 0}, + {LOCATION_CITY, "KMOB", "Mobile", 0, 0, 0}, + {LOCATION_CITY, "KHTX", "Nrn. Alabama", 0, 0, 0}, + {LOCATION_STATE, NULL, "Alaska", 0, 0, 0}, + {LOCATION_CITY, "KABC", "Bethel", 0, 0, 0}, + {LOCATION_CITY, "KACG", "Biorka Is.", 0, 0, 0}, + {LOCATION_CITY, "KAPD", "Fairbanks", 0, 0, 0}, + {LOCATION_CITY, "KAHG", "Kenai", 0, 0, 0}, + {LOCATION_CITY, "KAKC", "King Salmon", 0, 0, 0}, + {LOCATION_CITY, "KAIH", "Middleton Is.", 0, 0, 0}, + {LOCATION_CITY, "KAEC", "Nome", 0, 0, 0}, + {LOCATION_CITY, "KAPD", "Pedro Dome", 0, 0, 0}, + {LOCATION_CITY, "KACG", "Sitka", 0, 0, 0}, + {LOCATION_STATE, NULL, "Arizona", 0, 0, 0}, + {LOCATION_CITY, "KFSX", "Flagstaff", 0, 0, 0}, + {LOCATION_CITY, "KIWA", "Phoenix", 0, 0, 0}, + {LOCATION_CITY, "KEMX", "Tucson", 0, 0, 0}, + {LOCATION_CITY, "KYUX", "Yuma", 0, 0, 0}, + {LOCATION_STATE, NULL, "Arkansas", 0, 0, 0}, + {LOCATION_CITY, "KLZK", "Little Rock", 0, 0, 0}, + {LOCATION_CITY, "KSRX", "W. Ark./Ft. Smith", 0, 0, 0}, + {LOCATION_STATE, NULL, "California", 0, 0, 0}, + {LOCATION_CITY, "KBBX", "Beale AFB", 0, 0, 0}, + {LOCATION_CITY, "KEYX", "Edwards AFB", 0, 0, 0}, + {LOCATION_CITY, "KBHX", "Eureka", 0, 0, 0}, + {LOCATION_CITY, "KHNX", "Hanford", 0, 0, 0}, + {LOCATION_CITY, "KVTX", "Los Angeles", 0, 0, 0}, + {LOCATION_CITY, "KDAX", "Sacramento", 0, 0, 0}, + {LOCATION_CITY, "KNKX", "San Diego", 0, 0, 0}, + {LOCATION_CITY, "KMUX", "San Francisco", 0, 0, 0}, + {LOCATION_CITY, "KHNX", "San Joaquin Vly.", 0, 0, 0}, + {LOCATION_CITY, "KSOX", "Santa Ana Mtns", 0, 0, 0}, + {LOCATION_CITY, "KVBX", "Vandenberg AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Colorado", 0, 0, 0}, + {LOCATION_CITY, "KFTG", "Denver/Boulder", 0, 0, 0}, + {LOCATION_CITY, "KGJX", "Grand Junction", 0, 0, 0}, + {LOCATION_CITY, "KPUX", "Pueblo", 0, 0, 0}, + {LOCATION_STATE, NULL, "Delaware", 0, 0, 0}, + {LOCATION_CITY, "KDOX", "Dover AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Florida", 0, 0, 0}, + {LOCATION_CITY, "KEVX", "Eglin AFB", 0, 0, 0}, + {LOCATION_CITY, "KJAX", "Jacksonville", 0, 0, 0}, + {LOCATION_CITY, "KBYX", "Key West", 0, 0, 0}, + {LOCATION_CITY, "KMLB", "Melbourne", 0, 0, 0}, + {LOCATION_CITY, "KAMX", "Miami", 0, 0, 0}, + {LOCATION_CITY, "KEVX", "NW Florida", 0, 0, 0}, + {LOCATION_CITY, "KTLH", "Tallahassee", 0, 0, 0}, + {LOCATION_CITY, "KTBW", "Tampa Bay Area", 0, 0, 0}, + {LOCATION_STATE, NULL, "Georgia", 0, 0, 0}, + {LOCATION_CITY, "KFFC", "Atlanta", 0, 0, 0}, + {LOCATION_CITY, "KVAX", "Moody AFB", 0, 0, 0}, + {LOCATION_CITY, "KFFC", "Peachtree City", 0, 0, 0}, + {LOCATION_CITY, "KJGX", "Robins AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Guam", 0, 0, 0}, + {LOCATION_CITY, "KGUA", "Andersen AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Hawaii", 0, 0, 0}, + {LOCATION_CITY, "KHKI", "Kauai", 0, 0, 0}, + {LOCATION_CITY, "KHKM", "Kohala", 0, 0, 0}, + {LOCATION_CITY, "KHMO", "Molokai", 0, 0, 0}, + {LOCATION_CITY, "KHWA", "South Shore", 0, 0, 0}, + {LOCATION_STATE, NULL, "Idaho", 0, 0, 0}, + {LOCATION_CITY, "KCBX", "Boise", 0, 0, 0}, + {LOCATION_CITY, "KSFX", "Pocatello", 0, 0, 0}, + {LOCATION_STATE, NULL, "Illinois", 0, 0, 0}, + {LOCATION_CITY, "KILX", "Central IL", 0, 0, 0}, + {LOCATION_CITY, "KLOT", "Chicago", 0, 0, 0}, + {LOCATION_STATE, NULL, "Indiana", 0, 0, 0}, + {LOCATION_CITY, "KVWX", "Evansville", 0, 0, 0}, + {LOCATION_CITY, "KIND", "Indianapolis", 0, 0, 0}, + {LOCATION_CITY, "KIWX", "Nrn. Indiana", 0, 0, 0}, + {LOCATION_STATE, NULL, "Iowa", 0, 0, 0}, + {LOCATION_CITY, "KDMX", "Des Moines", 0, 0, 0}, + {LOCATION_CITY, "KDVN", "Quad Cities", 0, 0, 0}, + {LOCATION_STATE, NULL, "Kansas", 0, 0, 0}, + {LOCATION_CITY, "KDDC", "Dodge City", 0, 0, 0}, + {LOCATION_CITY, "KGLD", "Goodland", 0, 0, 0}, + {LOCATION_CITY, "KTWX", "Topeka", 0, 0, 0}, + {LOCATION_CITY, "KICT", "Wichita", 0, 0, 0}, + {LOCATION_STATE, NULL, "Kentucky", 0, 0, 0}, + {LOCATION_CITY, "KHPX", "Fort Cambell", 0, 0, 0}, + {LOCATION_CITY, "KJKL", "Jackson", 0, 0, 0}, + {LOCATION_CITY, "KLVX", "Louisville", 0, 0, 0}, + {LOCATION_CITY, "KPAH", "Paducah", 0, 0, 0}, + {LOCATION_STATE, NULL, "Louisiana", 0, 0, 0}, + {LOCATION_CITY, "KPOE", "Fort Polk", 0, 0, 0}, + {LOCATION_CITY, "KLCH", "Lake Charles", 0, 0, 0}, + {LOCATION_CITY, "KLIX", "New Orleans", 0, 0, 0}, + {LOCATION_CITY, "KSHV", "Shreveport", 0, 0, 0}, + {LOCATION_STATE, NULL, "Maine", 0, 0, 0}, + {LOCATION_CITY, "KCBW", "Caribou", 0, 0, 0}, + {LOCATION_CITY, "KGYX", "Portland", 0, 0, 0}, + {LOCATION_STATE, NULL, "Maryland", 0, 0, 0}, + {LOCATION_CITY, "KLWX", "Baltimore", 0, 0, 0}, + {LOCATION_STATE, NULL, "Massachusetts", 0, 0, 0}, + {LOCATION_CITY, "KBOX", "Boston", 0, 0, 0}, + {LOCATION_STATE, NULL, "Michigan", 0, 0, 0}, + {LOCATION_CITY, "KDTX", "Detroit", 0, 0, 0}, + {LOCATION_CITY, "KAPX", "Gaylord", 0, 0, 0}, + {LOCATION_CITY, "KGRR", "Grand Rapids", 0, 0, 0}, + {LOCATION_CITY, "KMQT", "Marquette", 0, 0, 0}, + {LOCATION_STATE, NULL, "Minnesota", 0, 0, 0}, + {LOCATION_CITY, "KDLH", "Duluth", 0, 0, 0}, + {LOCATION_CITY, "KMPX", "Minneapolis", 0, 0, 0}, + {LOCATION_STATE, NULL, "Mississippi", 0, 0, 0}, + {LOCATION_CITY, "KGWX", "Columbus AFB", 0, 0, 0}, + {LOCATION_CITY, "KDGX", "Jackson/Brandon", 0, 0, 0}, + {LOCATION_STATE, NULL, "Missouri", 0, 0, 0}, + {LOCATION_CITY, "KEAX", "Kansas City", 0, 0, 0}, + {LOCATION_CITY, "KSGF", "Springfield", 0, 0, 0}, + {LOCATION_CITY, "KLSX", "St. Louis", 0, 0, 0}, + {LOCATION_STATE, NULL, "Montana", 0, 0, 0}, + {LOCATION_CITY, "KBLX", "Billings", 0, 0, 0}, + {LOCATION_CITY, "KGGW", "Glasgow", 0, 0, 0}, + {LOCATION_CITY, "KTFX", "Great Falls", 0, 0, 0}, + {LOCATION_CITY, "KMSX", "Missoula", 0, 0, 0}, + {LOCATION_STATE, NULL, "Nebraska", 0, 0, 0}, + {LOCATION_CITY, "KUEX", "Hastings", 0, 0, 0}, + {LOCATION_CITY, "KLNX", "North Platte", 0, 0, 0}, + {LOCATION_CITY, "KOAX", "Omaha", 0, 0, 0}, + {LOCATION_STATE, NULL, "Nevada", 0, 0, 0}, + {LOCATION_CITY, "KLRX", "Elko", 0, 0, 0}, + {LOCATION_CITY, "KESX", "Las Vegas", 0, 0, 0}, + {LOCATION_CITY, "KRGX", "Reno", 0, 0, 0}, + {LOCATION_STATE, NULL, "New Jersey", 0, 0, 0}, + {LOCATION_CITY, "KDIX", "Mt. Holly", 0, 0, 0}, + {LOCATION_STATE, NULL, "New Mexico", 0, 0, 0}, + {LOCATION_CITY, "KABX", "Albuquerque", 0, 0, 0}, + {LOCATION_CITY, "KFDX", "Cannon AFB", 0, 0, 0}, + {LOCATION_CITY, "KHDX", "Holloman AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "New York", 0, 0, 0}, + {LOCATION_CITY, "KENX", "Albany", 0, 0, 0}, + {LOCATION_CITY, "KBGM", "Binghamton", 0, 0, 0}, + {LOCATION_CITY, "KBUF", "Buffalo", 0, 0, 0}, + {LOCATION_CITY, "KTYX", "Montague", 0, 0, 0}, + {LOCATION_CITY, "KOKX", "New York City", 0, 0, 0}, + {LOCATION_CITY, "KOKX", "Upton", 0, 0, 0}, + {LOCATION_STATE, NULL, "North Carolina", 0, 0, 0}, + {LOCATION_CITY, "KRAX", "Durham", 0, 0, 0}, + {LOCATION_CITY, "KMHX", "Morehead City", 0, 0, 0}, + {LOCATION_CITY, "KRAX", "Raleigh", 0, 0, 0}, + {LOCATION_CITY, "KLTX", "Wilmington", 0, 0, 0}, + {LOCATION_STATE, NULL, "North Dakota", 0, 0, 0}, + {LOCATION_CITY, "KBIS", "Bismarck", 0, 0, 0}, + {LOCATION_CITY, "KMVX", "Grand Forks", 0, 0, 0}, + {LOCATION_CITY, "KMBX", "Minot AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Ohio", 0, 0, 0}, + {LOCATION_CITY, "KILN", "Cincinnati", 0, 0, 0}, + {LOCATION_CITY, "KCLE", "Cleveland", 0, 0, 0}, + {LOCATION_CITY, "KILN", "Dayton", 0, 0, 0}, + {LOCATION_CITY, "KILN", "Wilmington", 0, 0, 0}, + {LOCATION_STATE, NULL, "Oklahoma", 0, 0, 0}, + {LOCATION_CITY, "KFDR", "Frederick", 0, 0, 0}, + {LOCATION_CITY, "KTLX", "Oklahoma City", 0, 0, 0}, + {LOCATION_CITY, "KINX", "Tulsa", 0, 0, 0}, + {LOCATION_CITY, "KVNX", "Vance AFB", 0, 0, 0}, + {LOCATION_STATE, NULL, "Oregon", 0, 0, 0}, + {LOCATION_CITY, "KMAX", "Medford", 0, 0, 0}, + {LOCATION_CITY, "KPDT", "Pendleton", 0, 0, 0}, + {LOCATION_CITY, "KRTX", "Portland", 0, 0, 0}, + {LOCATION_STATE, NULL, "Pennsylvania", 0, 0, 0}, + {LOCATION_CITY, "KDIX", "Philadelphia", 0, 0, 0}, + {LOCATION_CITY, "KPBZ", "Pittsburgh", 0, 0, 0}, + {LOCATION_CITY, "KCCX", "State College", 0, 0, 0}, + {LOCATION_STATE, NULL, "Puerto Rico", 0, 0, 0}, + {LOCATION_CITY, "KJUA", "Puerto Rico/V.I.", 0, 0, 0}, + {LOCATION_STATE, NULL, "South Carolina", 0, 0, 0}, + {LOCATION_CITY, "KCLX", "Charleston", 0, 0, 0}, + {LOCATION_CITY, "KCAE", "Columbia", 0, 0, 0}, + {LOCATION_CITY, "KGSP", "Greenville", 0, 0, 0}, + {LOCATION_CITY, "KGSP", "Spartanburg", 0, 0, 0}, + {LOCATION_CITY, "KGSP", "Greer", 0, 0, 0}, + {LOCATION_STATE, NULL, "South Dakota", 0, 0, 0}, + {LOCATION_CITY, "KABR", "Aberdeen", 0, 0, 0}, + {LOCATION_CITY, "KUDX", "Rapid City", 0, 0, 0}, + {LOCATION_CITY, "KFSD", "Sioux falls", 0, 0, 0}, + {LOCATION_STATE, NULL, "Tennessee", 0, 0, 0}, + {LOCATION_CITY, "KMRX", "Knoxville", 0, 0, 0}, + {LOCATION_CITY, "KNQA", "Memphis", 0, 0, 0}, + {LOCATION_CITY, "KMRX", "Morristown", 0, 0, 0}, + {LOCATION_CITY, "KOHX", "Nashville", 0, 0, 0}, + {LOCATION_CITY, "KMRX", "Tri Cities", 0, 0, 0}, + {LOCATION_STATE, NULL, "Texas", 0, 0, 0}, + {LOCATION_CITY, "KAMA", "Amarillo", 0, 0, 0}, + {LOCATION_CITY, "KEWX", "Austin", 0, 0, 0}, + {LOCATION_CITY, "KBRO", "Brownsville", 0, 0, 0}, + {LOCATION_CITY, "KGRK", "Central Texas", 0, 0, 0}, + {LOCATION_CITY, "KCRP", "Corpus Christi", 0, 0, 0}, + {LOCATION_CITY, "KFWS", "Dallas", 0, 0, 0}, + {LOCATION_CITY, "KDYX", "Dyess AFB", 0, 0, 0}, + {LOCATION_CITY, "KEPZ", "El Paso", 0, 0, 0}, + {LOCATION_CITY, "KFWS", "Fort Worth", 0, 0, 0}, + {LOCATION_CITY, "KHGX", "Galveston", 0, 0, 0}, + {LOCATION_CITY, "KHGX", "Houston", 0, 0, 0}, + {LOCATION_CITY, "KDFX", "Laughlin AFB", 0, 0, 0}, + {LOCATION_CITY, "KLBB", "Lubbock", 0, 0, 0}, + {LOCATION_CITY, "KMAF", "Midland/Odessa", 0, 0, 0}, + {LOCATION_CITY, "KSJT", "San Angelo", 0, 0, 0}, + {LOCATION_CITY, "KEWX", "San Antonio", 0, 0, 0}, + {LOCATION_STATE, NULL, "Utah", 0, 0, 0}, + {LOCATION_CITY, "KICX", "Cedar City", 0, 0, 0}, + {LOCATION_CITY, "KMTX", "Salt Lake City", 0, 0, 0}, + {LOCATION_STATE, NULL, "Vermont", 0, 0, 0}, + {LOCATION_CITY, "KCXX", "Burlington", 0, 0, 0}, + {LOCATION_STATE, NULL, "Virginia", 0, 0, 0}, + {LOCATION_CITY, "KFCX", "Blacksburg", 0, 0, 0}, + {LOCATION_CITY, "KAKQ", "Norfolk", 0, 0, 0}, + {LOCATION_CITY, "KAKQ", "Richmond", 0, 0, 0}, + {LOCATION_CITY, "KFCX", "Roanoke", 0, 0, 0}, + {LOCATION_CITY, "KLWX", "Sterling", 0, 0, 0}, + {LOCATION_CITY, "KAKQ", "Wakefield", 0, 0, 0}, + {LOCATION_STATE, NULL, "Washington", 0, 0, 0}, + {LOCATION_CITY, "KATX", "Seattle", 0, 0, 0}, + {LOCATION_CITY, "KOTX", "Spokane", 0, 0, 0}, + {LOCATION_CITY, "KATX", "Tacoma", 0, 0, 0}, + {LOCATION_STATE, NULL, "Washington DC", 0, 0, 0}, + {LOCATION_CITY, "KLWX", "Washington", 0, 0, 0}, + {LOCATION_STATE, NULL, "West Virginia", 0, 0, 0}, + {LOCATION_CITY, "KRLX", "Charleston", 0, 0, 0}, + {LOCATION_STATE, NULL, "Wisconsin", 0, 0, 0}, + {LOCATION_CITY, "KGRB", "Green Bay", 0, 0, 0}, + {LOCATION_CITY, "KARX", "La Crosse", 0, 0, 0}, + {LOCATION_CITY, "KMKX", "Milwaukee", 0, 0, 0}, + {LOCATION_STATE, NULL, "Wyoming", 0, 0, 0}, + {LOCATION_CITY, "KCYS", "Cheyenne", 0, 0, 0}, + {LOCATION_CITY, "KRIW", "Riverton", 0, 0, 0}, + {0, NULL, NULL, 0, 0, 0}, }; diff --git a/src/aweather-location.h b/src/aweather-location.h index b030f04..1c39668 100644 --- a/src/aweather-location.h +++ b/src/aweather-location.h @@ -27,16 +27,9 @@ typedef struct { int type; char *code; char *label; - struct { - gint north; - gint ne; - gint east; - gint se; - gint south; - gint sw; - gint west; - gint nw; - } neighbors; + double lat; + double lon; + double elev; } city_t; extern city_t cities[]; diff --git a/src/examples/mkfile b/src/examples/mkfile new file mode 100644 index 0000000..3036c6c --- /dev/null +++ b/src/examples/mkfile @@ -0,0 +1,6 @@ +MKSHELL=/usr/lib/plan9/bin/rc +PROGS=tex +tex_libs=`{pkg-config --libs gtkglext-1.0} +tex_cflags=`{pkg-config --cflags gtkglext-1.0} +default:V: tex-run +<$HOME/lib/mkcommon diff --git a/src/examples/tex.c b/src/examples/tex.c new file mode 100644 index 0000000..267c1c1 --- /dev/null +++ b/src/examples/tex.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include + +guint tex, texl, texr; + +gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer _) +{ + if (event->keyval == GDK_q) + gtk_main_quit(); + return FALSE; +} + +gboolean on_expose(GtkWidget *drawing, GdkEventExpose *event, gpointer _) +{ + glClearColor(0.5, 0.5, 1.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-1,1, -1,1, 10,-10); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(0, 0, -5); + + glEnable(GL_COLOR_MATERIAL); + glDisable(GL_TEXTURE_2D); + glColor3f(1.0, 1.0, 1.0); + glBegin(GL_QUADS); + glVertex3f(-0.25, -0.75, 0.0); + glVertex3f(-0.25, 0.75, 0.0); + glVertex3f( 0.25, 0.75, 0.0); + glVertex3f( 0.25, -0.75, 0.0); + glEnd(); + + /* Textures */ + glDisable(GL_COLOR_MATERIAL); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + gdouble y = 0.875; + + /* Left */ + glBlendFunc(GL_ONE, GL_ZERO); + glBindTexture(GL_TEXTURE_2D, texl); + glBegin(GL_QUADS); + glTexCoord2f(0.0, y); glVertex3f(-0.75, 0.0, 0.0); + glTexCoord2f(0.0, 1.0); glVertex3f(-0.75, 0.5, 0.0); + glTexCoord2f(2.0, 1.0); glVertex3f( 0.75, 0.5, 0.0); + glTexCoord2f(2.0, y); glVertex3f( 0.75, 0.0, 0.0); + glEnd(); + + /* Right */ + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, texr); + glBegin(GL_QUADS); + glTexCoord2f(-1.0, y); glVertex3f(-0.75, 0.0, 0.0); + glTexCoord2f(-1.0, 1.0); glVertex3f(-0.75, 0.5, 0.0); + glTexCoord2f( 1.0, 1.0); glVertex3f( 0.75, 0.5, 0.0); + glTexCoord2f( 1.0, y); glVertex3f( 0.75, 0.0, 0.0); + glEnd(); + + /* Bottom */ + glBlendFunc(GL_ONE, GL_ZERO); + glBindTexture(GL_TEXTURE_2D, tex); + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); glVertex3f(-0.75, -0.5, 0.0); + glTexCoord2f(0.0, 1.0-y); glVertex3f(-0.75, -0.0, 0.0); + glTexCoord2f(1.0, 1.0-y); glVertex3f( 0.75, -0.0, 0.0); + glTexCoord2f(1.0, 0.0); glVertex3f( 0.75, -0.5, 0.0); + glEnd(); + + + /* Flush */ + GdkGLDrawable *gldrawable = gdk_gl_drawable_get_current(); + if (gdk_gl_drawable_is_double_buffered(gldrawable)) + gdk_gl_drawable_swap_buffers(gldrawable); + else + glFlush(); + return FALSE; +} +gboolean on_configure(GtkWidget *drawing, GdkEventConfigure *event, gpointer _) +{ + glViewport(0, 0, + drawing->allocation.width, + drawing->allocation.height); + return FALSE; +} + +guint load_tex(gchar *filename) +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(filename, NULL); + guchar *pixels = gdk_pixbuf_get_pixels(pixbuf); + int width = gdk_pixbuf_get_width(pixbuf); + int height = gdk_pixbuf_get_height(pixbuf); + int alpha = gdk_pixbuf_get_has_alpha(pixbuf); + guint tex; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, 4, width, height, 0, + (alpha ? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, pixels); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + g_object_unref(pixbuf); + return tex; +} + +int main(int argc, char **argv) +{ + gtk_init(&argc, &argv); + + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *drawing = gtk_drawing_area_new(); + GdkGLConfig *glconfig = gdk_gl_config_new_by_mode((GdkGLConfigMode)( + GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH | + GDK_GL_MODE_DOUBLE | GDK_GL_MODE_ALPHA)); + g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL); + g_signal_connect(drawing, "expose-event", G_CALLBACK(on_expose), NULL); + g_signal_connect(drawing, "configure-event", G_CALLBACK(on_configure), NULL); + gtk_widget_set_gl_capability(drawing, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); + gtk_container_add(GTK_CONTAINER(window), drawing); + gtk_widget_show_all(window); + + /* OpenGL setup */ + GdkGLContext *glcontext = gtk_widget_get_gl_context(GTK_WIDGET(drawing)); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(drawing)); + gdk_gl_drawable_gl_begin(gldrawable, glcontext); + + /* Load texture */ + texl = load_tex("tex_png/texls.png"); + texr = load_tex("tex_png/texrs.png"); + tex = load_tex("tex_png/tex.png"); + + gtk_main(); + + gdk_gl_drawable_gl_end(gldrawable); +} diff --git a/src/examples/tex_png/tex.png b/src/examples/tex_png/tex.png new file mode 100644 index 0000000000000000000000000000000000000000..af375cb9a96eaee2e5e9d6e2f4582db25d776bdb GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJy0V3D3_&Eb9#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wkP>{XE)7O>#CMP4enB21NR*+uF64!_l=ltB<)VvY~=c3falGGH1^30M9 z1$R&1fbd2>aiAg%PZ!4!j_XrTZ{%b!;5lGWKkfhjlk50e-_7M=N_S6mU!-Dt=J4(N aj~RY!GiN^V<+LeK4}+(xpUXO@geCww)HLD% literal 0 HcmV?d00001 diff --git a/src/examples/tex_png/texl.png b/src/examples/tex_png/texl.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc714195a27873a0c11be21276e3513930da08c GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=7>k44ofy`glX(f`a29w(7Bet# z3xhBt!>lLpZLto?6JuV8C-=L(R4S|24~RzF43dej`xB_KnA{K4y7E Wb>VrZYMOz{89ZJ6T-G@yGywoJEi+00 literal 0 HcmV?d00001 diff --git a/src/examples/tex_png/texls.png b/src/examples/tex_png/texls.png new file mode 100644 index 0000000000000000000000000000000000000000..c3de39b0981e3550dd9c6dc227f18645afc6884f GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^%s|Y-!3HF~bz9ki6k~CayA#8@b22Z19L@rd$YKTt zZeb8+WSBKa0w~B{;_2(kev^}t+dx{>{?l5ZkYtH#M2T~LZfk&LH{V+hCf0}=vK(|H*9XUa?8{P2|n PsE)zY)z4*}Q$iB}(|<07 literal 0 HcmV?d00001 diff --git a/src/examples/tex_png/texr.png b/src/examples/tex_png/texr.png new file mode 100644 index 0000000000000000000000000000000000000000..a421a5de0843aaf0fc41cc05eed6e74db7fccef0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=7>k44ofy`glX(f`a29w(7Bet# z3xhBt!>l?U@}#8R~kJ+@~mV R<^q*6c)I$ztaD0e0suF7FMI$1 literal 0 HcmV?d00001 diff --git a/src/examples/tex_png/texrs.png b/src/examples/tex_png/texrs.png new file mode 100644 index 0000000000000000000000000000000000000000..49765d2a4bb17c95b02c1afd53502d0026118957 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^%s|Y-!3HF~bz9ki6k~CayA#8@b22Z19L@rd$YKTt zZeb8+WSBKa0w~B{;_2(kev^}t+kp4sYxh4uA;}Wgh!W@g+}zZ>5(ej@)Wnk16ovB4 zk_-iRPv3y>Mm}+%B56+-#}JO|$v@5?_%Q#W{o()rd3XfWd>R;+^Dv~p5Pw;C_udPj OG6qjqKbLh*2~7YNN-$mk literal 0 HcmV?d00001 diff --git a/src/gis/.vimrc b/src/gis/.vimrc new file mode 100644 index 0000000..5de2904 --- /dev/null +++ b/src/gis/.vimrc @@ -0,0 +1,2 @@ +set makeprg=make\ test +set tw=100 diff --git a/src/gis/Makefile.am b/src/gis/Makefile.am index fb12f5e..67a90dd 100644 --- a/src/gis/Makefile.am +++ b/src/gis/Makefile.am @@ -1,5 +1,6 @@ AM_CFLAGS = -Wall -Werror -Wno-unused --std=gnu99 +# Library BUILT_SOURCES = gis-marshal.c gis-marshal.h lib_LTLIBRARIES = libgis.la gis_includedir = $(includedir)/gis @@ -12,13 +13,16 @@ gis_include_HEADERS = \ gis-plugin.h \ gis-data.h libgis_la_SOURCES = \ - gis-world.c gis-world.h \ - gis-view.c gis-view.h \ - gis-prefs.c gis-prefs.h \ - gis-opengl.c gis-opengl.h \ - gis-plugin.c gis-plugin.h \ - gis-data.c gis-data.h \ - gis-marshal.c gis-marshal.h + gis-world.c gis-world.h \ + gis-view.c gis-view.h \ + gis-prefs.c gis-prefs.h \ + gis-opengl.c gis-opengl.h \ + gis-plugin.c gis-plugin.h \ + gis-marshal.c gis-marshal.h \ + gis-data.c gis-data.h \ + roam.c roam.h \ + wms.c wms.h \ + gpqueue.c gpqueue.h libgis_la_CPPFLAGS = \ -DDATADIR="\"$(datadir)\"" -DPLUGINDIR="\"$(libdir)/gis\"" \ $(SOUP_CFLAGS) $(GLIB_CFLAGS) $(GTK_CFLAGS) @@ -28,9 +32,42 @@ libgis_la_LIBADD = \ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgis.pc +# Test programs +bin_PROGRAMS = gis_test wms_test + +gis_test_SOURCES = gis_test.c gis.h +gis_test_CPPFLAGS = $(SOUP_CFLAGS) $(GLIB_CFLAGS) $(GTK_CFLAGS) +gis_test_LDADD = libgis.la $(SOUP_LIBS) $(GLIB_LIBS) $(GTK_LIBS) + +wms_test_SOURCES = wms_test.c gis-world.c gis-world.h wms.c wms.h +wms_test_CPPFLAGS = $(SOUP_CFLAGS) $(GLIB_CFLAGS) $(GTK_CFLAGS) +wms_test_LDADD = $(SOUP_LIBS) $(GLIB_LIBS) $(GTK_LIBS) + MAINTAINERCLEANFILES = Makefile.in .list.c: glib-genmarshal --prefix=gis_cclosure_marshal --body $< > $@ .list.h: glib-genmarshal --prefix=gis_cclosure_marshal --header $< > $@ + +test: all + make -C ../plugins all + LD_LIBRARY_PATH=.libs .libs/gis_test + +gdb: all + LD_LIBRARY_PATH=.libs gdb .libs/gis_test + +ddd: all + LD_LIBRARY_PATH=.libs ddd .libs/gis_test + +memcheck: all + LD_LIBRARY_PATH=.libs \ + G_SLICE=always-malloc \ + G_DEBUG=gc-friendly,resident-modules \ + valgrind --track-origins=yes \ + --leak-check=full \ + --leak-resolution=high \ + --num-callers=100 \ + --suppressions=../gtk.suppression \ + .libs/gis_test \ + 2> valgrind.out diff --git a/src/gis/gis-opengl.c b/src/gis/gis-opengl.c index bdcaf40..e1abb07 100644 --- a/src/gis/gis-opengl.c +++ b/src/gis/gis-opengl.c @@ -15,6 +15,10 @@ * along with this program. If not, see . */ +/* Tessellation, "finding intersecting triangles" */ +/* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */ +/* http://www.opengl.org/wiki/Alpha_Blending */ + #include #include #include @@ -24,237 +28,504 @@ #include #include "gis-opengl.h" +#include "roam.h" +#include "wms.h" -/**************** - * GObject code * - ****************/ -G_DEFINE_TYPE(GisOpenGL, gis_opengl, G_TYPE_OBJECT); -static void gis_opengl_init(GisOpenGL *self) -{ - g_debug("GisOpenGL: init"); -} -static GObject *gis_opengl_constructor(GType gtype, guint n_properties, - GObjectConstructParam *properties) +#define FOV_DIST 2000.0 +#define MPPX(dist) (4*dist/FOV_DIST) + + +/************* + * ROAM Code * + *************/ +void roam_queue_draw(WmsCacheNode *node, gpointer _self) { - g_debug("GisOpengl: constructor"); - GObjectClass *parent_class = G_OBJECT_CLASS(gis_opengl_parent_class); - return parent_class->constructor(gtype, n_properties, properties); + gtk_widget_queue_draw(GTK_WIDGET(_self)); } -static void gis_opengl_dispose(GObject *gobject) + +void roam_height_func(RoamPoint *point, gpointer _self) { - g_debug("GisOpenGL: dispose"); - GisOpenGL *self = GIS_OPENGL(gobject); - if (self->world) { - g_object_unref(self->world); - self->world = NULL; + GisOpenGL *self = _self; + + gdouble cam_lle[3], cam_xyz[3]; + gis_view_get_location(self->view, &cam_lle[0], &cam_lle[1], &cam_lle[2]); + lle2xyz(cam_lle[0], cam_lle[1], cam_lle[2], &cam_xyz[0], &cam_xyz[1], &cam_xyz[2]); + + gdouble lat, lon, elev; + xyz2lle(point->x, point->y, point->z, &lat, &lon, &elev); + + gdouble res = MPPX(distd(cam_xyz, (double*)point)); + //g_message("lat=%f, lon=%f, res=%f", lat, lon, res); + + WmsCacheNode *node = wms_info_fetch_cache(self->srtm, res, lat, lon, NULL, roam_queue_draw, self); + + if (node) { + WmsBil *bil = node->data; + + gint w = bil->width; + gint h = bil->height; + + gdouble xmin = node->latlon[0]; + gdouble ymin = node->latlon[1]; + gdouble xmax = node->latlon[2]; + gdouble ymax = node->latlon[3]; + + gdouble xdist = xmax - xmin; + gdouble ydist = ymax - ymin; + + gdouble x = (lon-xmin)/xdist * w; + gdouble y = (1-(lat-ymin)/ydist) * h; + + gdouble x_rem = x - (int)x; + gdouble y_rem = y - (int)y; + guint x_flr = (int)x; + guint y_flr = (int)y; + + /* TODO: Fix interpolation at edges: + * - Pad these at the edges instead of wrapping/truncating + * - Figure out which pixels to index (is 0,0 edge, center, etc) */ + gint16 px00 = bil->data[MIN((y_flr ),h-1)*w + MIN((x_flr ),w-1)]; + gint16 px10 = bil->data[MIN((y_flr ),h-1)*w + MIN((x_flr+1),w-1)]; + gint16 px01 = bil->data[MIN((y_flr+1),h-1)*w + MIN((x_flr ),w-1)]; + gint16 px11 = bil->data[MIN((y_flr+1),h-1)*w + MIN((x_flr+1),w-1)]; + + elev = px00 * (1-x_rem) * (1-y_rem) + + px10 * ( x_rem) * (1-y_rem) + + px01 * (1-x_rem) * ( y_rem) + + px11 * ( x_rem) * ( y_rem); + //g_message("elev=%f -- %hd %hd %hd %hd", + // elev, px00, px10, px01, px11); + } else { + elev = 0; } - if (self->view) { - g_object_unref(self->view); - self->view = NULL; - } - if (self->drawing) { - g_object_unref(self->drawing); - self->drawing = NULL; - } - G_OBJECT_CLASS(gis_opengl_parent_class)->dispose(gobject); -} -static void gis_opengl_finalize(GObject *gobject) -{ - g_debug("GisOpenGL: finalize"); - G_OBJECT_CLASS(gis_opengl_parent_class)->finalize(gobject); - gtk_main_quit(); -} -static void gis_opengl_class_init(GisOpenGLClass *klass) -{ - g_debug("GisOpenGL: class_init"); - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - gobject_class->constructor = gis_opengl_constructor; - gobject_class->dispose = gis_opengl_dispose; - gobject_class->finalize = gis_opengl_finalize; + lle2xyz(lat, lon, elev, &point->x, &point->y, &point->z); } -/************* - * Callbacks * - *************/ -gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, GisOpenGL *self) +void roam_tri_func(RoamTriangle *tri, gpointer _self) { - g_debug("GisOpenGL: on_drawing_button_press - Grabbing focus"); - gtk_widget_grab_focus(GTK_WIDGET(self->drawing)); - return TRUE; + GisOpenGL *self = _self; + if (tri->error < 0) return; + + /* Get lat-lon min and maxes for the triangle */ + gdouble lat[3], lon[3], elev[3]; + xyz2lle(tri->p.r->x, tri->p.r->y, tri->p.r->z, &lat[0], &lon[0], &elev[0]); + xyz2lle(tri->p.m->x, tri->p.m->y, tri->p.m->z, &lat[1], &lon[1], &elev[1]); + xyz2lle(tri->p.l->x, tri->p.l->y, tri->p.l->z, &lat[2], &lon[2], &elev[2]); + gdouble lat_max = MAX(MAX(lat[0], lat[1]), lat[2]); + gdouble lat_min = MIN(MIN(lat[0], lat[1]), lat[2]); + gdouble lon_max = MAX(MAX(lon[0], lon[1]), lon[2]); + gdouble lon_min = MIN(MIN(lon[0], lon[1]), lon[2]); + + /* Get target resolution */ + gdouble cam_lle[3], cam_xyz[3]; + gis_view_get_location(self->view, &cam_lle[0], &cam_lle[1], &cam_lle[2]); + lle2xyz(cam_lle[0], cam_lle[1], cam_lle[2], &cam_xyz[0], &cam_xyz[1], &cam_xyz[2]); + gdouble distr = distd(cam_xyz, (double*)tri->p.r); + gdouble distm = distd(cam_xyz, (double*)tri->p.m); + gdouble distl = distd(cam_xyz, (double*)tri->p.l); + double res = MPPX(MIN(MIN(distr, distm), distl)); + + /* TODO: + * - Fetch needed textures, not all corners + * - Also fetch center textures that aren't touched by a corner + * - Idea: send {lat,lon}{min,max} to fetch_cache and handle it in the recursion */ + /* Fetch textures */ + WmsCacheNode *textures[4] = { + wms_info_fetch_cache(self->bmng, res, lat_min, lon_min, NULL, roam_queue_draw, self), + wms_info_fetch_cache(self->bmng, res, lat_max, lon_min, NULL, roam_queue_draw, self), + wms_info_fetch_cache(self->bmng, res, lat_min, lon_max, NULL, roam_queue_draw, self), + wms_info_fetch_cache(self->bmng, res, lat_max, lon_max, NULL, roam_queue_draw, self), + }; + + /* Vertex color for hieght map viewing, 8848m == Everest */ + gfloat colors[] = { + (elev[0]-EARTH_R)/8848, + (elev[1]-EARTH_R)/8848, + (elev[2]-EARTH_R)/8848, + }; + + /* Draw each texture */ + /* TODO: Prevent double exposure when of hi-res textures on top of + * low-res textures when some high-res textures are not yet loaded. */ + glBlendFunc(GL_ONE, GL_ZERO); + for (int i = 0; i < 4; i++) { + /* Skip missing textures */ + if (textures[i] == NULL) + continue; + /* Skip already drawn textures */ + switch (i) { + case 3: if (textures[i] == textures[2]) continue; + case 2: if (textures[i] == textures[1]) continue; + case 1: if (textures[i] == textures[0]) continue; + } + + WmsCacheNode *node = textures[i]; + + if (node->latlon[0] == -180) { + if (lon[0] < -90 || lon[1] < -90 || lon[2] < -90) { + if (lon[0] > 90) lon[0] -= 360; + if (lon[1] > 90) lon[1] -= 360; + if (lon[2] > 90) lon[2] -= 360; + } + } else if (node->latlon[2] == 180.0) { + if (lon[0] < -90) lon[0] += 360; + if (lon[1] < -90) lon[1] += 360; + if (lon[2] < -90) lon[2] += 360; + } + + gdouble xmin = node->latlon[0]; + gdouble ymin = node->latlon[1]; + gdouble xmax = node->latlon[2]; + gdouble ymax = node->latlon[3]; + + gdouble xdist = xmax - xmin; + gdouble ydist = ymax - ymin; + + gdouble xy[][3] = { + {(lon[0]-xmin)/xdist, 1-(lat[0]-ymin)/ydist}, + {(lon[1]-xmin)/xdist, 1-(lat[1]-ymin)/ydist}, + {(lon[2]-xmin)/xdist, 1-(lat[2]-ymin)/ydist}, + }; + + glBindTexture(GL_TEXTURE_2D, *(guint*)node->data); + + glBegin(GL_TRIANGLES); + glColor3fv(colors); glNormal3dv(tri->p.r->norm); glTexCoord2dv(xy[0]); glVertex3dv((double*)tri->p.r); + glColor3fv(colors); glNormal3dv(tri->p.m->norm); glTexCoord2dv(xy[1]); glVertex3dv((double*)tri->p.m); + glColor3fv(colors); glNormal3dv(tri->p.l->norm); glTexCoord2dv(xy[2]); glVertex3dv((double*)tri->p.l); + glEnd(); + glBlendFunc(GL_ONE, GL_ONE); + } } -gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, GisOpenGL *self) + +static void set_camera(GisOpenGL *self) { - g_debug("GisOpenGL: on_drawing_key_press - key=%x, state=%x, plus=%x", - event->keyval, event->state, GDK_plus); - double x,y,z; - gis_view_get_location(self->view, &x, &y, &z); - guint kv = event->keyval; - if (kv == GDK_Left || kv == GDK_h) gis_view_pan(self->view, -z/10, 0, 0); - else if (kv == GDK_Down || kv == GDK_j) gis_view_pan(self->view, 0, -z/10, 0); - else if (kv == GDK_Up || kv == GDK_k) gis_view_pan(self->view, 0, z/10, 0); - else if (kv == GDK_Right || kv == GDK_l) gis_view_pan(self->view, z/10, 0, 0); - else if (kv == GDK_minus || kv == GDK_o) gis_view_zoom(self->view, 10./9); - else if (kv == GDK_plus || kv == GDK_i) gis_view_zoom(self->view, 9./10); - else if (kv == GDK_H ) gis_view_rotate(self->view, 0, -10, 0); - else if (kv == GDK_J ) gis_view_rotate(self->view, 10, 0, 0); - else if (kv == GDK_K ) gis_view_rotate(self->view, -10, 0, 0); - else if (kv == GDK_L ) gis_view_rotate(self->view, 0, 10, 0); - return TRUE; + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + double lat, lon, elev, rx, ry, rz; + gis_view_get_location(self->view, &lat, &lon, &elev); + gis_view_get_rotation(self->view, &rx, &ry, &rz); + glRotatef(rx, 1, 0, 0); + glRotatef(rz, 0, 0, 1); + glTranslatef(0, 0, -elev2rad(elev)); + glRotatef(lat, 1, 0, 0); + glRotatef(-lon, 0, 1, 0); } - -gboolean on_map(GtkWidget *drawing, GdkEventConfigure *event, GisOpenGL *self) +static void set_visuals(GisOpenGL *self) { - g_debug("GisOpenGL: on_map"); + /* Lighting */ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + float light_ambient[] = {0.2f, 0.2f, 0.2f, 1.0f}; + float light_diffuse[] = {5.0f, 5.0f, 5.0f, 1.0f}; + float light_position[] = {-13*EARTH_R, 1*EARTH_R, 3*EARTH_R, 1.0f}; + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHTING); + + float material_ambient[] = {0.2, 0.2, 0.2, 1.0}; + float material_diffuse[] = {0.8, 0.8, 0.8, 1.0}; + float material_specular[] = {0.0, 0.0, 0.0, 1.0}; + float material_emission[] = {0.0, 0.0, 0.0, 1.0}; + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_emission); + glDisable(GL_TEXTURE_2D); + glDisable(GL_COLOR_MATERIAL); + + /* Camera */ + set_camera(self); /* Misc */ - glEnable(GL_BLEND); + gdouble lat, lon, elev; + gis_view_get_location(self->view, &lat, &lon, &elev); + gdouble rg = MAX(0, 1-(elev/20000)); + gdouble blue = MAX(0, 1-(elev/50000)); + glClearColor(MIN(0.8,rg), MIN(0.8,rg), MIN(1,blue), 1.0f); + + glDisable(GL_ALPHA_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glEnable(GL_BLEND); - /* Tessellation, "finding intersecting triangles" */ - /* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */ - /* http://www.opengl.org/wiki/Alpha_Blending */ - glAlphaFunc(GL_GREATER,0.1); - glEnable(GL_ALPHA_TEST); + glCullFace(GL_BACK); + glEnable(GL_CULL_FACE); - /* Depth test */ glClearDepth(1.0); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); - gis_opengl_end(self); - return FALSE; + glEnable(GL_LINE_SMOOTH); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //glShadeModel(GL_FLAT); } -gboolean on_configure(GtkWidget *drawing, GdkEventConfigure *event, GisOpenGL *self) + +/************* + * Callbacks * + *************/ +static void on_realize(GisOpenGL *self, gpointer _) +{ + set_visuals(self); +} +static gboolean on_configure(GisOpenGL *self, GdkEventConfigure *event, gpointer _) { g_debug("GisOpenGL: on_confiure"); gis_opengl_begin(self); - double x, y, z; - gis_view_get_location(self->view, &x, &y, &z); - - /* Window is at 500 m from camera */ - double width = GTK_WIDGET(self->drawing)->allocation.width; - double height = GTK_WIDGET(self->drawing)->allocation.height; - + double width = GTK_WIDGET(self)->allocation.width; + double height = GTK_WIDGET(self)->allocation.height; glViewport(0, 0, width, height); - /* Perspective */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); - double ang = atan((height/2)/500); + double ang = atan(height/FOV_DIST); + gluPerspective(rad2deg(ang)*2, width/height, 1, 20*EARTH_R); - //gluPerspective(r2d(ang)*2, width/height, -z-20, -z+20); - gluPerspective(r2d(ang)*2, width/height, 1, 500*1000); + if (self->sphere) + roam_sphere_update_errors(self->sphere); gis_opengl_end(self); return FALSE; } -gboolean on_expose(GtkWidget *drawing, GdkEventExpose *event, GisOpenGL *self) +static void on_expose_plugin(GisPlugin *plugin, gchar *name, GisOpenGL *self) +{ + set_visuals(self); + glMatrixMode(GL_PROJECTION); glPushMatrix(); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); + gis_plugin_expose(plugin); + glMatrixMode(GL_PROJECTION); glPopMatrix(); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); +} +static gboolean on_expose(GisOpenGL *self, GdkEventExpose *event, gpointer _) { g_debug("GisOpenGL: on_expose - begin"); gis_opengl_begin(self); - double lx, ly, lz; - double rx, ry, rz; - gis_view_get_location(self->view, &lx, &ly, &lz); - gis_view_get_rotation(self->view, &rx, &ry, &rz); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glTranslatef(lx, ly, lz); - glRotatef(rx, 1, 0, 0); - glRotatef(ry, 0, 1, 0); - glRotatef(rz, 0, 0, 1); + set_visuals(self); + glEnable(GL_TEXTURE_2D); + roam_sphere_draw(self->sphere); - /* Expose plugins */ - gis_plugins_foreach(self->plugins, G_CALLBACK(gis_plugin_expose), NULL); + //glDisable(GL_TEXTURE_2D); + //glEnable(GL_COLOR_MATERIAL); + //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //roam_sphere_draw(self->sphere); + //glColor4f(0.0, 0.0, 9.0, 0.6); + //glDisable(GL_TEXTURE_2D); + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //roam_sphere_draw(self->sphere); + + gis_plugins_foreach(self->plugins, G_CALLBACK(on_expose_plugin), self); + + set_visuals(self); gis_opengl_end(self); gis_opengl_flush(self); g_debug("GisOpenGL: on_expose - end\n"); return FALSE; } -void on_state_changed(GisView *view, - gdouble x, gdouble y, gdouble z, GisOpenGL *self) +static gboolean on_button_press(GisOpenGL *self, GdkEventButton *event, gpointer _) { - /* Reset clipping area and redraw */ - on_configure(NULL, NULL, self); + g_debug("GisOpenGL: on_button_press - Grabbing focus"); + gtk_widget_grab_focus(GTK_WIDGET(self)); + return TRUE; +} + +static gboolean on_key_press(GisOpenGL *self, GdkEventKey *event, gpointer _) +{ + g_debug("GisOpenGL: on_key_press - key=%x, state=%x, plus=%x", + event->keyval, event->state, GDK_plus); + + double lat, lon, elev, pan; + gis_view_get_location(self->view, &lat, &lon, &elev); + pan = MIN(elev/(EARTH_R/2), 30); + guint kv = event->keyval; + if (kv == GDK_Left || kv == GDK_h) gis_view_pan(self->view, 0, -pan, 0); + else if (kv == GDK_Down || kv == GDK_j) gis_view_pan(self->view, -pan, 0, 0); + else if (kv == GDK_Up || kv == GDK_k) gis_view_pan(self->view, pan, 0, 0); + else if (kv == GDK_Right || kv == GDK_l) gis_view_pan(self->view, 0, pan, 0); + else if (kv == GDK_minus || kv == GDK_o) gis_view_zoom(self->view, 10./9); + else if (kv == GDK_plus || kv == GDK_i) gis_view_zoom(self->view, 9./10); + else if (kv == GDK_H ) gis_view_rotate(self->view, 0, 0, -10); + else if (kv == GDK_J ) gis_view_rotate(self->view, 10, 0, 0); + else if (kv == GDK_K ) gis_view_rotate(self->view, -10, 0, 0); + else if (kv == GDK_L ) gis_view_rotate(self->view, 0, 0, 10); + return TRUE; +} + +static void on_view_changed(GisView *view, + gdouble _1, gdouble _2, gdouble _3, GisOpenGL *self) +{ + gis_opengl_begin(self); + set_visuals(self); + roam_sphere_update_errors(self->sphere); gis_opengl_redraw(self); + gis_opengl_end(self); +} + +static gboolean on_idle(GisOpenGL *self) +{ + gis_opengl_begin(self); + if (roam_sphere_split_merge(self->sphere)) + gis_opengl_redraw(self); + gis_opengl_end(self); + return TRUE; } + /*********** * Methods * ***********/ -GisOpenGL *gis_opengl_new(GisWorld *world, GisView *view, GtkDrawingArea *drawing) +GisOpenGL *gis_opengl_new(GisWorld *world, GisView *view, GisPlugins *plugins) { g_debug("GisOpenGL: new"); GisOpenGL *self = g_object_new(GIS_TYPE_OPENGL, NULL); self->world = world; self->view = view; - self->drawing = drawing; + self->plugins = plugins; g_object_ref(world); g_object_ref(view); - g_object_ref(drawing); - /* OpenGL setup */ - GdkGLConfig *glconfig = gdk_gl_config_new_by_mode( - GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH | - GDK_GL_MODE_DOUBLE | GDK_GL_MODE_ALPHA); - if (!glconfig) - g_error("Failed to create glconfig"); - if (!gtk_widget_set_gl_capability(GTK_WIDGET(self->drawing), - glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE)) - g_error("GL lacks required capabilities"); + g_signal_connect(self->view, "location-changed", G_CALLBACK(on_view_changed), self); + g_signal_connect(self->view, "rotation-changed", G_CALLBACK(on_view_changed), self); - g_signal_connect(self->view, "location-changed", G_CALLBACK(on_state_changed), self); - g_signal_connect(self->view, "rotation-changed", G_CALLBACK(on_state_changed), self); + /* TODO: update point eights sometime later so we have heigh-res heights for them */ + self->sphere = roam_sphere_new(roam_tri_func, roam_height_func, self); - g_signal_connect(self->drawing, "map-event", G_CALLBACK(on_map), self); - g_signal_connect(self->drawing, "configure-event", G_CALLBACK(on_configure), self); - g_signal_connect(self->drawing, "expose-event", G_CALLBACK(on_expose), self); + return g_object_ref(self); +} - g_signal_connect(self->drawing, "button-press-event", G_CALLBACK(on_button_press), self); - g_signal_connect(self->drawing, "enter-notify-event", G_CALLBACK(on_button_press), self); - g_signal_connect(self->drawing, "key-press-event", G_CALLBACK(on_key_press), self); - return self; +void gis_opengl_center_position(GisOpenGL *self, gdouble lat, gdouble lon, gdouble elev) +{ + set_camera(self); + glRotatef(lon, 0, 1, 0); + glRotatef(-lat, 1, 0, 0); + glTranslatef(0, 0, elev2rad(elev)); } void gis_opengl_redraw(GisOpenGL *self) { g_debug("GisOpenGL: gl_redraw"); - gtk_widget_queue_draw(GTK_WIDGET(self->drawing)); + gtk_widget_queue_draw(GTK_WIDGET(self)); } void gis_opengl_begin(GisOpenGL *self) { g_assert(GIS_IS_OPENGL(self)); - GdkGLContext *glcontext = gtk_widget_get_gl_context(GTK_WIDGET(self->drawing)); - GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self->drawing)); + GdkGLContext *glcontext = gtk_widget_get_gl_context(GTK_WIDGET(self)); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self)); if (!gdk_gl_drawable_gl_begin(gldrawable, glcontext)) g_assert_not_reached(); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void gis_opengl_end(GisOpenGL *self) { g_assert(GIS_IS_OPENGL(self)); - GdkGLDrawable *gldrawable = gdk_gl_drawable_get_current(); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self)); gdk_gl_drawable_gl_end(gldrawable); } void gis_opengl_flush(GisOpenGL *self) { g_assert(GIS_IS_OPENGL(self)); - GdkGLDrawable *gldrawable = gdk_gl_drawable_get_current(); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(GTK_WIDGET(self)); if (gdk_gl_drawable_is_double_buffered(gldrawable)) gdk_gl_drawable_swap_buffers(gldrawable); else glFlush(); gdk_gl_drawable_gl_end(gldrawable); } + + +/**************** + * GObject code * + ****************/ +G_DEFINE_TYPE(GisOpenGL, gis_opengl, GTK_TYPE_DRAWING_AREA); +static void gis_opengl_init(GisOpenGL *self) +{ + g_debug("GisOpenGL: init"); + self->bmng = wms_info_new_for_bmng(NULL, NULL); + self->srtm = wms_info_new_for_srtm(NULL, NULL); + + /* OpenGL setup */ + GdkGLConfig *glconfig = gdk_gl_config_new_by_mode( + GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH | + GDK_GL_MODE_DOUBLE | GDK_GL_MODE_ALPHA); + if (!glconfig) + g_error("Failed to create glconfig"); + if (!gtk_widget_set_gl_capability(GTK_WIDGET(self), + glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE)) + g_error("GL lacks required capabilities"); + g_object_unref(glconfig); + + gtk_widget_set_size_request(GTK_WIDGET(self), 600, 550); + gtk_widget_set_events(GTK_WIDGET(self), + GDK_BUTTON_PRESS_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_KEY_PRESS_MASK); + g_object_set(self, "can-focus", TRUE, NULL); + + self->sm_source = g_timeout_add(10, (GSourceFunc)on_idle, self); + + g_signal_connect(self, "realize", G_CALLBACK(on_realize), NULL); + g_signal_connect(self, "configure-event", G_CALLBACK(on_configure), NULL); + g_signal_connect(self, "expose-event", G_CALLBACK(on_expose), NULL); + + g_signal_connect(self, "button-press-event", G_CALLBACK(on_button_press), NULL); + g_signal_connect(self, "enter-notify-event", G_CALLBACK(on_button_press), NULL); + g_signal_connect(self, "key-press-event", G_CALLBACK(on_key_press), NULL); +} +static GObject *gis_opengl_constructor(GType gtype, guint n_properties, + GObjectConstructParam *properties) +{ + g_debug("GisOpengl: constructor"); + GObjectClass *parent_class = G_OBJECT_CLASS(gis_opengl_parent_class); + return parent_class->constructor(gtype, n_properties, properties); +} +static void gis_opengl_dispose(GObject *_self) +{ + g_debug("GisOpenGL: dispose"); + GisOpenGL *self = GIS_OPENGL(_self); + if (self->sm_source) { + g_source_remove(self->sm_source); + self->sm_source = 0; + } + if (self->sphere) { + roam_sphere_free(self->sphere); + self->sphere = NULL; + } + if (self->world) { + g_object_unref(self->world); + self->world = NULL; + } + if (self->view) { + g_object_unref(self->view); + self->view = NULL; + } + G_OBJECT_CLASS(gis_opengl_parent_class)->dispose(_self); +} +static void gis_opengl_finalize(GObject *_self) +{ + g_debug("GisOpenGL: finalize"); + GisOpenGL *self = GIS_OPENGL(_self); + wms_info_free(self->bmng); + wms_info_free(self->srtm); + G_OBJECT_CLASS(gis_opengl_parent_class)->finalize(_self); +} +static void gis_opengl_class_init(GisOpenGLClass *klass) +{ + g_debug("GisOpenGL: class_init"); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->constructor = gis_opengl_constructor; + gobject_class->dispose = gis_opengl_dispose; + gobject_class->finalize = gis_opengl_finalize; +} diff --git a/src/gis/gis-opengl.h b/src/gis/gis-opengl.h index ee753a7..fcaaa0e 100644 --- a/src/gis/gis-opengl.h +++ b/src/gis/gis-opengl.h @@ -36,22 +36,24 @@ typedef struct _GisOpenGLClass GisOpenGLClass; #include "gis-view.h" #include "gis-world.h" #include "gis-plugin.h" - -#define d2r(deg) (((deg)*M_PI)/180.0) -#define r2d(rad) (((rad)*180.0)/M_PI) +#include "roam.h" +#include "wms.h" struct _GisOpenGL { - GObject parent_instance; + GtkDrawingArea parent_instance; /* instance members */ - GisWorld *world; - GisView *view; - GisPlugins *plugins; - GtkDrawingArea *drawing; + GisWorld *world; + GisView *view; + GisPlugins *plugins; + RoamSphere *sphere; + WmsInfo *bmng; + WmsInfo *srtm; + guint sm_source; }; struct _GisOpenGLClass { - GObjectClass parent_class; + GtkDrawingAreaClass parent_class; /* class members */ }; @@ -59,11 +61,14 @@ struct _GisOpenGLClass { GType gis_opengl_get_type(void); /* Methods */ -GisOpenGL *gis_opengl_new(GisWorld *world, GisView *view, GtkDrawingArea *drawing); +GisOpenGL *gis_opengl_new(GisWorld *world, GisView *view, GisPlugins *plugins); + +void gis_opengl_center_position(GisOpenGL *gis, gdouble lat, gdouble lon, gdouble elev); + +void gis_opengl_redraw(GisOpenGL *gis); -void gis_opengl_redraw(GisOpenGL *gis); -void gis_opengl_begin(GisOpenGL *gis); -void gis_opengl_end(GisOpenGL *gis); -void gis_opengl_flush(GisOpenGL *gis); +void gis_opengl_begin(GisOpenGL *gis); +void gis_opengl_end(GisOpenGL *gis); +void gis_opengl_flush(GisOpenGL *gis); #endif diff --git a/src/gis/gis-plugin.c b/src/gis/gis-plugin.c index e3d132b..4bfab58 100644 --- a/src/gis/gis-plugin.c +++ b/src/gis/gis-plugin.c @@ -20,6 +20,9 @@ #include "gis-plugin.h" +/******************** + * Plugin interface * + ********************/ static void gis_plugin_base_init(gpointer g_class) { static gboolean is_initialized = FALSE; @@ -56,7 +59,10 @@ GtkWidget *gis_plugin_get_config(GisPlugin *self) return GIS_PLUGIN_GET_INTERFACE(self)->get_config(self); } -/* Plugins API */ + +/*************** + * Plugins API * + ***************/ typedef struct { gchar *name; GisPlugin *plugin; diff --git a/src/gis/gis-prefs.c b/src/gis/gis-prefs.c index ea3f05a..abae1a2 100644 --- a/src/gis/gis-prefs.c +++ b/src/gis/gis-prefs.c @@ -19,68 +19,13 @@ #include "gis-marshal.h" #include "gis-prefs.h" -/**************** - * GObject code * - ****************/ + enum { SIG_PREF_CHANGED, NUM_SIGNALS, }; static guint signals[NUM_SIGNALS]; -G_DEFINE_TYPE(GisPrefs, gis_prefs, G_TYPE_OBJECT); -static void gis_prefs_init(GisPrefs *self) -{ - g_debug("GisPrefs: init"); - self->key_file = g_key_file_new(); -} -static GObject *gis_prefs_constructor(GType gtype, guint n_properties, - GObjectConstructParam *properties) -{ - g_debug("gis_prefs: constructor"); - GObjectClass *parent_class = G_OBJECT_CLASS(gis_prefs_parent_class); - return parent_class->constructor(gtype, n_properties, properties); -} -static void gis_prefs_dispose(GObject *_self) -{ - g_debug("GisPrefs: dispose"); - GisPrefs *self = GIS_PREFS(_self); - if (self->key_file) { - gsize length; - g_mkdir_with_parents(g_path_get_dirname(self->key_path), 0755); - gchar *data = g_key_file_to_data(self->key_file, &length, NULL); - g_file_set_contents(self->key_path, data, length, NULL); - g_key_file_free(self->key_file); - self->key_file = NULL; - } - G_OBJECT_CLASS(gis_prefs_parent_class)->dispose(_self); -} -static void gis_prefs_finalize(GObject *_self) -{ - g_debug("GisPrefs: finalize"); - G_OBJECT_CLASS(gis_prefs_parent_class)->finalize(_self); -} -static void gis_prefs_class_init(GisPrefsClass *klass) -{ - g_debug("GisPrefs: class_init"); - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - gobject_class->constructor = gis_prefs_constructor; - gobject_class->dispose = gis_prefs_dispose; - gobject_class->finalize = gis_prefs_finalize; - signals[SIG_PREF_CHANGED] = g_signal_new( - "pref-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - gis_cclosure_marshal_VOID__STRING_UINT_POINTER, - G_TYPE_NONE, - 1, - G_TYPE_STRING, - G_TYPE_UINT, - G_TYPE_POINTER); -} /*********** * Methods * @@ -157,3 +102,65 @@ make_pref_type(string, gchar*, G_TYPE_STRING) make_pref_type(boolean, gboolean, G_TYPE_BOOLEAN) make_pref_type(integer, gint, G_TYPE_INT) make_pref_type(double, gdouble, G_TYPE_DOUBLE) + + +/**************** + * GObject code * + ****************/ +G_DEFINE_TYPE(GisPrefs, gis_prefs, G_TYPE_OBJECT); +static void gis_prefs_init(GisPrefs *self) +{ + g_debug("GisPrefs: init"); + self->key_file = g_key_file_new(); +} +static GObject *gis_prefs_constructor(GType gtype, guint n_properties, + GObjectConstructParam *properties) +{ + g_debug("gis_prefs: constructor"); + GObjectClass *parent_class = G_OBJECT_CLASS(gis_prefs_parent_class); + return parent_class->constructor(gtype, n_properties, properties); +} +static void gis_prefs_dispose(GObject *_self) +{ + g_debug("GisPrefs: dispose"); + GisPrefs *self = GIS_PREFS(_self); + if (self->key_file) { + gsize length; + gchar *dir = g_path_get_dirname(self->key_path); + g_mkdir_with_parents(dir, 0755); + gchar *data = g_key_file_to_data(self->key_file, &length, NULL); + g_file_set_contents(self->key_path, data, length, NULL); + g_key_file_free(self->key_file); + g_free(self->key_path); + g_free(dir); + g_free(data); + self->key_file = NULL; + } + G_OBJECT_CLASS(gis_prefs_parent_class)->dispose(_self); +} +static void gis_prefs_finalize(GObject *_self) +{ + g_debug("GisPrefs: finalize"); + G_OBJECT_CLASS(gis_prefs_parent_class)->finalize(_self); +} +static void gis_prefs_class_init(GisPrefsClass *klass) +{ + g_debug("GisPrefs: class_init"); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->constructor = gis_prefs_constructor; + gobject_class->dispose = gis_prefs_dispose; + gobject_class->finalize = gis_prefs_finalize; + signals[SIG_PREF_CHANGED] = g_signal_new( + "pref-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + gis_cclosure_marshal_VOID__STRING_UINT_POINTER, + G_TYPE_NONE, + 1, + G_TYPE_STRING, + G_TYPE_UINT, + G_TYPE_POINTER); +} diff --git a/src/gis/gis-view.c b/src/gis/gis-view.c index 409aa68..0caf3f0 100644 --- a/src/gis/gis-view.c +++ b/src/gis/gis-view.c @@ -19,10 +19,8 @@ #include "gis-marshal.h" #include "gis-view.h" +#include "gis-world.h" -/**************** - * GObject code * - ****************/ /* Constants */ enum { PROP_0, @@ -38,126 +36,6 @@ enum { }; static guint signals[NUM_SIGNALS]; -/* Class/Object init */ -G_DEFINE_TYPE(GisView, gis_view, G_TYPE_OBJECT); -static void gis_view_init(GisView *self) -{ - g_debug("GisView: init"); - /* Default values */ - self->time = g_strdup(""); - self->site = g_strdup(""); - self->location[0] = 0; - self->location[1] = 0; - self->location[2] = -300*1000; - self->rotation[0] = 0; - self->rotation[1] = 0; - self->rotation[2] = 0; -} -static void gis_view_dispose(GObject *gobject) -{ - g_debug("GisView: dispose"); - /* Drop references to other GObjects */ - G_OBJECT_CLASS(gis_view_parent_class)->dispose(gobject); -} -static void gis_view_finalize(GObject *gobject) -{ - g_debug("GisView: finalize"); - GisView *self = GIS_VIEW(gobject); - g_free(self->time); - g_free(self->site); - G_OBJECT_CLASS(gis_view_parent_class)->finalize(gobject); -} -static void gis_view_set_property(GObject *object, guint property_id, - const GValue *value, GParamSpec *pspec) -{ - g_debug("GisView: set_property"); - GisView *self = GIS_VIEW(object); - switch (property_id) { - case PROP_TIME: gis_view_set_time(self, g_value_get_string(value)); break; - case PROP_SITE: gis_view_set_site(self, g_value_get_string(value)); break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - } -} -static void gis_view_get_property(GObject *object, guint property_id, - GValue *value, GParamSpec *pspec) -{ - g_debug("GisView: get_property"); - GisView *self = GIS_VIEW(object); - switch (property_id) { - case PROP_TIME: g_value_set_string(value, gis_view_get_time(self)); break; - case PROP_SITE: g_value_set_string(value, gis_view_get_site(self)); break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - } -} -static void gis_view_class_init(GisViewClass *klass) -{ - g_debug("GisView: class_init"); - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - gobject_class->dispose = gis_view_dispose; - gobject_class->finalize = gis_view_finalize; - gobject_class->get_property = gis_view_get_property; - gobject_class->set_property = gis_view_set_property; - g_object_class_install_property(gobject_class, PROP_TIME, - g_param_spec_pointer( - "time", - "time of the current frame", - "(format unknown)", - G_PARAM_READWRITE)); - g_object_class_install_property(gobject_class, PROP_SITE, - g_param_spec_pointer( - "site", - "site seen by the viewport", - "Site of the viewport. Currently this is the name of the radar site.", - G_PARAM_READWRITE)); - signals[SIG_TIME_CHANGED] = g_signal_new( - "time-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, - 1, - G_TYPE_STRING); - signals[SIG_SITE_CHANGED] = g_signal_new( - "site-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, - 1, - G_TYPE_STRING); - signals[SIG_LOCATION_CHANGED] = g_signal_new( - "location-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE, - G_TYPE_NONE, - 3, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE); - signals[SIG_ROTATION_CHANGED] = g_signal_new( - "rotation-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE, - G_TYPE_NONE, - 3, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE); -} /* Signal helpers */ static void _gis_view_emit_location_changed(GisView *view) @@ -211,32 +89,32 @@ gchar *gis_view_get_time(GisView *view) return view->time; } -void gis_view_set_location(GisView *view, gdouble x, gdouble y, gdouble z) +void gis_view_set_location(GisView *view, gdouble lat, gdouble lon, gdouble elev) { g_assert(GIS_IS_VIEW(view)); g_debug("GisView: set_location"); - view->location[0] = x; - view->location[1] = y; - view->location[2] = z; + view->location[0] = lat; + view->location[1] = lon; + view->location[2] = elev; _gis_view_emit_location_changed(view); } -void gis_view_get_location(GisView *view, gdouble *x, gdouble *y, gdouble *z) +void gis_view_get_location(GisView *view, gdouble *lat, gdouble *lon, gdouble *elev) { g_assert(GIS_IS_VIEW(view)); - g_debug("GisView: get_location"); - *x = view->location[0]; - *y = view->location[1]; - *z = view->location[2]; + //g_debug("GisView: get_location"); + *lat = view->location[0]; + *lon = view->location[1]; + *elev = view->location[2]; } -void gis_view_pan(GisView *view, gdouble x, gdouble y, gdouble z) +void gis_view_pan(GisView *view, gdouble lat, gdouble lon, gdouble elev) { g_assert(GIS_IS_VIEW(view)); - g_debug("GisView: pan - x=%.0f, y=%.0f, z=%.0f", x, y, z); - view->location[0] += x; - view->location[1] += y; - view->location[2] += z; + g_debug("GisView: pan - lat=%8.3f, lon=%8.3f, elev=%8.3f", lat, lon, elev); + view->location[0] += lat; + view->location[1] += lon; + view->location[2] += elev; _gis_view_emit_location_changed(view); } @@ -293,3 +171,127 @@ gchar *gis_view_get_site(GisView *view) g_debug("GisView: get_site - %s", view->site); return view->site; } + + +/**************** + * GObject code * + ****************/ +G_DEFINE_TYPE(GisView, gis_view, G_TYPE_OBJECT); +static void gis_view_init(GisView *self) +{ + g_debug("GisView: init"); + /* Default values */ + self->time = g_strdup(""); + self->site = g_strdup(""); + self->location[0] = 40; + self->location[1] = -100; + self->location[2] = 1.5*EARTH_R; + self->rotation[0] = 0; + self->rotation[1] = 0; + self->rotation[2] = 0; +} +static void gis_view_dispose(GObject *gobject) +{ + g_debug("GisView: dispose"); + /* Drop references to other GObjects */ + G_OBJECT_CLASS(gis_view_parent_class)->dispose(gobject); +} +static void gis_view_finalize(GObject *gobject) +{ + g_debug("GisView: finalize"); + GisView *self = GIS_VIEW(gobject); + g_free(self->time); + g_free(self->site); + G_OBJECT_CLASS(gis_view_parent_class)->finalize(gobject); +} +static void gis_view_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + g_debug("GisView: set_property"); + GisView *self = GIS_VIEW(object); + switch (property_id) { + case PROP_TIME: gis_view_set_time(self, g_value_get_string(value)); break; + case PROP_SITE: gis_view_set_site(self, g_value_get_string(value)); break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } +} +static void gis_view_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + g_debug("GisView: get_property"); + GisView *self = GIS_VIEW(object); + switch (property_id) { + case PROP_TIME: g_value_set_string(value, gis_view_get_time(self)); break; + case PROP_SITE: g_value_set_string(value, gis_view_get_site(self)); break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } +} +static void gis_view_class_init(GisViewClass *klass) +{ + g_debug("GisView: class_init"); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + gobject_class->dispose = gis_view_dispose; + gobject_class->finalize = gis_view_finalize; + gobject_class->get_property = gis_view_get_property; + gobject_class->set_property = gis_view_set_property; + g_object_class_install_property(gobject_class, PROP_TIME, + g_param_spec_pointer( + "time", + "time of the current frame", + "(format unknown)", + G_PARAM_READWRITE)); + g_object_class_install_property(gobject_class, PROP_SITE, + g_param_spec_pointer( + "site", + "site seen by the viewport", + "Site of the viewport. Currently this is the name of the radar site.", + G_PARAM_READWRITE)); + signals[SIG_TIME_CHANGED] = g_signal_new( + "time-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + signals[SIG_SITE_CHANGED] = g_signal_new( + "site-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + signals[SIG_LOCATION_CHANGED] = g_signal_new( + "location-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE, + G_TYPE_NONE, + 3, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + signals[SIG_ROTATION_CHANGED] = g_signal_new( + "rotation-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE, + G_TYPE_NONE, + 3, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); +} diff --git a/src/gis/gis-view.h b/src/gis/gis-view.h index 4608788..3eb57ad 100644 --- a/src/gis/gis-view.h +++ b/src/gis/gis-view.h @@ -55,9 +55,9 @@ GisView *gis_view_new(); void gis_view_set_time(GisView *view, const gchar *time); gchar *gis_view_get_time(GisView *view); -void gis_view_set_location(GisView *view, gdouble x, gdouble y, gdouble z); -void gis_view_get_location(GisView *view, gdouble *x, gdouble *y, gdouble *z); -void gis_view_pan (GisView *view, gdouble x, gdouble y, gdouble z); +void gis_view_set_location(GisView *view, gdouble lat, gdouble lon, gdouble elev); +void gis_view_get_location(GisView *view, gdouble *lat, gdouble *lon, gdouble *elev); +void gis_view_pan (GisView *view, gdouble lat, gdouble lon, gdouble elev); void gis_view_zoom (GisView *view, gdouble scale); void gis_view_set_rotation(GisView *view, gdouble x, gdouble y, gdouble z); diff --git a/src/gis/gis-world.c b/src/gis/gis-world.c index 9d7ecb2..b8611ab 100644 --- a/src/gis/gis-world.c +++ b/src/gis/gis-world.c @@ -16,13 +16,11 @@ */ #include +#include #include "gis-marshal.h" #include "gis-world.h" -/**************** - * GObject code * - ****************/ /* Constants */ double WGS884_SEMI_MAJOR = 6378137.0; // a double WGS884_SEMI_MINOR = 6356752.314245; // b @@ -35,7 +33,52 @@ enum { }; static guint signals[NUM_SIGNALS]; -/* Class/Object init */ + +/* Signal helpers */ +static void _gis_world_emit_refresh(GisWorld *world) +{ + g_signal_emit(world, signals[SIG_REFRESH], 0); +} +static void _gis_world_emit_offline(GisWorld *world) +{ + g_signal_emit(world, signals[SIG_OFFLINE], 0, + world->offline); +} + +/*********** + * Methods * + ***********/ +GisWorld *gis_world_new() +{ + g_debug("GisWorld: new"); + return g_object_new(GIS_TYPE_WORLD, NULL); +} + +void gis_world_refresh(GisWorld *world) +{ + g_debug("GisWorld: refresh"); + _gis_world_emit_refresh(world); +} + +void gis_world_set_offline(GisWorld *world, gboolean offline) +{ + g_assert(GIS_IS_WORLD(world)); + g_debug("GisWorld: set_offline - %d", offline); + world->offline = offline; + _gis_world_emit_offline(world); +} + +gboolean gis_world_get_offline(GisWorld *world) +{ + g_assert(GIS_IS_WORLD(world)); + g_debug("GisWorld: get_offline - %d", world->offline); + return world->offline; +} + + +/**************** + * GObject code * + ****************/ G_DEFINE_TYPE(GisWorld, gis_world, G_TYPE_OBJECT); static void gis_world_init(GisWorld *self) { @@ -84,46 +127,42 @@ static void gis_world_class_init(GisWorldClass *klass) G_TYPE_BOOLEAN); } -/* Signal helpers */ -static void _gis_world_emit_refresh(GisWorld *world) -{ - g_signal_emit(world, signals[SIG_REFRESH], 0); -} -static void _gis_world_emit_offline(GisWorld *world) -{ - g_signal_emit(world, signals[SIG_OFFLINE], 0, - world->offline); -} - -/*********** - * Methods * - ***********/ -GisWorld *gis_world_new() +/****************** + * Global helpers * + ******************/ +void lle2xyz(gdouble lat, gdouble lon, gdouble elev, + gdouble *x, gdouble *y, gdouble *z) { - g_debug("GisWorld: new"); - return g_object_new(GIS_TYPE_WORLD, NULL); + gdouble rad = elev2rad(elev); + gdouble azim = lon2azim(lon); + gdouble incl = lat2incl(lat); + *z = rad * cos(azim) * sin(incl); + *x = rad * sin(azim) * sin(incl); + *y = rad * cos(incl); } -void gis_world_refresh(GisWorld *world) +void xyz2lle(gdouble x, gdouble y, gdouble z, + gdouble *lat, gdouble *lon, gdouble *elev) { - g_debug("GisWorld: refresh"); - _gis_world_emit_refresh(world); + gdouble rad = sqrt(x*x + y*y + z*z); + *lat = incl2lat(acos(y / rad)); + *lon = azim2lon(atan2(x,z)); + *elev = rad2elev(rad); } -void gis_world_set_offline(GisWorld *world, gboolean offline) + +gdouble ll2m(gdouble lon_dist, gdouble lat) { - g_assert(GIS_IS_WORLD(world)); - g_debug("GisWorld: set_offline - %d", offline); - world->offline = offline; - _gis_world_emit_offline(world); + gdouble azim = (-lat+90)/180*M_PI; + gdouble rad = sin(azim) * EARTH_R; + gdouble circ = 2 * M_PI * rad; + return lon_dist/360 * circ; } -gboolean gis_world_get_offline(GisWorld *world) +gdouble distd(gdouble *a, gdouble *b) { - g_assert(GIS_IS_WORLD(world)); - g_debug("GisWorld: get_offline - %d", world->offline); - return world->offline; + return sqrt((a[0]-b[0])*(a[0]-b[0]) + + (a[1]-b[1])*(a[1]-b[1]) + + (a[2]-b[2])*(a[2]-b[2])); } - - diff --git a/src/gis/gis-world.h b/src/gis/gis-world.h index 64e52a2..78ca130 100644 --- a/src/gis/gis-world.h +++ b/src/gis/gis-world.h @@ -20,7 +20,79 @@ #include -/* Type macros */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define EARTH_R (6371000) +#define EARTH_C (2*M_PI*EARTH_R) + +/** + * Terms + * ----- + * deg - Degrees + * rad - Radians, also radius + * m - Meters, for earth-based distances + * px - Pixels, for screen-based distances + * + * height - Height, the distance above the geoid (ground) + * elev - Elevation, the distance above the spheroid + * rad - Radius, the distance from the center of the earth + * + * lat - Latitude, amount north-south, -90 (S) .. 90 (N) + * lon - Longitude, amount east-west, -180 (W) .. 180 (E) + * incl - Inclination, polar equiv of latitude, Pi .. 0 + * azim - Azimuth, polar equiv of longitude, -Pi .. Pi + * + * x - 0° lon is positive + * y - 90° lon is positive + * z - North pole is positive + * + * llh - lat,lon,height + * lle - lat,lon,elev + * llr - lat,lon,rad + * pol - incl,azim,rad + * xyz - x,y,z + */ + +/** + * lat lon elev -> x y z + * lle2xyz: 0.0, 0.0, 0.0 -> 0.0, 0.0, 10.0 + * lle2xyz: 90.0, 0.0, 0.0 -> 0.0, 10.0, 0.0 + * lle2xyz: 0.0, 90.0, 0.0 -> 10.0, 0.0, 0.0 + * + * x y z -> lat lon elev + * xyz2lle: 10.0, 0.0, 0.0 -> 0.0, 90.0, 0.0 + * xyz2lle: 0.0, 10.0, 0.0 -> 90.0, 0.0, 0.0 + * xyz2lle: 0.0, 0.0, 10.0 -> 0.0, 0.0, 0.0 + */ + +#define azim2lon(azim) ((azim)*180/M_PI) +#define lon2azim(lon) ((lon)*M_PI/180) +#define incl2lat(incl) (90-(incl)*180/M_PI) +#define lat2incl(lat) ((90-(lat))*M_PI/180) +#define rad2elev(rad) ((rad)-EARTH_R) +#define elev2rad(elev) ((elev)+EARTH_R) + +#define deg2rad(deg) (((deg)*M_PI)/180.0) +#define rad2deg(rad) (((rad)*180.0)/M_PI) + +#define FOV_DIST 2000.0 +#define MPPX(dist) (4*dist/FOV_DIST) + +void lle2xyz(gdouble lat, gdouble lon, gdouble elev, + gdouble *x, gdouble *y, gdouble *z); + +void xyz2lle(gdouble x, gdouble y, gdouble z, + gdouble *lat, gdouble *lon, gdouble *elev); + +gdouble ll2m(gdouble lon_dist, gdouble lat); + +gdouble distd(gdouble *a, gdouble *b); + +/************ + * GisWorld * + ************/ #define GIS_TYPE_WORLD (gis_world_get_type()) #define GIS_WORLD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GIS_TYPE_WORLD, GisWorld)) #define GIS_IS_WORLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GIS_TYPE_WORLD)) diff --git a/src/gis/gis_test.c b/src/gis/gis_test.c new file mode 100644 index 0000000..fbe46a9 --- /dev/null +++ b/src/gis/gis_test.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 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 "gis.h" + +/************* + * Callbacks * + *************/ +static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer _) +{ + g_debug("gis: on_key_press - key=%x, state=%x", + event->keyval, event->state); + switch (event->keyval) { + case GDK_q: + gtk_widget_destroy(widget); + return TRUE; + } + return FALSE; +} + +/*********** + * Methods * + ***********/ +int main(int argc, char **argv) +{ + gtk_init(&argc, &argv); + g_thread_init(NULL); + + GisPrefs *prefs = gis_prefs_new("aweather"); + GisPlugins *plugins = gis_plugins_new(); + GisWorld *world = gis_world_new(); + GisView *view = gis_view_new(); + GisOpenGL *opengl = gis_opengl_new(world, view, plugins); + + //gis_plugins_load(plugins, "radar", world, view, opengl, prefs); + //gis_plugins_load(plugins, "ridge", world, view, opengl, prefs); + //gis_plugins_load(plugins, "example", world, view, opengl, prefs); + + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(opengl)); + gtk_widget_show_all(window); + + gis_view_set_site(view, "KLSX"); + gtk_main(); + + g_object_unref(prefs); + g_object_unref(world); + g_object_unref(view); + g_object_unref(opengl); + gis_plugins_free(plugins); + return 0; +} diff --git a/src/gis/gpqueue.c b/src/gis/gpqueue.c new file mode 100644 index 0000000..906181c --- /dev/null +++ b/src/gis/gpqueue.c @@ -0,0 +1,567 @@ +#include +#include "gpqueue.h" + +/** + * SECTION:priority_queues + * @short_description: a collection of data entries with associated priority + * values that returns entries one by one in order of priority + * + * + * The #GPQueue structure and its associated functions provide a sorted + * collection of objects. Entries can be inserted in any order and at any time, + * and an entry's priority can be changed after it has been inserted into the + * queue. Entries are supposed to be removed one at a time in order of priority + * with g_pqueue_pop(), but deleting entries out of order is possible. + * + * + * The entries cannot be iterated over in any way other + * than removing them one by one in order of priority, but when doing that, + * this structure is far more efficient than sorted lists or balanced trees, + * which on the other hand do not suffer from this restriction. + * + * + * You will want to be very careful with calls that use #GPQueueHandle. + * Handles immediately become invalid when an entry is removed from a #GPQueue, + * but the current implementation cannot detect this and will do unfortunate + * things to undefined memory locations if you try to use an invalid handle. + * + * + * + * Internally, #GPQueue currently uses a Fibonacci heap to store + * the entries. This implementation detail may change. + * + * + **/ + +struct _GPQueueNode { + GPQueueNode *next; + GPQueueNode *prev; + GPQueueNode *parent; + GPQueueNode *child; + + gpointer data; + + gint degree; + gboolean marked; +}; + +struct _GPQueue { + GPQueueNode *root; + GCompareDataFunc cmp; + gpointer *cmpdata; +}; + +/** + * g_pqueue_new: + * @compare_func: the #GCompareDataFunc used to sort the new priority queue. + * This function is passed two elements of the queue and should return 0 if + * they are equal, a negative value if the first comes before the second, and + * a positive value if the second comes before the first. + * @compare_userdata: user data passed to @compare_func + * + * Creates a new #GPQueue. + * + * Returns: a new #GPQueue. + * + * Since: 2.x + **/ +GPQueue* +g_pqueue_new (GCompareDataFunc compare_func, + gpointer *compare_userdata) +{ + g_return_val_if_fail (compare_func != NULL, NULL); + + GPQueue *pqueue = g_slice_new (GPQueue); + pqueue->root = NULL; + pqueue->cmp = compare_func; + pqueue->cmpdata = compare_userdata; + return pqueue; +} + +/** + * g_pqueue_is_empty: + * @pqueue: a #GPQueue. + * + * Returns %TRUE if the queue is empty. + * + * Returns: %TRUE if the queue is empty. + * + * Since: 2.x + **/ +gboolean +g_pqueue_is_empty (GPQueue *pqueue) +{ + return (pqueue->root == NULL); +} + +static void +g_pqueue_node_foreach (GPQueueNode *node, + GPQueueNode *stop, + GFunc func, + gpointer user_data) +{ + if (node == NULL || node == stop) return; + func(node->data, user_data); + if (stop == NULL) stop = node; + g_pqueue_node_foreach (node->next, stop, func, user_data); + g_pqueue_node_foreach (node->child, NULL, func, user_data); +} + +/** + * g_pqueue_foreach: + * @pqueue: a #GQueue. + * @func: the function to call for each element's data + * @user_data: user data to pass to func + * + * Calls func for each element in the pqueue passing user_data to the function. + * + * Since: 2.x + */ +void +g_pqueue_foreach (GPQueue *pqueue, + GFunc func, + gpointer user_data) +{ + g_pqueue_node_foreach (pqueue->root, NULL, func, user_data); +} + +static inline gint +cmp (GPQueue *pqueue, + GPQueueNode *a, + GPQueueNode *b) +{ + return pqueue->cmp (a->data, b->data, pqueue->cmpdata); +} + +static inline void +g_pqueue_node_cut (GPQueueNode *src) +{ + src->prev->next = src->next; + src->next->prev = src->prev; + src->next = src; + src->prev = src; +} + +static inline void +g_pqueue_node_insert_before (GPQueueNode *dest, + GPQueueNode *src) +{ + GPQueueNode *prev; + + prev = dest->prev; + dest->prev = src->prev; + src->prev->next = dest; + src->prev = prev; + prev->next = src; +} + +static inline void +g_pqueue_node_insert_after (GPQueueNode *dest, + GPQueueNode *src) +{ + GPQueueNode *next; + + next = dest->next; + dest->next = src; + src->prev->next = next; + next->prev = src->prev; + src->prev = dest; +} + +/** + * g_pqueue_push: + * @pqueue: a #GPQueue. + * @data: the object to insert into the priority queue. + * + * Inserts a new entry into a #GPQueue. + * + * The returned handle can be used in calls to g_pqueue_remove() and + * g_pqueue_priority_changed(). Never make such calls for entries that have + * already been removed from the queue. The same @data can be inserted into + * a #GPQueue more than once, but remember that in this case, + * g_pqueue_priority_changed() needs to be called for + * every handle for that object if its priority changes. + * + * Returns: a handle for the freshly inserted entry. + * + * Since: 2.x + **/ +GPQueueHandle +g_pqueue_push (GPQueue *pqueue, + gpointer data) +{ + GPQueueNode *e; + + e = g_slice_new (GPQueueNode); + e->next = e; + e->prev = e; + e->parent = NULL; + e->child = NULL; + e->data = data; + e->degree = 0; + e->marked = FALSE; + + if (pqueue->root != NULL) { + g_pqueue_node_insert_before (pqueue->root, e); + if (cmp (pqueue, e, pqueue->root) < 0) + pqueue->root = e; + } else { + pqueue->root = e; + } + + return e; +} + +/** + * g_pqueue_peek: + * @pqueue: a #GPQueue. + * + * Returns the topmost entry's data pointer, or %NULL if the queue is empty. + * + * If you need to tell the difference between an empty queue and a queue + * that happens to have a %NULL pointer at the top, check if the queue is + * empty first. + * + * Returns: the topmost entry's data pointer, or %NULL if the queue is empty. + * + * Since: 2.x + **/ +gpointer +g_pqueue_peek (GPQueue *pqueue) +{ + return (pqueue->root != NULL) ? pqueue->root->data : NULL; +} + +static inline GPQueueNode* +g_pqueue_make_child (GPQueueNode *a, + GPQueueNode *b) +{ + g_pqueue_node_cut(b); + if (a->child != NULL) { + g_pqueue_node_insert_before (a->child, b); + a->degree += 1; + } else { + a->child = b; + a->degree = 1; + } + b->parent = a; + return a; +} + +static inline GPQueueNode* +g_pqueue_join_trees (GPQueue *pqueue, + GPQueueNode *a, + GPQueueNode *b) +{ + if (cmp (pqueue, a, b) < 0) + return g_pqueue_make_child (a, b); + return g_pqueue_make_child (b, a); +} + +static void +g_pqueue_fix_rootlist (GPQueue* pqueue) +{ + gsize degnode_size; + GPQueueNode **degnode; + GPQueueNode sentinel; + GPQueueNode *current; + GPQueueNode *minimum; + + /* We need to iterate over the circular list we are given and do + * several things: + * - Make sure all the elements are unmarked + * - Make sure to return the element in the list with smallest + * priority value + * - Find elements of identical degree and join them into trees + * The last point is irrelevant for correctness, but essential + * for performance. If we did not do this, our data structure would + * degrade into an unsorted linked list. + */ + + degnode_size = (8 * sizeof(gpointer) + 1) * sizeof(gpointer); + degnode = g_slice_alloc0 (degnode_size); + + sentinel.next = &sentinel; + sentinel.prev = &sentinel; + g_pqueue_node_insert_before (pqueue->root, &sentinel); + + current = pqueue->root; + while (current != &sentinel) { + current->marked = FALSE; + current->parent = NULL; + gint d = current->degree; + if (degnode[d] == NULL) { + degnode[d] = current; + current = current->next; + } else { + if (degnode[d] != current) { + current = g_pqueue_join_trees (pqueue, degnode[d], current); + degnode[d] = NULL; + } else { + current = current->next; + } + } + } + + current = sentinel.next; + minimum = current; + while (current != &sentinel) { + if (cmp (pqueue, current, minimum) < 0) + minimum = current; + current = current->next; + } + pqueue->root = minimum; + + g_pqueue_node_cut (&sentinel); + + g_slice_free1 (degnode_size, degnode); +} + +static void +g_pqueue_remove_root (GPQueue *pqueue, + GPQueueNode *root) +{ + /* This removes a node at the root _level_ of the structure, which can be, + * but does not have to be, the actual pqueue->root node. That is why + * we require an explicit pointer to the node to be removed instead of just + * removing pqueue->root implictly. + */ + + /* Step one: + * If root has any children, pull them up to root level. + * At this time, we only deal with their next/prev pointers, + * further changes are made later in g_pqueue_fix_rootlist(). + */ + if (root->child) { + g_pqueue_node_insert_after (root, root->child); + root->child = NULL; + root->degree = 0; + } + + /* Step two: + * Cut root out of the list. + */ + if (root->next != root) { + pqueue->root = root->next; + g_pqueue_node_cut (root); + /* Step three: + * Clean up the remaining list. + */ + g_pqueue_fix_rootlist (pqueue); + } else { + pqueue->root = NULL; + } + + g_slice_free (GPQueueNode, root); +} + +/** + * g_pqueue_pop: + * @pqueue: a #GPQueue. + * + * Removes the topmost entry from a #GPQueue and returns its data pointer. + * Calling this on an empty #GPQueue is not an error, but removes nothing + * and returns %NULL. + * + * If you need to tell the difference between an empty queue and a queue + * that happens to have a %NULL pointer at the top, check if the queue is + * empty first. + * + * Returns: the topmost entry's data pointer, or %NULL if the queue was empty. + * + * Since: 2.x + **/ +gpointer +g_pqueue_pop (GPQueue *pqueue) +{ + gpointer data; + + if (pqueue->root == NULL) return NULL; + data = pqueue->root->data; + g_pqueue_remove_root (pqueue, pqueue->root); + return data; +} + +static inline void +g_pqueue_make_root (GPQueue *pqueue, + GPQueueNode *entry) +{ + /* This moves a node up to the root _level_ of the structure. + * It does not always become the actual root element (pqueue->root). + */ + + GPQueueNode *parent; + + parent = entry->parent; + entry->parent = NULL; + entry->marked = FALSE; + if (parent != NULL) { + if (entry->next != entry) { + if (parent->child == entry) parent->child = entry->next; + g_pqueue_node_cut (entry); + parent->degree -= 1; + } else { + parent->child = NULL; + parent->degree = 0; + } + g_pqueue_node_insert_before (pqueue->root, entry); + } + + if (cmp (pqueue, entry, pqueue->root) < 0) + pqueue->root = entry; +} + +static void +g_pqueue_cut_tree (GPQueue *pqueue, + GPQueueNode *entry) +{ + /* This function moves an entry up to the root level of the structure. + * It extends g_pqueue_make_root() in that the entry's parent, grandparent + * etc. may also be moved to the root level if they are "marked". This is + * not essential for correctness, it just maintains the so-called "potential" + * of the structure, which is necessary for the amortized runtime analysis. + */ + + GPQueueNode *current; + GPQueueNode *parent; + + current = entry; + while ((current != NULL) && (current->parent != NULL)) { + parent = current->parent; + g_pqueue_make_root (pqueue, entry); + if (parent->marked) { + current = parent; + } else { + parent->marked = TRUE; + current = NULL; + } + } + if (cmp (pqueue, entry, pqueue->root) < 0) + pqueue->root = entry; +} + +/** + * g_pqueue_remove: + * @pqueue: a #GPQueue. + * @entry: a #GPQueueHandle for an entry in @pqueue. + * + * Removes one entry from a #GPQueue. + * + * Make sure that @entry refers to an entry that is actually part of + * @pqueue at the time, otherwise the behavior of this function is + * undefined (expect crashes). + * + * Since: 2.x + **/ +void +g_pqueue_remove (GPQueue* pqueue, + GPQueueHandle entry) +{ + g_pqueue_cut_tree (pqueue, entry); + g_pqueue_remove_root (pqueue, entry); +} + +/** + * g_pqueue_priority_changed: + * @pqueue: a #GPQueue. + * @entry: a #GPQueueHandle for an entry in @pqueue. + * + * Notifies the #GPQueue that the priority of one entry has changed. + * The internal representation is updated accordingly. + * + * Make sure that @entry refers to an entry that is actually part of + * @pqueue at the time, otherwise the behavior of this function is + * undefined (expect crashes). + * + * Do not attempt to change the priorities of several entries at once. + * Every time a single object is changed, the #GPQueue needs to be updated + * by calling g_pqueue_priority_changed() for that object. + * + * Since: 2.x + **/ +void +g_pqueue_priority_changed (GPQueue* pqueue, + GPQueueHandle entry) +{ + g_pqueue_cut_tree (pqueue, entry); + + if (entry->child) { + g_pqueue_node_insert_after (entry, entry->child); + entry->child = NULL; + entry->degree = 0; + } + + g_pqueue_fix_rootlist (pqueue); +} + +/** + * g_pqueue_priority_decreased: + * @pqueue: a #GPQueue. + * @entry: a #GPQueueHandle for an entry in @pqueue. + * + * Notifies the #GPQueue that the priority of one entry has + * decreased. + * + * This is a special case of g_pqueue_priority_changed(). If you are absolutely + * sure that the new priority of @entry is lower than it was before, you + * may call this function instead of g_pqueue_priority_changed(). + * + * + * + * In the current implementation, an expensive step in + * g_pqueue_priority_changed() can be skipped if the new priority is known + * to be lower, leading to an amortized running time of O(1) instead of + * O(log n). Of course, if the priority is not actually lower, behavior + * is undefined. + * + * + * + * Since: 2.x + **/ +void +g_pqueue_priority_decreased (GPQueue* pqueue, + GPQueueHandle entry) +{ + g_pqueue_cut_tree (pqueue, entry); +} + +static void +g_pqueue_node_free_all (GPQueueNode *node) +{ + if (node == NULL) return; + g_pqueue_node_free_all (node->child); + node->prev->next = NULL; + g_pqueue_node_free_all (node->next); + g_slice_free (GPQueueNode, node); +} + +/** + * g_pqueue_clear: + * @pqueue: a #GPQueue. + * + * Removes all entries from a @pqueue. + * + * Since: 2.x + **/ +void +g_pqueue_clear (GPQueue* pqueue) +{ + g_pqueue_node_free_all (pqueue->root); + pqueue->root = NULL; +} + +/** + * g_pqueue_free: + * @pqueue: a #GPQueue. + * + * Deallocates the memory used by @pqueue itself, but not any memory pointed + * to by the data pointers of its entries. + * + * Since: 2.x + **/ +void +g_pqueue_free (GPQueue* pqueue) +{ + g_pqueue_clear (pqueue); + g_slice_free (GPQueue, pqueue); +} diff --git a/src/gis/gpqueue.h b/src/gis/gpqueue.h new file mode 100644 index 0000000..87f6cf9 --- /dev/null +++ b/src/gis/gpqueue.h @@ -0,0 +1,61 @@ +#if defined(G_DISABLE_SINGLE_INCLUDES) && !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __G_PQUEUE_H__ +#define __G_PQUEUE_H__ + +G_BEGIN_DECLS + +typedef struct _GPQueueNode GPQueueNode; + +/** + * GPQueue: + * + * An opaque structure representing a priority queue. + * + * Since: 2.x + **/ +typedef struct _GPQueue GPQueue; + +/** + * GPQueueHandle: + * + * An opaque value representing one entry in a #GPQueue. + * + * Since: 2.x + **/ +typedef GPQueueNode* GPQueueHandle; + +GPQueue* g_pqueue_new (GCompareDataFunc compare_func, + gpointer *compare_userdata); + +void g_pqueue_free (GPQueue* pqueue); + +gboolean g_pqueue_is_empty (GPQueue *pqueue); + +void g_pqueue_foreach (GPQueue *pqueue, + GFunc func, + gpointer user_data); + +GPQueueHandle g_pqueue_push (GPQueue *pqueue, + gpointer data); + +gpointer g_pqueue_peek (GPQueue *pqueue); + +gpointer g_pqueue_pop (GPQueue *pqueue); + +void g_pqueue_remove (GPQueue* pqueue, + GPQueueHandle entry); + +void g_pqueue_priority_changed (GPQueue* pqueue, + GPQueueHandle entry); + +void g_pqueue_priority_decreased (GPQueue* pqueue, + GPQueueHandle entry); + +void g_pqueue_clear (GPQueue* pqueue); + +G_END_DECLS + +#endif /* __G_PQUEUE_H__ */ diff --git a/src/gis/roam.c b/src/gis/roam.c new file mode 100644 index 0000000..1a7b4f8 --- /dev/null +++ b/src/gis/roam.c @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2009 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 "gpqueue.h" +#include +#include + +#include "roam.h" + +/** + * TODO: + * - Remove/free unused memory + * - Optimize for memory consumption + * - Profile for computation speed + * - Implement good error algorithm + * - Target polygon count/detail + */ + +/* For GPQueue comparators */ +static gint tri_cmp(RoamTriangle *a, RoamTriangle *b, gpointer data) +{ + if (a->error < b->error) return 1; + else if (a->error > b->error) return -1; + else return 0; +} +static gint dia_cmp(RoamDiamond *a, RoamDiamond *b, gpointer data) +{ + if (a->error < b->error) return -1; + else if (a->error > b->error) return 1; + else return 0; +} + +/************* + * RoamPoint * + *************/ +RoamPoint *roam_point_new(double x, double y, double z) +{ + RoamPoint *self = g_new0(RoamPoint, 1); + self->x = x; + self->y = y; + self->z = z; + return self; +} +void roam_point_add_triangle(RoamPoint *self, RoamTriangle *triangle) +{ + for (int i = 0; i < 3; i++) { + self->norm[i] *= self->refs; + self->norm[i] += triangle->norm[i]; + } + self->refs++; + for (int i = 0; i < 3; i++) + self->norm[i] /= self->refs; +} +void roam_point_remove_triangle(RoamPoint *self, RoamTriangle *triangle) +{ + for (int i = 0; i < 3; i++) { + self->norm[i] *= self->refs; + self->norm[i] -= triangle->norm[i]; + } + self->refs--; + for (int i = 0; i < 3; i++) + self->norm[i] /= self->refs; +} + +/**************** + * RoamTriangle * + ****************/ +RoamTriangle *roam_triangle_new(RoamPoint *l, RoamPoint *m, RoamPoint *r) +{ + RoamTriangle *self = g_new0(RoamTriangle, 1); + + self->p.l = l; + self->p.m = m; + self->p.r = r; + + self->error = 0; + + /* Update normal */ + double pa[3]; + double pb[3]; + pa[0] = self->p.l->x - self->p.m->x; + pa[1] = self->p.l->y - self->p.m->y; + pa[2] = self->p.l->z - self->p.m->z; + + pb[0] = self->p.r->x - self->p.m->x; + pb[1] = self->p.r->y - self->p.m->y; + pb[2] = self->p.r->z - self->p.m->z; + + self->norm[0] = pa[1] * pb[2] - pa[2] * pb[1]; + self->norm[1] = pa[2] * pb[0] - pa[0] * pb[2]; + self->norm[2] = pa[0] * pb[1] - pa[1] * pb[0]; + + double total = sqrt(self->norm[0] * self->norm[0] + + self->norm[1] * self->norm[1] + + self->norm[2] * self->norm[2]); + + self->norm[0] /= total; + self->norm[1] /= total; + self->norm[2] /= total; + + //g_message("roam_triangle_new: %p", self); + return self; +} + +void roam_triangle_add(RoamTriangle *self, + RoamTriangle *left, RoamTriangle *base, RoamTriangle *right, + RoamSphere *sphere) +{ + self->t.l = left; + self->t.b = base; + self->t.r = right; + + roam_point_add_triangle(self->p.l, self); + roam_point_add_triangle(self->p.m, self); + roam_point_add_triangle(self->p.r, self); + + roam_triangle_update_error(self, sphere, NULL); + + self->handle = g_pqueue_push(sphere->triangles, self); +} + +void roam_triangle_remove(RoamTriangle *self, RoamSphere *sphere) +{ + /* Update vertex normals */ + roam_point_remove_triangle(self->p.l, self); + roam_point_remove_triangle(self->p.m, self); + roam_point_remove_triangle(self->p.r, self); + + g_pqueue_remove(sphere->triangles, self->handle); +} + +void roam_triangle_sync_neighbors(RoamTriangle *new, RoamTriangle *old, RoamTriangle *neigh) +{ + if (neigh->t.l == old) neigh->t.l = new; + else if (neigh->t.b == old) neigh->t.b = new; + else if (neigh->t.r == old) neigh->t.r = new; + else g_assert_not_reached(); +} + +gboolean roam_triangle_update_error_visible(RoamPoint *point, double *model, double *proj) +{ + double x, y, z; + int view[4] = {0,0,1,1}; + if (!gluProject(point->x, point->y, point->z, model, proj, view, &x, &y, &z)) { + g_warning("RoamTriangle: update_error_visible - gluProject failed"); + return TRUE; + } + return !(x < 0 || x > 1 || + y < 0 || y > 1 || + z < 0 || z > 1); +} + +void roam_triangle_update_error(RoamTriangle *self, RoamSphere *sphere, GPQueue *triangles) +{ + RoamPoint cur = { + (self->p.l->x + self->p.r->x)/2, + (self->p.l->y + self->p.r->y)/2, + (self->p.l->z + self->p.r->z)/2, + }; + RoamPoint good = cur; + roam_sphere_update_point(sphere, &good); + + //g_message("cur = (%+5.2f %+5.2f %+5.2f) good = (%+5.2f %+5.2f %+5.2f)", + // cur[0], cur[1], cur[2], good[0], good[1], good[2]); + + double model[16], proj[16]; + int view[4]; + glGetError(); + glGetDoublev(GL_MODELVIEW_MATRIX, model); + glGetDoublev(GL_PROJECTION_MATRIX, proj); + glGetIntegerv(GL_VIEWPORT, view); + if (glGetError() != GL_NO_ERROR) { + g_warning("RoamTriangle: update_error - glGet failed"); + return; + } + + double scur[3], sgood[3]; + gluProject( cur.x, cur.y, cur.z, model,proj,view, &scur[0], &scur[1], &scur[2]); + gluProject(good.x,good.y,good.z, model,proj,view, &sgood[0],&sgood[1],&sgood[2]); + + /* Not exactly correct, could be out on both sides (middle in) */ + if (!roam_triangle_update_error_visible(self->p.l, model, proj) && + !roam_triangle_update_error_visible(self->p.m, model, proj) && + !roam_triangle_update_error_visible(self->p.r, model, proj) && + !roam_triangle_update_error_visible(&good, model, proj)) { + self->error = -1; + } else { + self->error = sqrt((scur[0]-sgood[0])*(scur[0]-sgood[0]) + + (scur[1]-sgood[1])*(scur[1]-sgood[1])); + + /* Multiply by size of triangle (todo, do this in screen space) */ + double sa[3], sb[3], sc[3]; + double *a = (double*)self->p.l; + double *b = (double*)self->p.m; + double *c = (double*)self->p.r; + gluProject(a[0],a[1],a[2], model,proj,view, &sa[0],&sa[1],&sa[2]); + gluProject(b[0],b[1],b[2], model,proj,view, &sb[0],&sb[1],&sb[2]); + gluProject(c[0],c[1],c[2], model,proj,view, &sc[0],&sc[1],&sc[2]); + double size = -( sa[0]*(sb[1]-sc[1]) + sb[0]*(sc[1]-sa[1]) + sc[0]*(sa[1]-sb[1]) ) / 2.0; + //g_message("%f * %f = %f", self->error, size, self->error * size); + /* Size < 0 == backface */ + self->error *= size; + } + + if (triangles) + g_pqueue_priority_changed(triangles, self->handle); +} + +void roam_triangle_split(RoamTriangle *self, RoamSphere *sphere) +{ + //g_message("roam_triangle_split: %p, e=%f\n", self, self->error); + + sphere->polys += 2; + + if (self != self->t.b->t.b) + roam_triangle_split(self->t.b, sphere); + + RoamTriangle *base = self->t.b; + + /* Calculate midpoint */ + RoamPoint *mid = roam_point_new( + (self->p.l->x + self->p.r->x)/2, + (self->p.l->y + self->p.r->y)/2, + (self->p.l->z + self->p.r->z)/2 + ); + roam_sphere_update_point(sphere, mid); + + /* Add new triangles */ + RoamTriangle *sl = roam_triangle_new(self->p.m, mid, self->p.l); // Self Left + RoamTriangle *sr = roam_triangle_new(self->p.r, mid, self->p.m); // Self Right + RoamTriangle *bl = roam_triangle_new(base->p.m, mid, base->p.l); // Base Left + RoamTriangle *br = roam_triangle_new(base->p.r, mid, base->p.m); // Base Right + + /* tri,l, base, r, sphere */ + roam_triangle_add(sl, sr, self->t.l, br, sphere); + roam_triangle_add(sr, bl, self->t.r, sl, sphere); + roam_triangle_add(bl, br, base->t.l, sr, sphere); + roam_triangle_add(br, sl, base->t.r, bl, sphere); + + roam_triangle_sync_neighbors(sl, self, self->t.l); + roam_triangle_sync_neighbors(sr, self, self->t.r); + roam_triangle_sync_neighbors(bl, base, base->t.l); + roam_triangle_sync_neighbors(br, base, base->t.r); + + /* Remove old triangles */ + roam_triangle_remove(self, sphere); + roam_triangle_remove(base, sphere); + + /* Add/Remove diamonds */ + RoamDiamond *diamond = roam_diamond_new(self, base, sl, sr, bl, br); + roam_diamond_add(diamond, sphere); + roam_diamond_remove(self->diamond, sphere); + roam_diamond_remove(base->diamond, sphere); +} + +void roam_triangle_draw_normal(RoamTriangle *self) +{ + double center[] = { + (self->p.l->x + self->p.m->x + self->p.r->x)/3.0, + (self->p.l->y + self->p.m->y + self->p.r->y)/3.0, + (self->p.l->z + self->p.m->z + self->p.r->z)/3.0, + }; + double end[] = { + center[0]+self->norm[0]/2, + center[1]+self->norm[1]/2, + center[2]+self->norm[2]/2, + }; + glBegin(GL_LINES); + glVertex3dv(center); + glVertex3dv(end); + glEnd(); +} + +/*************** + * RoamDiamond * + ***************/ +RoamDiamond *roam_diamond_new( + RoamTriangle *parent0, RoamTriangle *parent1, + RoamTriangle *kid0, RoamTriangle *kid1, + RoamTriangle *kid2, RoamTriangle *kid3) +{ + RoamDiamond *self = g_new0(RoamDiamond, 1); + + self->kid[0] = kid0; + self->kid[1] = kid1; + self->kid[2] = kid2; + self->kid[3] = kid3; + + kid0->diamond = self; + kid1->diamond = self; + kid2->diamond = self; + kid3->diamond = self; + + self->parent[0] = parent0; + self->parent[1] = parent1; + + return self; +} +void roam_diamond_add(RoamDiamond *self, RoamSphere *sphere) +{ + self->active = TRUE; + roam_diamond_update_error(self, sphere, NULL); + self->handle = g_pqueue_push(sphere->diamonds, self); +} +void roam_diamond_remove(RoamDiamond *self, RoamSphere *sphere) +{ + if (self && self->active) { + self->active = FALSE; + g_pqueue_remove(sphere->diamonds, self->handle); + } +} +void roam_diamond_merge(RoamDiamond *self, RoamSphere *sphere) +{ + //g_message("roam_diamond_merge: %p, e=%f\n", self, self->error); + + sphere->polys -= 2; + + /* TODO: pick the best split */ + RoamTriangle **kid = self->kid; + + /* Create triangles */ + RoamTriangle *tri = self->parent[0]; + RoamTriangle *base = self->parent[1]; + + roam_triangle_add(tri, kid[0]->t.b, base, kid[1]->t.b, sphere); + roam_triangle_add(base, kid[2]->t.b, tri, kid[3]->t.b, sphere); + + roam_triangle_sync_neighbors(tri, kid[0], kid[0]->t.b); + roam_triangle_sync_neighbors(tri, kid[1], kid[1]->t.b); + roam_triangle_sync_neighbors(base, kid[2], kid[2]->t.b); + roam_triangle_sync_neighbors(base, kid[3], kid[3]->t.b); + + /* Remove triangles */ + roam_triangle_remove(kid[0], sphere); + roam_triangle_remove(kid[1], sphere); + roam_triangle_remove(kid[2], sphere); + roam_triangle_remove(kid[3], sphere); + + /* Add/Remove triangles */ + if (tri->t.l->t.l == tri->t.r->t.r && + tri->t.l->t.l != tri && tri->diamond) + roam_diamond_add(tri->diamond, sphere); + + if (base->t.l->t.l == base->t.r->t.r && + base->t.l->t.l != base && base->diamond) + roam_diamond_add(base->diamond, sphere); + + /* Remove and free diamond and child triangles */ + roam_diamond_remove(self, sphere); + g_assert(self->kid[0]->p.m == self->kid[1]->p.m && + self->kid[1]->p.m == self->kid[2]->p.m && + self->kid[2]->p.m == self->kid[3]->p.m); + g_assert(self->kid[0]->p.m->refs == 0); + g_free(self->kid[0]->p.m); + g_free(self->kid[0]); + g_free(self->kid[1]); + g_free(self->kid[2]); + g_free(self->kid[3]); + g_free(self); +} +void roam_diamond_update_error(RoamDiamond *self, RoamSphere *sphere, GPQueue *diamonds) +{ + roam_triangle_update_error(self->parent[0], sphere, NULL); + roam_triangle_update_error(self->parent[1], sphere, NULL); + self->error = MAX(self->parent[0]->error, self->parent[1]->error); + if (diamonds) + g_pqueue_priority_changed(diamonds, self->handle); +} + +/************** + * RoamSphere * + **************/ +RoamSphere *roam_sphere_new(RoamTriFunc tri_func, RoamHeightFunc height_func, gpointer user_data) +{ + RoamSphere *self = g_new0(RoamSphere, 1); + self->tri_func = tri_func; + self->height_func = height_func; + self->user_data = user_data; + self->polys = 12; + self->triangles = g_pqueue_new((GCompareDataFunc)tri_cmp, NULL); + self->diamonds = g_pqueue_new((GCompareDataFunc)dia_cmp, NULL); + + RoamPoint *vertexes[] = { + roam_point_new( 1, 1, 1), // 0 + roam_point_new( 1, 1,-1), // 1 + roam_point_new( 1,-1, 1), // 2 + roam_point_new( 1,-1,-1), // 3 + roam_point_new(-1, 1, 1), // 4 + roam_point_new(-1, 1,-1), // 5 + roam_point_new(-1,-1, 1), // 6 + roam_point_new(-1,-1,-1), // 7 + }; + int _triangles[][2][3] = { + /*lv mv rv ln, bn, rn */ + {{3,2,0}, {10, 1, 7}}, // 0 + {{0,1,3}, { 9, 0, 2}}, // 1 + {{7,3,1}, {11, 3, 1}}, // 2 + {{1,5,7}, { 8, 2, 4}}, // 3 + {{6,7,5}, {11, 5, 3}}, // 4 + {{5,4,6}, { 8, 4, 6}}, // 5 + {{2,6,4}, {10, 7, 5}}, // 6 + {{4,0,2}, { 9, 6, 0}}, // 7 + {{4,5,1}, { 5, 9, 3}}, // 8 + {{1,0,4}, { 1, 8, 7}}, // 9 + {{6,2,3}, { 6,11, 0}}, // 10 + {{3,7,6}, { 2,10, 4}}, // 11 + }; + RoamTriangle *triangles[12]; + + for (int i = 0; i < 8; i++) + roam_sphere_update_point(self, vertexes[i]); + for (int i = 0; i < 12; i++) + triangles[i] = roam_triangle_new( + vertexes[_triangles[i][0][0]], + vertexes[_triangles[i][0][1]], + vertexes[_triangles[i][0][2]]); + for (int i = 0; i < 12; i++) + roam_triangle_add(triangles[i], + triangles[_triangles[i][1][0]], + triangles[_triangles[i][1][1]], + triangles[_triangles[i][1][2]], + self); + + return self; +} +static void roam_sphere_update_errors_cb(gpointer obj, GPtrArray *ptrs) +{ + g_ptr_array_add(ptrs, obj); +} +void roam_sphere_update_errors(RoamSphere *self) +{ + g_debug("RoamSphere: update_errors - polys=%d", self->polys); + GPtrArray *ptrs = g_ptr_array_new(); + g_pqueue_foreach(self->triangles, (GFunc)roam_sphere_update_errors_cb, ptrs); + for (int i = 0; i < ptrs->len; i++) + roam_triangle_update_error(ptrs->pdata[i], self, self->triangles); + g_ptr_array_free(ptrs, TRUE); + + ptrs = g_ptr_array_new(); + g_pqueue_foreach(self->diamonds, (GFunc)roam_sphere_update_errors_cb, ptrs); + for (int i = 0; i < ptrs->len; i++) + roam_diamond_update_error(ptrs->pdata[i], self, self->diamonds); + g_ptr_array_free(ptrs, TRUE); +} +void roam_sphere_update_point(RoamSphere *self, RoamPoint *point) +{ + self->height_func(point, self->user_data); +} +void roam_sphere_split_one(RoamSphere *self) +{ + RoamTriangle *to_split = g_pqueue_peek(self->triangles); + if (!to_split) return; + roam_triangle_split(to_split, self); +} +void roam_sphere_merge_one(RoamSphere *self) +{ + RoamDiamond *to_merge = g_pqueue_peek(self->diamonds); + if (!to_merge) return; + roam_diamond_merge(to_merge, self); +} +gint roam_sphere_split_merge(RoamSphere *self) +{ + gint iters = 0, max_iters = 500; + gint target = 2000; + while (self->polys < target && iters++ < max_iters) + roam_sphere_split_one(self); + while (self->polys > target && iters++ < max_iters) + roam_sphere_merge_one(self); + while (((RoamTriangle*)g_pqueue_peek(self->triangles))->error > + ((RoamDiamond *)g_pqueue_peek(self->diamonds ))->error && + iters++ < max_iters) { + roam_sphere_split_one(self); + if (((RoamTriangle*)g_pqueue_peek(self->triangles))->error > + ((RoamDiamond *)g_pqueue_peek(self->diamonds ))->error) + roam_sphere_merge_one(self); + } + return iters; +} +void roam_sphere_draw(RoamSphere *self) +{ + g_pqueue_foreach(self->triangles, (GFunc)self->tri_func, self->user_data); +} +void roam_sphere_draw_normals(RoamSphere *self) +{ + g_pqueue_foreach(self->triangles, (GFunc)roam_triangle_draw_normal, NULL); +} +static void roam_sphere_free_tri(RoamTriangle *tri) +{ + if (--tri->p.l->refs == 0) g_free(tri->p.l); + if (--tri->p.m->refs == 0) g_free(tri->p.m); + if (--tri->p.r->refs == 0) g_free(tri->p.r); + g_free(tri); +} +void roam_sphere_free(RoamSphere *self) +{ + /* Slow method, but it should work */ + while (self->polys > 12) + roam_sphere_merge_one(self); + /* TODO: free points */ + g_pqueue_foreach(self->triangles, (GFunc)roam_sphere_free_tri, NULL); + g_pqueue_free(self->triangles); + g_pqueue_free(self->diamonds); + g_free(self); +} diff --git a/src/gis/roam.h b/src/gis/roam.h new file mode 100644 index 0000000..c255812 --- /dev/null +++ b/src/gis/roam.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 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 . + */ + +#ifndef __ROAM_H__ +#define __ROAM_H__ + +#include "gpqueue.h" + +/* Roam */ +typedef struct _RoamPoint RoamPoint; +typedef struct _RoamTriangle RoamTriangle; +typedef struct _RoamDiamond RoamDiamond; +typedef struct _RoamSphere RoamSphere; +typedef void (*RoamTriFunc)(RoamTriangle *triangle, gpointer user_data); +typedef void (*RoamHeightFunc)(RoamPoint *point, gpointer user_data); + +/************* + * RoamPoint * + *************/ +struct _RoamPoint { + double x,y,z; + double coords; // Texture coordinantes + double norm[3]; // Vertex normal + int refs; // Reference count +}; +RoamPoint *roam_point_new(double x, double y, double z); +void roam_point_add_triangle(RoamPoint *point, RoamTriangle *triangle); +void roam_point_remove_triangle(RoamPoint *point, RoamTriangle *triangle); + +/**************** + * RoamTriangle * + ****************/ +struct _RoamTriangle { + struct { RoamPoint *l,*m,*r; } p; + struct { RoamTriangle *l,*b,*r; } t; + RoamDiamond *diamond; + double norm[3]; + double error; + GPQueueHandle handle; +}; +RoamTriangle *roam_triangle_new(RoamPoint *l, RoamPoint *m, RoamPoint *r); +void roam_triangle_add(RoamTriangle *triangle, + RoamTriangle *left, RoamTriangle *base, RoamTriangle *right, + RoamSphere *sphere); +void roam_triangle_remove(RoamTriangle *triangle, RoamSphere *sphere); +void roam_triangle_update_error(RoamTriangle *triangle, RoamSphere *sphere, GPQueue *triangles); +void roam_triangle_split(RoamTriangle *triangle, RoamSphere *sphere); +void roam_triangle_draw_normal(RoamTriangle *triangle); + +/*************** + * RoamDiamond * + ***************/ +struct _RoamDiamond { + RoamTriangle *kid[4]; + RoamTriangle *parent[2]; + double error; + gboolean active; + GPQueueHandle handle; +}; +RoamDiamond *roam_diamond_new( + RoamTriangle *parent0, RoamTriangle *parent1, + RoamTriangle *kid0, RoamTriangle *kid1, + RoamTriangle *kid2, RoamTriangle *kid3); +void roam_diamond_add(RoamDiamond *diamond, RoamSphere *sphere); +void roam_diamond_remove(RoamDiamond *diamond, RoamSphere *sphere); +void roam_diamond_merge(RoamDiamond *diamond, RoamSphere *sphere); +void roam_diamond_update_error(RoamDiamond *self, RoamSphere *sphere, GPQueue *diamonds); + +/************** + * RoamSphere * + **************/ +struct _RoamSphere { + GPQueue *triangles; + GPQueue *diamonds; + RoamTriFunc tri_func; + RoamHeightFunc height_func; + gpointer user_data; + gint polys; +}; +RoamSphere *roam_sphere_new(RoamTriFunc tri_func, RoamHeightFunc height_func, gpointer user_data); +void roam_sphere_update_errors(RoamSphere *sphere); +void roam_sphere_update_point(RoamSphere *sphere, RoamPoint *point); +void roam_sphere_split_one(RoamSphere *sphere); +void roam_sphere_merge_one(RoamSphere *sphere); +gint roam_sphere_split_merge(RoamSphere *sphere); +void roam_sphere_draw(RoamSphere *sphere); +void roam_sphere_draw_normals(RoamSphere *sphere); +void roam_sphere_free(RoamSphere *sphere); + +#endif diff --git a/src/gis/wms.c b/src/gis/wms.c new file mode 100644 index 0000000..aa54444 --- /dev/null +++ b/src/gis/wms.c @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2009 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 . + */ + +/** + * http://www.nasa.network.com/elev? + * SERVICE=WMS& + * VERSION=1.1.0& + * REQUEST=GetMap& + * LAYERS=bmng200406& + * STYLES=& + * SRS=EPSG:4326& + * BBOX=-180,-90,180,90& + * FORMAT=image/jpeg& + * WIDTH=600& + * HEIGHT=300 + * + * http://www.nasa.network.com/elev? + * SERVICE=WMS& + * VERSION=1.1.0& + * REQUEST=GetMap& + * LAYERS=srtm30& + * STYLES=& + * SRS=EPSG:4326& + * BBOX=-180,-90,180,90& + * FORMAT=application/bil32& + * WIDTH=600& + * HEIGHT=300 + */ + +#include +#include +#include +#include +#include +#include + +#include "wms.h" + +/* TODO: try to remove these */ +#include "gis-world.h" +#include + +gchar *wms_make_uri(WmsInfo *info, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax) +{ + return g_strdup_printf( + "%s?" + "SERVICE=WMS&" + "VERSION=1.1.0&" + "REQUEST=GetMap&" + "LAYERS=%s&" + "STYLES=&" + "SRS=EPSG:4326&" + "BBOX=%f,%f,%f,%f&" + "FORMAT=%s&" + "WIDTH=%d&" + "HEIGHT=%d", + info->uri_prefix, + info->uri_layer, + xmin, ymin, xmax, ymax, + info->uri_format, + info->width, + info->height); +} + +/**************** + * WmsCacheNode * + ****************/ +WmsCacheNode *wms_cache_node_new(gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax) +{ + WmsCacheNode *self = g_new0(WmsCacheNode, 1); + //g_debug("WmsCacheNode: new - %p %+7.3f,%+7.3f,%+7.3f,%+7.3f", + // self, xmin, ymin, xmax, ymax); + self->latlon[0] = xmin; + self->latlon[1] = ymin; + self->latlon[2] = xmax; + self->latlon[3] = ymax; + return self; +} + +void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer) +{ + //g_debug("WmsCacheNode: free - %p", node); + if (node->data) { + freeer(node); + node->data = NULL; + } + for (int x = 0; x < 4; x++) + for (int y = 0; y < 4; y++) + if (node->children[x][y]) + wms_cache_node_free(node->children[x][y], freeer); + g_free(node); +} + +/*********** + * WmsInfo * + ***********/ +WmsInfo *wms_info_new(WmsLoader loader, WmsFreeer freeer, + gchar *uri_prefix, gchar *uri_layer, gchar *uri_format, + gchar *cache_prefix, gchar *cache_ext, + gint resolution, gint width, gint height +) { + WmsInfo *self = g_new0(WmsInfo, 1); + self->loader = loader; + self->freeer = freeer; + self->uri_prefix = uri_prefix; + self->uri_layer = uri_layer; + self->uri_format = uri_format; + self->cache_prefix = cache_prefix; + self->cache_ext = cache_ext; + self->resolution = resolution; + self->width = width; + self->height = height; + + self->max_age = 30; + self->atime = time(NULL); + self->gc_source = g_timeout_add_seconds(1, (GSourceFunc)wms_info_gc, self); + self->cache_root = wms_cache_node_new(-180, -90, 180, 90); + self->soup = soup_session_async_new(); + return self; +} + +struct _CacheImageState { + WmsInfo *info; + gchar *path; + FILE *output; + WmsCacheNode *node; + WmsChunkCallback user_chunk_cb; + WmsDoneCallback user_done_cb; + gpointer user_data; +}; +void wms_info_soup_chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _state) +{ + struct _CacheImageState *state = _state; + if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) + return; + + goffset total = soup_message_headers_get_content_length(message->response_headers); + if (fwrite(chunk->data, chunk->length, 1, state->output) != 1) + g_warning("WmsInfo: soup_chunk_cb - eror writing data"); + + gdouble cur = (gdouble)ftell(state->output); + if (state->user_chunk_cb) + state->user_chunk_cb(cur, total, state->user_data); +} +void wms_info_soup_done_cb(SoupSession *session, SoupMessage *message, gpointer _state) +{ + struct _CacheImageState *state = _state; + if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) + return; + gchar *dest = g_strndup(state->path, strlen(state->path)-5); + g_rename(state->path, dest); + state->node->atime = time(NULL); + state->info->loader(state->node, dest, state->info->width, state->info->height); + if (state->user_done_cb) + state->user_done_cb(state->node, state->user_data); + state->node->caching = FALSE; + fclose(state->output); + g_free(state->path); + g_free(dest); + g_free(state); +} +gboolean wms_info_cache_loader_cb(gpointer _state) +{ + struct _CacheImageState *state = _state; + state->node->atime = time(NULL); + state->info->loader(state->node, state->path, state->info->width, state->info->height); + if (state->user_done_cb) + state->user_done_cb(state->node, state->user_data); + state->node->caching = FALSE; + g_free(state->path); + g_free(state); + return FALSE; +} +/** + * Cache required tiles + * 1. Load closest tile that's stored on disk + * 2. Fetch the correct tile from the remote server + */ +void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon, + WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, + gpointer user_data) +{ + /* Base cache path */ + gdouble x=lon, y=lat; + gdouble xmin=-180, ymin=-90, xmax=180, ymax=90; + gdouble xdist = xmax - xmin; + gdouble ydist = ymax - ymin; + int xpos=0, ypos=0; + gdouble cur_lat = 0; + + WmsCacheNode *target_node = info->cache_root; + WmsCacheNode *approx_node = NULL; + + GString *target_path = g_string_new(g_get_user_cache_dir()); + g_string_append(target_path, G_DIR_SEPARATOR_S); + g_string_append(target_path, "wms"); + g_string_append(target_path, G_DIR_SEPARATOR_S); + g_string_append(target_path, info->cache_prefix); + gchar *approx_path = NULL; + + /* Create nodes to tiles, determine paths and lat-lon coords */ + while (TRUE) { + /* Update the best approximation if it exists on disk */ + gchar *tmp = g_strconcat(target_path->str, info->cache_ext, NULL); + if (g_file_test(tmp, G_FILE_TEST_EXISTS)) { + g_free(approx_path); + approx_node = target_node; + approx_path = tmp; + } else { + g_free(tmp); + } + + /* Break if current resolution (m/px) is good enough */ + if (ll2m(xdist, cur_lat)/info->width < resolution || + ll2m(xdist, cur_lat)/info->width < info->resolution) + break; + + /* Get locations for the correct sub-tile */ + xpos = (int)(((x - xmin) / xdist) * 4); + ypos = (int)(((y - ymin) / ydist) * 4); + if (xpos == 4) xpos--; + if (ypos == 4) ypos--; + xdist /= 4; + ydist /= 4; + xmin = xmin + xdist*(xpos+0); + ymin = ymin + ydist*(ypos+0); + xmax = xmin + xdist; + ymax = ymin + ydist; + cur_lat = MIN(ABS(ymin), ABS(ymax)); + + /* Update target for correct sub-tile */ + g_string_append_printf(target_path, "/%d%d", xpos, ypos); + if (target_node->children[xpos][ypos] == NULL) + target_node->children[xpos][ypos] = + wms_cache_node_new(xmin, ymin, xmax, ymax); + target_node = target_node->children[xpos][ypos]; + } + + /* Load disk on-disk approximation, TODO: async */ + if (approx_node && !approx_node->data && !approx_node->caching) { + approx_node->caching = TRUE; + struct _CacheImageState *state = g_new0(struct _CacheImageState, 1); + state->info = info; + state->path = approx_path; + state->node = approx_node; + state->user_done_cb = done_callback; + state->user_data = user_data; + g_idle_add(wms_info_cache_loader_cb, state); + } else { + g_free(approx_path); + } + + /* If target image is not on-disk, download it now */ + if (target_node != approx_node && !target_node->caching) { + target_node->caching = TRUE; + g_string_append(target_path, info->cache_ext); + g_string_append(target_path, ".part"); + + gchar *dirname = g_path_get_dirname(target_path->str); + g_mkdir_with_parents(dirname, 0755); + g_free(dirname); + + struct _CacheImageState *state = g_new0(struct _CacheImageState, 1); + state->info = info; + state->path = target_path->str; + state->output = fopen(target_path->str, "a"); + state->node = target_node; + state->user_chunk_cb = chunk_callback; + state->user_done_cb = done_callback; + state->user_data = user_data; + + gchar *uri = wms_make_uri(info, xmin, ymin, xmax, ymax); + SoupMessage *message = soup_message_new("GET", uri); + g_signal_connect(message, "got-chunk", G_CALLBACK(wms_info_soup_chunk_cb), state); + soup_message_headers_set_range(message->request_headers, ftell(state->output), -1); + + soup_session_queue_message(info->soup, message, wms_info_soup_done_cb, state); + + g_debug("Caching file: %s -> %s", uri, state->path); + g_free(uri); + g_string_free(target_path, FALSE); + } else { + g_string_free(target_path, TRUE); + } +} +WmsCacheNode *wms_info_fetch(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon, + gboolean *correct) +{ + if (info->cache_root == NULL) { + *correct = FALSE; + return NULL; + } + WmsCacheNode *node = info->cache_root; + WmsCacheNode *best = (node && node->data ? node : NULL); + gdouble x=lon, y=lat; + gdouble xmin=-180, ymin=-90, xmax=180, ymax=90; + gdouble xdist = xmax - xmin; + gdouble ydist = ymax - ymin; + gdouble cur_lat = 0; + int xpos=0, ypos=0; + while (ll2m(xdist, cur_lat)/info->width > resolution && + ll2m(xdist, cur_lat)/info->width > info->resolution) { + + xpos = (int)(((x - xmin) / xdist) * 4); + ypos = (int)(((y - ymin) / ydist) * 4); + //g_message("%d = (int)(((%f - %f) / %f) * 4)", + // xpos, x, xmin, xdist); + if (xpos == 4) xpos--; + if (ypos == 4) ypos--; + xdist /= 4; + ydist /= 4; + xmin = xmin + xdist*(xpos+0); + ymin = ymin + ydist*(ypos+0); + xmax = xmin + xdist; + ymax = ymin + ydist; + cur_lat = MIN(ABS(ymin), ABS(ymax)); + + node = node->children[xpos][ypos]; + if (node == NULL) + break; + if (node->data) + best = node; + } + if (correct) + *correct = (node && node == best); + info->atime = time(NULL); + if (best) + best->atime = info->atime; + return best; +} + +WmsCacheNode *wms_info_fetch_cache(WmsInfo *info, gdouble res, gdouble lat, gdouble lon, + WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, gpointer user_data) +{ + gboolean correct; + WmsCacheNode *node = wms_info_fetch(info, res, lat, lon, &correct); + if (!node || !correct) + wms_info_cache(info, res, lat, lon, chunk_callback, done_callback, user_data); + return node; +} + +/* Delete unused nodes and prune empty branches */ +static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node) +{ + gboolean empty = FALSE; + if (node->data && !node->caching && + self->atime - node->atime > self->max_age) { + g_debug("WmsInfo: gc - expired node %p", node); + self->freeer(node); + node->data = NULL; + empty = TRUE; + } + for (int x = 0; x < 4; x++) + for (int y = 0; y < 4; y++) + if (node->children[x][y]) { + node->children[x][y] = + wms_info_gc_cb(self, node->children[x][y]); + empty = FALSE; + } + if (empty) { + g_debug("WmsInfo: gc - empty branch %p", node); + g_free(node); + return NULL; + } else { + return node; + } +} + +gboolean wms_info_gc(WmsInfo *self) +{ + if (!wms_info_gc_cb(self, self->cache_root)) + g_warning("WmsInfo: gc - root not should not be empty"); + return TRUE; +} + +void wms_info_free(WmsInfo *self) +{ + wms_cache_node_free(self->cache_root, self->freeer); + g_object_unref(self->soup); + g_free(self); +} + + +/************************ + * Blue Marble Next Gen * + ************************/ +void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height) +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL); + node->data = g_new0(guint, 1); + + /* Load image */ + guchar *pixels = gdk_pixbuf_get_pixels(pixbuf); + int alpha = gdk_pixbuf_get_has_alpha(pixbuf); + int nchan = 4; // gdk_pixbuf_get_n_channels(pixbuf); + + /* Create Texture */ + glGenTextures(1, node->data); + glBindTexture(GL_TEXTURE_2D, *(guint*)node->data); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, nchan, width, height, 0, + (alpha ? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, pixels); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + g_object_unref(pixbuf); + g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data); +} +void bmng_opengl_freeer(WmsCacheNode *node) +{ + g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data); + glDeleteTextures(1, node->data); + g_free(node->data); +} + +void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height) +{ + node->data = gdk_pixbuf_new_from_file(path, NULL); + g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data); +} +void bmng_pixbuf_freeer(WmsCacheNode *node) +{ + g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data); + g_object_unref(node->data); +} + +WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer) +{ + loader = loader ?: bmng_opengl_loader; + freeer = freeer ?: bmng_opengl_freeer; + return wms_info_new(loader, freeer, + "http://www.nasa.network.com/wms", "bmng200406", "image/jpeg", + "bmng", ".jpg", 500, 512, 256); +} + +/******************************************** + * Shuttle Radar Topography Mission 30 Plus * + ********************************************/ +void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height) +{ + WmsBil *bil = g_new0(WmsBil, 1); + g_file_get_contents(path, (gchar**)&bil->data, NULL, NULL); + bil->width = width; + bil->height = height; + node->data = bil; + g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data); +} +void srtm_bil_freeer(WmsCacheNode *node) +{ + g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node); + g_free(((WmsBil*)node->data)->data); + g_free(node->data); +} + +void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height) +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height); + guchar *pixels = gdk_pixbuf_get_pixels(pixbuf); + gint stride = gdk_pixbuf_get_rowstride(pixbuf); + + gint16 *data; + g_file_get_contents(path, (gchar**)&data, NULL, NULL); + for (int r = 0; r < height; r++) { + for (int c = 0; c < width; c++) { + gint16 value = data[r*width + c]; + //guchar color = (float)(MAX(value,0))/8848 * 255; + guchar color = (float)value/8848 * 255; + pixels[r*stride + c*3 + 0] = color; + pixels[r*stride + c*3 + 1] = color; + pixels[r*stride + c*3 + 2] = color; + } + } + g_free(data); + + node->data = pixbuf; + g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data); +} +void srtm_pixbuf_freeer(WmsCacheNode *node) +{ + g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node); + g_object_unref(node->data); +} + +WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer) +{ + loader = loader ?: srtm_bil_loader; + freeer = freeer ?: srtm_bil_freeer; + return wms_info_new(loader, freeer, + "http://www.nasa.network.com/elev", "srtm30", "application/bil", + "srtm", ".bil", 500, 512, 256); +} diff --git a/src/gis/wms.h b/src/gis/wms.h new file mode 100644 index 0000000..dfe6b7d --- /dev/null +++ b/src/gis/wms.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 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 . + */ + +#ifndef __WMS_H__ +#define __WMS_H__ + +#include +#include +#include + +typedef struct _WmsCacheNode WmsCacheNode; +typedef struct _WmsInfo WmsInfo; + +typedef void (*WmsChunkCallback)(gsize cur, gsize total, gpointer user_data); +typedef void (*WmsDoneCallback)(WmsCacheNode *node, gpointer user_data); +typedef void (*WmsLoader)(WmsCacheNode *node, const gchar *path, gint width, gint height); +typedef void (*WmsFreeer)(WmsCacheNode *node); + +/**************** + * WmsCacheNode * + ****************/ +struct _WmsCacheNode { + gpointer data; + gdouble latlon[4]; // xmin,ymin,xmax,ymax + gboolean caching; + time_t atime; + WmsCacheNode *children[4][4]; +}; + +WmsCacheNode *wms_cache_node_new(gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax); + +void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer); + +/*********** + * WmsInfo * + ***********/ +struct _WmsInfo { + WmsLoader loader; + WmsFreeer freeer; + gchar *uri_prefix; + gchar *uri_layer; + gchar *uri_format; + gchar *cache_prefix; + gchar *cache_ext; + gint resolution; // m/px + gint width; + gint height; + + guint max_age; + guint gc_source; + time_t atime; + WmsCacheNode *cache_root; + SoupSession *soup; +}; + +WmsInfo *wms_info_new(WmsLoader loader, WmsFreeer freeer, + gchar *uri_prefix, gchar *uri_layer, gchar *uri_format, + gchar *cache_prefix, gchar *cache_ext, + gint resolution, gint width, gint height); + +void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon, + WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, + gpointer user_data); + +WmsCacheNode *wms_info_fetch(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon, + gboolean *correct); + +WmsCacheNode *wms_info_fetch_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon, + WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, + gpointer user_data); + +gboolean wms_info_gc(WmsInfo *self); + +void wms_info_free(WmsInfo *info); + + +/******************************** + * Specific things (bmng, srtm) * + ********************************/ +typedef struct _WmsBil WmsBil; +struct _WmsBil { + gint16 *data; + gint width; + gint height; +}; + +void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height); +void bmng_opengl_freeer(WmsCacheNode *node); + +void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height); +void bmng_pixbuf_freeer(WmsCacheNode *node); + +WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer); + +void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height); +void srtm_bil_freeer(WmsCacheNode *node); + +void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height); +void srtm_pixbuf_freeer(WmsCacheNode *node); + +WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer); + +#endif diff --git a/src/gis/wms_test.c b/src/gis/wms_test.c new file mode 100644 index 0000000..45476c4 --- /dev/null +++ b/src/gis/wms_test.c @@ -0,0 +1,70 @@ +#include +#include + +#include "wms.h" + +/*********** + * Testing * + ***********/ +GtkWidget *image = NULL; +GtkWidget *bar = NULL; + +void done_callback(WmsCacheNode *node, gpointer user_data) +{ + g_message("done_callback: %p->%p", node, node->data); + gtk_image_set_from_pixbuf(GTK_IMAGE(image), node->data); +} + +void chunk_callback(gsize cur, gsize total, gpointer user_data) +{ + g_message("chunk_callback: %d/%d", cur, total); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar), (gdouble)cur/total); +} + +gboolean key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + if (event->keyval == GDK_q) + gtk_main_quit(); + return TRUE; +} + +int main(int argc, char **argv) +{ + gtk_init(&argc, &argv); + g_thread_init(NULL); + + GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GtkWidget *box = gtk_vbox_new(FALSE, 0); + GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); + image = gtk_image_new(); + bar = gtk_progress_bar_new(); + gtk_container_add(GTK_CONTAINER(win), box); + gtk_box_pack_start(GTK_BOX(box), scroll, TRUE, TRUE, 0); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), image); + gtk_box_pack_start(GTK_BOX(box), bar, FALSE, FALSE, 0); + g_signal_connect(win, "key-press-event", G_CALLBACK(key_press_cb), NULL); + g_signal_connect(win, "destroy", gtk_main_quit, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gdouble res = 200, lon = -121.76, lat = 46.85; + + WmsInfo *info = wms_info_new_for_bmng(bmng_pixbuf_loader, bmng_pixbuf_freeer); + wms_info_cache(info, res, lat, lon, NULL, NULL, NULL); + WmsCacheNode *node = wms_info_fetch(info, res, lat, lon, NULL); + if (node) gtk_image_set_from_pixbuf(GTK_IMAGE(image), node->data); + wms_info_cache(info, res, lat, lon, NULL, done_callback, NULL); + wms_info_cache(info, res, lat, lon, chunk_callback, done_callback, NULL); + + //WmsInfo *info = wms_info_new_for_srtm(srtm_pixbuf_loader, srtm_pixbuf_freeer); + //wms_info_cache(info, res, lat, lon, chunk_callback, NULL, NULL); + //WmsCacheNode *node = wms_info_fetch(info, res, lat, lon, NULL); + //if (node) gtk_image_set_from_pixbuf(GTK_IMAGE(image), node->data); + + gtk_widget_show_all(win); + gtk_main(); + + wms_info_free(info); + + return 0; +} diff --git a/src/plugins/example.c b/src/plugins/example.c index 01ef9ca..3d6a668 100644 --- a/src/plugins/example.c +++ b/src/plugins/example.c @@ -24,58 +24,6 @@ #include "example.h" -/**************** - * GObject code * - ****************/ -/* Plugin init */ -static gboolean rotate(gpointer _self); -static void gis_plugin_example_plugin_init(GisPluginInterface *iface); -static void gis_plugin_example_expose(GisPlugin *_self); -static GtkWidget *gis_plugin_example_get_config(GisPlugin *_self); -G_DEFINE_TYPE_WITH_CODE(GisPluginExample, gis_plugin_example, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, - gis_plugin_example_plugin_init)); -static void gis_plugin_example_plugin_init(GisPluginInterface *iface) -{ - g_debug("GisPluginExample: plugin_init"); - /* Add methods to the interface */ - iface->expose = gis_plugin_example_expose; - iface->get_config = gis_plugin_example_get_config; -} -/* Class/Object init */ -static void gis_plugin_example_init(GisPluginExample *self) -{ - g_debug("GisPluginExample: init"); - /* Set defaults */ - self->button = GTK_TOGGLE_BUTTON(gtk_toggle_button_new_with_label("Rotate")); - self->rotate_id = g_timeout_add(1000/60, rotate, self); - self->rotation = 30.0; - self->opengl = NULL; -} -static void gis_plugin_example_dispose(GObject *gobject) -{ - g_debug("GisPluginExample: dispose"); - GisPluginExample *self = GIS_PLUGIN_EXAMPLE(gobject); - g_source_remove(self->rotate_id); - /* Drop references */ - G_OBJECT_CLASS(gis_plugin_example_parent_class)->dispose(gobject); -} -static void gis_plugin_example_finalize(GObject *gobject) -{ - g_debug("GisPluginExample: finalize"); - GisPluginExample *self = GIS_PLUGIN_EXAMPLE(gobject); - /* Free data */ - G_OBJECT_CLASS(gis_plugin_example_parent_class)->finalize(gobject); - -} -static void gis_plugin_example_class_init(GisPluginExampleClass *klass) -{ - g_debug("GisPluginExample: class_init"); - GObjectClass *gobject_class = (GObjectClass*)klass; - gobject_class->dispose = gis_plugin_example_dispose; - gobject_class->finalize = gis_plugin_example_finalize; -} - /*********** * Helpers * ***********/ @@ -89,6 +37,7 @@ static gboolean rotate(gpointer _self) return TRUE; } + /*********** * Methods * ***********/ @@ -111,33 +60,75 @@ static void gis_plugin_example_expose(GisPlugin *_self) { GisPluginExample *self = GIS_PLUGIN_EXAMPLE(_self); g_debug("GisPluginExample: expose"); - glDisable(GL_TEXTURE_2D); - glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); - glOrtho(-1,1,-1,1,-10,10); - glMatrixMode(GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); - float light_ambient[] = {0.1f, 0.1f, 0.0f}; - float light_diffuse[] = {0.9f, 0.9f, 0.9f}; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(1,-1, -1,1, -10,10); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + float light_ambient[] = {0.1f, 0.1f, 0.0f, 1.0f}; + float light_diffuse[] = {0.9f, 0.9f, 0.9f, 1.0f}; float light_position[] = {-30.0f, 50.0f, 40.0f, 1.0f}; glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_POSITION, light_position); - glEnable(GL_LIGHT0); - glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); - glTranslatef(0.5, -0.5, -2); - glRotatef(self->rotation, 1, 0, 1); + glTranslatef(-0.5, -0.5, -2); + glRotatef(self->rotation, 1, 1, 0); glColor4f(0.9, 0.9, 0.7, 1.0); + glDisable(GL_CULL_FACE); gdk_gl_draw_teapot(TRUE, 0.25); - glColor4f(1.0, 1.0, 1.0, 1.0); +} - glDisable(GL_LIGHT0); - glDisable(GL_LIGHTING); - glDisable(GL_COLOR_MATERIAL); - glMatrixMode(GL_PROJECTION); glPopMatrix(); - glMatrixMode(GL_MODELVIEW ); glPopMatrix(); - return; +/**************** + * GObject code * + ****************/ +/* Plugin init */ +static void gis_plugin_example_plugin_init(GisPluginInterface *iface); +G_DEFINE_TYPE_WITH_CODE(GisPluginExample, gis_plugin_example, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, + gis_plugin_example_plugin_init)); +static void gis_plugin_example_plugin_init(GisPluginInterface *iface) +{ + g_debug("GisPluginExample: plugin_init"); + /* Add methods to the interface */ + iface->expose = gis_plugin_example_expose; + iface->get_config = gis_plugin_example_get_config; } +/* Class/Object init */ +static void gis_plugin_example_init(GisPluginExample *self) +{ + g_debug("GisPluginExample: init"); + /* Set defaults */ + self->button = GTK_TOGGLE_BUTTON(gtk_toggle_button_new_with_label("Rotate")); + self->rotate_id = g_timeout_add(1000/60, rotate, self); + self->rotation = 30.0; + self->opengl = NULL; +} +static void gis_plugin_example_dispose(GObject *gobject) +{ + g_debug("GisPluginExample: dispose"); + GisPluginExample *self = GIS_PLUGIN_EXAMPLE(gobject); + g_source_remove(self->rotate_id); + /* Drop references */ + G_OBJECT_CLASS(gis_plugin_example_parent_class)->dispose(gobject); +} +static void gis_plugin_example_finalize(GObject *gobject) +{ + g_debug("GisPluginExample: finalize"); + GisPluginExample *self = GIS_PLUGIN_EXAMPLE(gobject); + /* Free data */ + G_OBJECT_CLASS(gis_plugin_example_parent_class)->finalize(gobject); +} +static void gis_plugin_example_class_init(GisPluginExampleClass *klass) +{ + g_debug("GisPluginExample: class_init"); + GObjectClass *gobject_class = (GObjectClass*)klass; + gobject_class->dispose = gis_plugin_example_dispose; + gobject_class->finalize = gis_plugin_example_finalize; +} diff --git a/src/plugins/radar.c b/src/plugins/radar.c index 26403ea..9c76a57 100644 --- a/src/plugins/radar.c +++ b/src/plugins/radar.c @@ -28,59 +28,6 @@ #include "radar.h" #include "marching.h" -/**************** - * GObject code * - ****************/ -/* Plugin init */ -static void gis_plugin_radar_plugin_init(GisPluginInterface *iface); -static void gis_plugin_radar_expose(GisPlugin *_radar); -static GtkWidget *gis_plugin_radar_get_config(GisPlugin *_self); -G_DEFINE_TYPE_WITH_CODE(GisPluginRadar, gis_plugin_radar, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, - gis_plugin_radar_plugin_init)); -static void gis_plugin_radar_plugin_init(GisPluginInterface *iface) -{ - g_debug("GisPluginRadar: plugin_init"); - /* Add methods to the interface */ - iface->expose = gis_plugin_radar_expose; - iface->get_config = gis_plugin_radar_get_config; -} -/* Class/Object init */ -static void gis_plugin_radar_init(GisPluginRadar *self) -{ - g_debug("GisPluginRadar: class_init"); - /* Set defaults */ - self->soup = NULL; - self->cur_triangles = NULL; - self->cur_num_triangles = 0; - - self->config_body = gtk_alignment_new(0, 0, 1, 1); - gtk_container_set_border_width(GTK_CONTAINER(self->config_body), 5); - gtk_container_add(GTK_CONTAINER(self->config_body), gtk_label_new("No radar loaded")); -} -static void gis_plugin_radar_dispose(GObject *gobject) -{ - g_debug("GisPluginRadar: dispose"); - GisPluginRadar *self = GIS_PLUGIN_RADAR(gobject); - g_signal_handler_disconnect(self->view, self->time_changed_id); - /* Drop references */ - G_OBJECT_CLASS(gis_plugin_radar_parent_class)->dispose(gobject); -} -static void gis_plugin_radar_finalize(GObject *gobject) -{ - g_debug("GisPluginRadar: finalize"); - GisPluginRadar *self = GIS_PLUGIN_RADAR(gobject); - /* Free data */ - G_OBJECT_CLASS(gis_plugin_radar_parent_class)->finalize(gobject); - -} -static void gis_plugin_radar_class_init(GisPluginRadarClass *klass) -{ - g_debug("GisPluginRadar: class_init"); - GObjectClass *gobject_class = (GObjectClass*)klass; - gobject_class->dispose = gis_plugin_radar_dispose; - gobject_class->finalize = gis_plugin_radar_finalize; -} /************************** * Data loading functions * @@ -228,8 +175,8 @@ static void _gis_plugin_radar_grid_set(GRIDCELL *grid, int gi, Ray *ray, int bi) { Range range = ray->range[bi]; - double angle = d2r(ray->h.azimuth); - double tilt = d2r(ray->h.elev); + double angle = deg2rad(ray->h.azimuth); + double tilt = deg2rad(ray->h.elev); double lx = sin(angle); double ly = cos(angle); @@ -366,6 +313,7 @@ static void load_radar(GisPluginRadar *self, gchar *radar_file) load_radar_gui(self, radar); } + /***************** * ASync helpers * *****************/ @@ -441,6 +389,7 @@ static void cache_done_cb(char *path, gboolean updated, gpointer _self) self->soup = NULL; } + /************* * Callbacks * *************/ @@ -494,6 +443,7 @@ static void on_time_changed(GisView *view, const char *time, gpointer _self) g_free(path); } + /*********** * Methods * ***********/ @@ -555,26 +505,33 @@ static void gis_plugin_radar_expose(GisPlugin *_self) glPopMatrix(); #endif - /* Draw the rays */ + g_message("set camera"); + Radar_header *h = &self->cur_radar->h; + gdouble lat = (double)h->latd + (double)h->latm/60 + (double)h->lats/(60*60); + gdouble lon = (double)h->lond + (double)h->lonm/60 + (double)h->lons/(60*60); + gdouble elev = h->height; + gis_opengl_center_position(self->opengl, lat, lon, elev); + + glDisable(GL_ALPHA_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glDisable(GL_COLOR_MATERIAL); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glBindTexture(GL_TEXTURE_2D, self->cur_sweep_tex); glEnable(GL_TEXTURE_2D); - glDisable(GL_ALPHA_TEST); glColor4f(1,1,1,1); + + /* Draw the rays */ + glBindTexture(GL_TEXTURE_2D, self->cur_sweep_tex); glBegin(GL_QUAD_STRIP); for (int ri = 0; ri <= sweep->h.nrays; ri++) { Ray *ray = NULL; double angle = 0; if (ri < sweep->h.nrays) { ray = sweep->ray[ri]; - angle = d2r(ray->h.azimuth - ((double)ray->h.beam_width/2.)); + angle = deg2rad(ray->h.azimuth - ((double)ray->h.beam_width/2.)); } else { /* Do the right side of the last sweep */ ray = sweep->ray[ri-1]; - angle = d2r(ray->h.azimuth + ((double)ray->h.beam_width/2.)); + angle = deg2rad(ray->h.azimuth + ((double)ray->h.beam_width/2.)); } double lx = sin(angle); @@ -590,13 +547,12 @@ static void gis_plugin_radar_expose(GisPlugin *_self) // far left // todo: correct range-height function - double height = sin(d2r(ray->h.elev)) * far_dist; + double height = sin(deg2rad(ray->h.elev)) * far_dist; glTexCoord2f(1.0, (double)ri/sweep->h.nrays-0.01); glVertex3f(lx*far_dist, ly*far_dist, height); } - //g_print("ri=%d, nr=%d, bw=%f\n", _ri, sweep->h.nrays, sweep->h.beam_width); glEnd(); - glPopMatrix(); + //g_print("ri=%d, nr=%d, bw=%f\n", _ri, sweep->h.nrays, sweep->h.beam_width); /* Texture debug */ //glBegin(GL_QUADS); @@ -607,25 +563,72 @@ static void gis_plugin_radar_expose(GisPlugin *_self) //glEnd(); /* Print the color table */ + glMatrixMode(GL_MODELVIEW ); glLoadIdentity(); + glMatrixMode(GL_PROJECTION); glLoadIdentity(); glDisable(GL_TEXTURE_2D); - glDisable(GL_DEPTH_TEST); - glMatrixMode(GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); - glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); + glEnable(GL_COLOR_MATERIAL); glBegin(GL_QUADS); int i; for (i = 0; i < 256; i++) { - glColor4ub(self->cur_colormap->data[i][0], - self->cur_colormap->data[i][1], - self->cur_colormap->data[i][2], - self->cur_colormap->data[i][3]); + glColor4ubv(self->cur_colormap->data[i]); glVertex3f(-1.0, (float)((i ) - 256/2)/(256/2), 0.0); // bot left glVertex3f(-1.0, (float)((i+1) - 256/2)/(256/2), 0.0); // top left glVertex3f(-0.9, (float)((i+1) - 256/2)/(256/2), 0.0); // top right glVertex3f(-0.9, (float)((i ) - 256/2)/(256/2), 0.0); // bot right } glEnd(); - glEnable(GL_DEPTH_TEST); - glEnable(GL_ALPHA_TEST); - glMatrixMode(GL_PROJECTION); glPopMatrix(); - glMatrixMode(GL_MODELVIEW ); glPopMatrix(); } + + +/**************** + * GObject code * + ****************/ +/* Plugin init */ +static void gis_plugin_radar_plugin_init(GisPluginInterface *iface); +G_DEFINE_TYPE_WITH_CODE(GisPluginRadar, gis_plugin_radar, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, + gis_plugin_radar_plugin_init)); +static void gis_plugin_radar_plugin_init(GisPluginInterface *iface) +{ + g_debug("GisPluginRadar: plugin_init"); + /* Add methods to the interface */ + iface->expose = gis_plugin_radar_expose; + iface->get_config = gis_plugin_radar_get_config; +} +/* Class/Object init */ +static void gis_plugin_radar_init(GisPluginRadar *self) +{ + g_debug("GisPluginRadar: class_init"); + /* Set defaults */ + self->soup = NULL; + self->cur_triangles = NULL; + self->cur_num_triangles = 0; + + self->config_body = gtk_alignment_new(0, 0, 1, 1); + gtk_container_set_border_width(GTK_CONTAINER(self->config_body), 5); + gtk_container_add(GTK_CONTAINER(self->config_body), gtk_label_new("No radar loaded")); +} +static void gis_plugin_radar_dispose(GObject *gobject) +{ + g_debug("GisPluginRadar: dispose"); + GisPluginRadar *self = GIS_PLUGIN_RADAR(gobject); + g_signal_handler_disconnect(self->view, self->time_changed_id); + /* Drop references */ + G_OBJECT_CLASS(gis_plugin_radar_parent_class)->dispose(gobject); +} +static void gis_plugin_radar_finalize(GObject *gobject) +{ + g_debug("GisPluginRadar: finalize"); + GisPluginRadar *self = GIS_PLUGIN_RADAR(gobject); + /* Free data */ + G_OBJECT_CLASS(gis_plugin_radar_parent_class)->finalize(gobject); + +} +static void gis_plugin_radar_class_init(GisPluginRadarClass *klass) +{ + g_debug("GisPluginRadar: class_init"); + GObjectClass *gobject_class = (GObjectClass*)klass; + gobject_class->dispose = gis_plugin_radar_dispose; + gobject_class->finalize = gis_plugin_radar_finalize; +} + diff --git a/src/plugins/ridge.c b/src/plugins/ridge.c index a25bd59..1d79f78 100644 --- a/src/plugins/ridge.c +++ b/src/plugins/ridge.c @@ -25,51 +25,6 @@ #include "ridge.h" -/**************** - * GObject code * - ****************/ -/* Plugin init */ -static void gis_plugin_ridge_plugin_init(GisPluginInterface *iface); -static void gis_plugin_ridge_expose(GisPlugin *_self); -static GtkWidget *gis_plugin_ridge_get_config(GisPlugin *_self); -G_DEFINE_TYPE_WITH_CODE(GisPluginRidge, gis_plugin_ridge, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, - gis_plugin_ridge_plugin_init)); -static void gis_plugin_ridge_plugin_init(GisPluginInterface *iface) -{ - g_debug("GisPluginRidge: plugin_init"); - /* Add methods to the interface */ - iface->expose = gis_plugin_ridge_expose; - iface->get_config = gis_plugin_ridge_get_config; -} -/* Class/Object init */ -static void gis_plugin_ridge_init(GisPluginRidge *self) -{ - g_debug("GisPluginRidge: init"); - /* Set defaults */ -} -static void gis_plugin_ridge_dispose(GObject *gobject) -{ - g_debug("GisPluginRidge: dispose"); - GisPluginRidge *self = GIS_PLUGIN_RIDGE(gobject); - /* Drop references */ - G_OBJECT_CLASS(gis_plugin_ridge_parent_class)->dispose(gobject); -} -static void gis_plugin_ridge_finalize(GObject *gobject) -{ - g_debug("GisPluginRidge: finalize"); - GisPluginRidge *self = GIS_PLUGIN_RIDGE(gobject); - /* Free data */ - G_OBJECT_CLASS(gis_plugin_ridge_parent_class)->finalize(gobject); - -} -static void gis_plugin_ridge_class_init(GisPluginRidgeClass *klass) -{ - g_debug("GisPluginRidge: class_init"); - GObjectClass *gobject_class = (GObjectClass*)klass; - gobject_class->dispose = gis_plugin_ridge_dispose; - gobject_class->finalize = gis_plugin_ridge_finalize; -} /********************* * Overlay constants * @@ -165,6 +120,7 @@ void cached_cb(gchar *filename, gboolean updated, gpointer _udata) g_free(udata); } + /************* * callbacks * *************/ @@ -191,6 +147,7 @@ void toggle_layer(GtkToggleButton *check, GisPluginRidge *self) gis_opengl_redraw(self->opengl); } + /*********** * Methods * ***********/ @@ -228,7 +185,6 @@ static void gis_plugin_ridge_expose(GisPlugin *_self) GisPluginRidge *self = GIS_PLUGIN_RIDGE(_self); g_debug("GisPluginRidge: expose"); - glPushMatrix(); glEnable(GL_TEXTURE_2D); glColor4f(1,1,1,1); @@ -243,6 +199,50 @@ static void gis_plugin_ridge_expose(GisPlugin *_self) glTexCoord2f(1.0, 0.0); glVertex3f(240*1000* 1.0, 282*1000* 1.0, layers[i].z); glEnd(); } +} + - glPopMatrix(); +/**************** + * GObject code * + ****************/ +/* Plugin init */ +static void gis_plugin_ridge_plugin_init(GisPluginInterface *iface); +G_DEFINE_TYPE_WITH_CODE(GisPluginRidge, gis_plugin_ridge, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GIS_TYPE_PLUGIN, + gis_plugin_ridge_plugin_init)); +static void gis_plugin_ridge_plugin_init(GisPluginInterface *iface) +{ + g_debug("GisPluginRidge: plugin_init"); + /* Add methods to the interface */ + iface->expose = gis_plugin_ridge_expose; + iface->get_config = gis_plugin_ridge_get_config; } +/* Class/Object init */ +static void gis_plugin_ridge_init(GisPluginRidge *self) +{ + g_debug("GisPluginRidge: init"); + /* Set defaults */ +} +static void gis_plugin_ridge_dispose(GObject *gobject) +{ + g_debug("GisPluginRidge: dispose"); + GisPluginRidge *self = GIS_PLUGIN_RIDGE(gobject); + /* Drop references */ + G_OBJECT_CLASS(gis_plugin_ridge_parent_class)->dispose(gobject); +} +static void gis_plugin_ridge_finalize(GObject *gobject) +{ + g_debug("GisPluginRidge: finalize"); + GisPluginRidge *self = GIS_PLUGIN_RIDGE(gobject); + /* Free data */ + G_OBJECT_CLASS(gis_plugin_ridge_parent_class)->finalize(gobject); + +} +static void gis_plugin_ridge_class_init(GisPluginRidgeClass *klass) +{ + g_debug("GisPluginRidge: class_init"); + GObjectClass *gobject_class = (GObjectClass*)klass; + gobject_class->dispose = gis_plugin_ridge_dispose; + gobject_class->finalize = gis_plugin_ridge_finalize; +} + -- 2.41.0