]> Pileus Git - aweather/commitdiff
Lots of work on libGIS
authorAndy Spencer <andy753421@gmail.com>
Wed, 2 Sep 2009 06:06:59 +0000 (06:06 +0000)
committerAndy Spencer <andy753421@gmail.com>
Wed, 2 Sep 2009 06:06:59 +0000 (06:06 +0000)
 - Added a `virtual globe'
 - Using Blue Marble NG for ground textures
 - Using SRTM30 Plus for elevation data

38 files changed:
README
TODO
configure.ac
data/defaults.ini
data/main.ui
docs/api/Makefile.am
src/Makefile.am
src/aweather-gui.c
src/aweather-location.c
src/aweather-location.h
src/examples/mkfile [new file with mode: 0644]
src/examples/tex.c [new file with mode: 0644]
src/examples/tex_png/tex.png [new file with mode: 0644]
src/examples/tex_png/texl.png [new file with mode: 0644]
src/examples/tex_png/texls.png [new file with mode: 0644]
src/examples/tex_png/texr.png [new file with mode: 0644]
src/examples/tex_png/texrs.png [new file with mode: 0644]
src/gis/.vimrc [new file with mode: 0644]
src/gis/Makefile.am
src/gis/gis-opengl.c
src/gis/gis-opengl.h
src/gis/gis-plugin.c
src/gis/gis-prefs.c
src/gis/gis-view.c
src/gis/gis-view.h
src/gis/gis-world.c
src/gis/gis-world.h
src/gis/gis_test.c [new file with mode: 0644]
src/gis/gpqueue.c [new file with mode: 0644]
src/gis/gpqueue.h [new file with mode: 0644]
src/gis/roam.c [new file with mode: 0644]
src/gis/roam.h [new file with mode: 0644]
src/gis/wms.c [new file with mode: 0644]
src/gis/wms.h [new file with mode: 0644]
src/gis/wms_test.c [new file with mode: 0644]
src/plugins/example.c
src/plugins/radar.c
src/plugins/ridge.c

diff --git a/README b/README
index 0501bb1494417b6ae436bd3c612c48451f0c529a..8e2fb59ecfb68554b4861189f3025f8d0065ab44 100644 (file)
--- 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 1ed0735091cc8c3450c33948be3016624f2b93ab..a77a5d69d667eebbb44d738d3bb86d1851e700f9 100644 (file)
--- 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
index 583b00df5c4b8c7b4f72c41a4c0334565c03405b..58a1103cb1e1db57903d726fd6ebad29677ca01a 100644 (file)
@@ -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])
index 085cd4f55546b0e295bdff0e7bbed8352555ef6d..9c669b28c7686c87feaa72d13577cf3a2947fcf6 100644 (file)
@@ -10,7 +10,7 @@ offline=false
 enabled=true
 
 [ridge]
-enabled=true
+enabled=false
 
 [radar]
 enabled=true
index 3511db08a57322f4ae4e540bb4e5e5fbb04e703a..f37784bf63f0485aeff620ce22271246651e203f 100644 (file)
                       <object class="GtkCheckMenuItem" id="offline_item">
                         <property name="visible">True</property>
                         <property name="tooltip_text" translatable="yes">Work offline</property>
-                        <property name="related_action">offline</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">offline</property>
                       </object>
                     </child>
                     <child>
                       <object class="GtkImageMenuItem" id="prefs_item">
                         <property name="visible">True</property>
-                        <property name="related_action">prefs</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">prefs</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
@@ -65,8 +65,8 @@
                     <child>
                       <object class="GtkImageMenuItem" id="quit_item">
                         <property name="visible">True</property>
-                        <property name="related_action">quit</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">quit</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
@@ -86,8 +86,8 @@
                     <child>
                       <object class="GtkImageMenuItem" id="zoomin_item">
                         <property name="visible">True</property>
-                        <property name="related_action">zoomin</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">zoomin</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
@@ -95,8 +95,8 @@
                     <child>
                       <object class="GtkImageMenuItem" id="zoomout_item">
                         <property name="visible">True</property>
-                        <property name="related_action">zoomout</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">zoomout</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
                     <child>
                       <object class="GtkImageMenuItem" id="refresh_item">
                         <property name="visible">True</property>
-                        <property name="related_action">refresh</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">refresh</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
                     <child>
                       <object class="GtkImageMenuItem" id="about_item">
                         <property name="visible">True</property>
-                        <property name="related_action">about</property>
                         <property name="use_action_appearance">True</property>
+                        <property name="related_action">about</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                       </object>
             <child>
               <object class="GtkToolButton" id="zoomin_button">
                 <property name="visible">True</property>
-                <property name="related_action">zoomin</property>
                 <property name="use_action_appearance">True</property>
+                <property name="related_action">zoomin</property>
               </object>
               <packing>
                 <property name="expand">False</property>
             <child>
               <object class="GtkToolButton" id="zoomout_button">
                 <property name="visible">True</property>
-                <property name="related_action">zoomout</property>
                 <property name="use_action_appearance">True</property>
+                <property name="related_action">zoomout</property>
               </object>
               <packing>
                 <property name="expand">False</property>
             <child>
               <object class="GtkToggleToolButton" id="offline_button">
                 <property name="visible">True</property>
-                <property name="related_action">offline</property>
                 <property name="use_action_appearance">True</property>
+                <property name="related_action">offline</property>
                 <property name="label" translatable="yes">toolbutton3</property>
                 <property name="use_underline">True</property>
               </object>
             <child>
               <object class="GtkToolButton" id="refresh_button">
                 <property name="visible">True</property>
-                <property name="related_action">refresh</property>
                 <property name="use_action_appearance">True</property>
+                <property name="related_action">refresh</property>
                 <property name="use_underline">True</property>
               </object>
               <packing>
             <child>
               <object class="GtkToolButton" id="prefs_button">
                 <property name="visible">True</property>
-                <property name="related_action">prefs</property>
                 <property name="use_action_appearance">True</property>
+                <property name="related_action">prefs</property>
                 <property name="label" translatable="yes">toolbutton1</property>
                 <property name="use_underline">True</property>
               </object>
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <child>
-              <object class="GtkHPaned" id="hpaned1">
+              <object class="GtkHPaned" id="hpaned">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <child>
-                  <object class="GtkDrawingArea" id="drawing">
-                    <property name="width_request">600</property>
-                    <property name="height_request">550</property>
+                  <object class="GtkLabel" id="label3">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="has_focus">True</property>
-                    <property name="is_focus">True</property>
-                    <property name="can_default">True</property>
-                    <property name="has_default">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="events">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</property>
+                    <property name="label" translatable="yes">placeholder
+(it's buggy with blank placeholders)</property>
+                    <property name="justify">center</property>
                   </object>
                   <packing>
-                    <property name="resize">True</property>
-                    <property name="shrink">False</property>
+                    <property name="resize">False</property>
+                    <property name="shrink">True</property>
                   </packing>
                 </child>
                 <child>
   <object class="GtkAboutDialog" id="about_window">
     <property name="border_width">5</property>
     <property name="title" translatable="yes">About - AWeather</property>
-    <property name="resizable">False</property>
     <property name="window_position">center-on-parent</property>
     <property name="type_hint">dialog</property>
     <property name="transient_for">main_window</property>
     <property name="has_separator">False</property>
     <property name="program_name">AWeather</property>
-    <property name="version">0.1</property>
-    <property name="copyright" translatable="yes">Copyright &#xA9;2008-2008 Andy Spencer</property>
+    <property name="version">(beta)</property>
+    <property name="copyright" translatable="yes">Copyright &#xA9;2008-2009 Andy Spencer</property>
     <property name="comments" translatable="yes">A semi-advanced weather viewer</property>
     <property name="website">http://lug.rose-hulman.edu/wiki/AWeather</property>
     <property name="license" translatable="yes">                    GNU GENERAL PUBLIC LICENSE
@@ -912,9 +905,9 @@ The hypothetical commands `show w' and `show c' should show the appropriate part
   </object>
   <object class="GtkSizeGroup" id="prefs_size">
     <widgets>
-      <widget name="log_label"/>
-      <widget name="nexrad_label"/>
       <widget name="site_label"/>
+      <widget name="nexrad_label"/>
+      <widget name="log_label"/>
     </widgets>
   </object>
   <object class="GtkAdjustment" id="adjustment">
index ac63adcf0898331d20f9a61c4c990415a79db00e..bf6b877106cce14596ce970c95e81b084fcb68da 100644 (file)
@@ -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/
index 9f317e6cf113f04eced9b1deefc37b18a2d058f4..d5e5fac0cbc01dc8bc1e37d3966deca1e44d2eb7 100644 (file)
@@ -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              \
index dcb2b2cb517afc1dd3aae07a6ec1c91a852bb336..0c3204d6e15bc40ae763e274ed39d01635e437ac 100644 (file)
 #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;
+}
index b893f390fc604e0b74385b12bafd02d916363591..c4952d058df775dd7b4b443057a9eab204de1f78 100644 (file)
 #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},
 };
index b030f04b4d9caeb82aa58c58b97f1ae3b6c1e450..1c3966850acdc45d3369612ad4e53352045e7282 100644 (file)
@@ -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 (file)
index 0000000..3036c6c
--- /dev/null
@@ -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 (file)
index 0000000..267c1c1
--- /dev/null
@@ -0,0 +1,143 @@
+#include <gtk/gtk.h>
+#include <gtk/gtkgl.h>
+#include <gdk/gdkkeysyms.h>
+#include <GL/gl.h>
+#include <GL/glu.h>
+
+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 (file)
index 0000000..af375cb
Binary files /dev/null and b/src/examples/tex_png/tex.png differ
diff --git a/src/examples/tex_png/texl.png b/src/examples/tex_png/texl.png
new file mode 100644 (file)
index 0000000..7fc7141
Binary files /dev/null and b/src/examples/tex_png/texl.png differ
diff --git a/src/examples/tex_png/texls.png b/src/examples/tex_png/texls.png
new file mode 100644 (file)
index 0000000..c3de39b
Binary files /dev/null and b/src/examples/tex_png/texls.png differ
diff --git a/src/examples/tex_png/texr.png b/src/examples/tex_png/texr.png
new file mode 100644 (file)
index 0000000..a421a5d
Binary files /dev/null and b/src/examples/tex_png/texr.png differ
diff --git a/src/examples/tex_png/texrs.png b/src/examples/tex_png/texrs.png
new file mode 100644 (file)
index 0000000..49765d2
Binary files /dev/null and b/src/examples/tex_png/texrs.png differ
diff --git a/src/gis/.vimrc b/src/gis/.vimrc
new file mode 100644 (file)
index 0000000..5de2904
--- /dev/null
@@ -0,0 +1,2 @@
+set makeprg=make\ test
+set tw=100
index fb12f5e1e5543bb70a83b32d5f7bad5fcdf445af..67a90ddc24ba9ab17527092a6200dc1dad9ad774 100644 (file)
@@ -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
index bdcaf404bf0c59da1ce6a96cb154c1dc3d757fc7..e1abb07d6dcf24b70aa9cf49a6762be0c895315c 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/* Tessellation, "finding intersecting triangles" */
+/* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */
+/* http://www.opengl.org/wiki/Alpha_Blending */
+
 #include <config.h>
 #include <math.h>
 #include <gdk/gdkkeysyms.h>
 #include <GL/glu.h>
 
 #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;
+}
index ee753a7c91129b09ca1994791098c33568b2b340..fcaaa0e636c5d450b248bac9b5d6631abf7f79b7 100644 (file)
@@ -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
index e3d132b0156caee72f29987488d0e2f2a93ed675..4bfab58d71afb8d634ebd7b6d7a5957b180eb01e 100644 (file)
@@ -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;
index ea3f05a5a8611c54995c8b8ba8a19fbcdccd7ae8..abae1a2a1c6f97d5d83d2fabc33a5e1e62022fc4 100644 (file)
 #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);
+}
index 409aa68458337868b62790ec1ffbdc5ac6f4a4bd..0caf3f02b04977514afa5a2c10234057ff227ed7 100644 (file)
 
 #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);
+}
index 4608788d5c10672c80fdaab6a9226bc1a6ab7d82..3eb57ad699719720cc5111cb90e24e86908e3b4b 100644 (file)
@@ -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);
index 9d7ecb27b53e576b3a6b75d27d1713cc404b1739..b8611aba2030d9957f6026d41d809277bcb42a72 100644 (file)
  */
 
 #include <glib.h>
+#include <math.h>
 
 #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]));
 }
-
-
index 64e52a2d32a752fb4dd5fcd101628cca8f7051bf..78ca130463ff8b4a09f7bba4bc0d9c9d61ed98e6 100644 (file)
 
 #include <glib-object.h>
 
-/* 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 (file)
index 0000000..fbe46a9
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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 (file)
index 0000000..906181c
--- /dev/null
@@ -0,0 +1,567 @@
+#include <glib.h>
+#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
+ * 
+ * <para>
+ * 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.
+ * </para>
+ * <para>
+ * The entries <emphasis>cannot</emphasis> 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.
+ * </para>
+ * <para>
+ * 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.
+ * </para>
+ * <note>
+ *   <para>
+ *     Internally, #GPQueue currently uses a Fibonacci heap to store
+ *     the entries. This implementation detail may change.
+ *   </para>
+ * </note>
+ **/
+
+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
+ * <emphasis>every</emphasis> 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
+ * <emphasis>decreased</emphasis>.
+ * 
+ * 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().
+ * 
+ * <note>
+ *   <para>
+ *     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.
+ *   </para>
+ * </note>
+ * 
+ * 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 (file)
index 0000000..87f6cf9
--- /dev/null
@@ -0,0 +1,61 @@
+#if defined(G_DISABLE_SINGLE_INCLUDES) && !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> 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 (file)
index 0000000..1a7b4f8
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <math.h>
+#include <string.h>
+#include "gpqueue.h"
+#include <GL/gl.h>
+#include <GL/glu.h>
+
+#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 (file)
index 0000000..c255812
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __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 (file)
index 0000000..aa54444
--- /dev/null
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * 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 <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+#include <libsoup/soup.h>
+
+#include "wms.h"
+
+/* TODO: try to remove these */
+#include "gis-world.h"
+#include <GL/gl.h>
+
+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 (file)
index 0000000..dfe6b7d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WMS_H__
+#define __WMS_H__
+
+#include <math.h>
+#include <time.h>
+#include <libsoup/soup.h>
+
+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 (file)
index 0000000..45476c4
--- /dev/null
@@ -0,0 +1,70 @@
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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;
+}
index 01ef9cad5f11a9647723eae9d8bc382041492b97..3d6a6683964c5fcef1241ee46b9225864d6664a9 100644 (file)
 
 #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;
+}
index 26403eaecce57bfd76bddc0426e3e34ebbeba5fc..9c76a57e5bb0a188b2dfa45ce8da309412898a70 100644 (file)
 #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;
+}
+
index a25bd59b1a94368037754f27e1b4d44d3df9727c..1d79f78acf39a7ef5eb1a2505f65c63b50828d05 100644 (file)
 
 #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;
+}
+