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
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
* Warning/watch boxes
* Fronts
* Air pressure
-
-0.x - Animation
- * Loop though times
# 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])
enabled=true
[ridge]
-enabled=true
+enabled=false
[radar]
enabled=true
<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>
<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>
<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>
<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 ©2008-2008 Andy Spencer</property>
+ <property name="version">(beta)</property>
+ <property name="copyright" translatable="yes">Copyright ©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
</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">
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/
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 \
#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 *
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");
}
return FALSE;
}
+
static void update_time_widget(GisView *view, const char *time, AWeatherGui *self)
{
g_debug("AWeatherGui: update_time_widget - time=%s", time);
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);
G_CALLBACK(on_site_changed), self);
}
}
+
/* Prefs callbacks */
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;
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);
gis_prefs_set_integer(self->prefs, "aweather/log_level", value);
return TRUE;
}
-// plugins
+
/*****************
* Setup helpers *
update_times(self, self->view, site);
}
+
/***********
* Methods *
***********/
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;
+}
#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},
};
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[];
--- /dev/null
+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
--- /dev/null
+#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);
+}
--- /dev/null
+set makeprg=make\ test
+set tw=100
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
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)
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
* 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;
+}
#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 */
};
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
#include "gis-plugin.h"
+/********************
+ * Plugin interface *
+ ********************/
static void gis_plugin_base_init(gpointer g_class)
{
static gboolean is_initialized = FALSE;
return GIS_PLUGIN_GET_INTERFACE(self)->get_config(self);
}
-/* Plugins API */
+
+/***************
+ * Plugins API *
+ ***************/
typedef struct {
gchar *name;
GisPlugin *plugin;
#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 *
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);
+}
#include "gis-marshal.h"
#include "gis-view.h"
+#include "gis-world.h"
-/****************
- * GObject code *
- ****************/
/* Constants */
enum {
PROP_0,
};
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)
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);
}
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);
+}
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);
*/
#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
};
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)
{
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]));
}
-
-
#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))
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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__ */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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;
+}
#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 *
***********/
return TRUE;
}
+
/***********
* Methods *
***********/
{
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;
+}
#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 *
{
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);
load_radar_gui(self, radar);
}
+
/*****************
* ASync helpers *
*****************/
self->soup = NULL;
}
+
/*************
* Callbacks *
*************/
g_free(path);
}
+
/***********
* Methods *
***********/
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);
// 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);
//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;
+}
+
#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 *
g_free(udata);
}
+
/*************
* callbacks *
*************/
gis_opengl_redraw(self->opengl);
}
+
/***********
* Methods *
***********/
GisPluginRidge *self = GIS_PLUGIN_RIDGE(_self);
g_debug("GisPluginRidge: expose");
- glPushMatrix();
glEnable(GL_TEXTURE_2D);
glColor4f(1,1,1,1);
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;
+}
+