From 82adb52036f7330bb6d47e354c24bcc13c34dd34 Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Mon, 9 Nov 2009 13:24:54 +0000 Subject: [PATCH] Add Shuttle Radar Topography Mission plugin and height code For the most part, the SRTM plugin is simmilar to the BMNG plugin. The loaders are more complicated because it generates both a bil (unsigned short array) and a pixmap from the downloaded files. Highmaps are in two parts when set_height_func is called with update=TRUE, the heights of all the vertices for a given tile are updated by calling height_func and using the returned value as the new vertex height. In addition to updating the vertex height immediately, the height function is also stored in each vertex and used later during the split-merge process. The user_data passed with set_height_func could theoretically be used to speed searching for the correct tile during the split-merge process, but this is currently not done. (Each call to the srtm height function does a search for the correct tile) --- src/gis-opengl.c | 21 ++++ src/gis-opengl.h | 3 + src/gis-tile.c | 22 +++++ src/gis-tile.h | 3 + src/gis_test.c | 4 +- src/plugins/srtm.c | 238 +++++++++++++++++++++++++++++++++++++-------- src/plugins/srtm.h | 8 +- 7 files changed, 250 insertions(+), 49 deletions(-) diff --git a/src/gis-opengl.c b/src/gis-opengl.c index 4d3b7e4..d1c845f 100644 --- a/src/gis-opengl.c +++ b/src/gis-opengl.c @@ -335,6 +335,27 @@ void gis_opengl_render_tiles(GisOpenGL *opengl, GisTile *tile) /* No children, render this tile */ gis_opengl_render_tile(opengl, tile); } + +void gis_opengl_set_height_func(GisOpenGL *self, GisTile *tile, + RoamHeightFunc height_func, gpointer user_data, gboolean update) +{ + if (!tile) + return; + /* TODO: get points? */ + GList *triangles = roam_sphere_get_intersect(self->sphere, + tile->edge.n, tile->edge.s, tile->edge.e, tile->edge.w); + for (GList *cur = triangles; cur; cur = cur->next) { + RoamTriangle *tri = cur->data; + RoamPoint *points[] = {tri->p.l, tri->p.m, tri->p.r, tri->split}; + for (int i = 0; i < G_N_ELEMENTS(points); i++) { + points[i]->height_func = height_func; + points[i]->height_data = user_data; + roam_point_update_height(points[i]); + } + } + g_list_free(triangles); +} + void gis_opengl_redraw(GisOpenGL *self) { g_debug("GisOpenGL: redraw"); diff --git a/src/gis-opengl.h b/src/gis-opengl.h index 19feefc..37099a7 100644 --- a/src/gis-opengl.h +++ b/src/gis-opengl.h @@ -71,6 +71,9 @@ void gis_opengl_render_tile(GisOpenGL *opengl, GisTile *tile); void gis_opengl_render_tiles(GisOpenGL *opengl, GisTile *root); +void gis_opengl_set_height_func(GisOpenGL *self, GisTile *tile, + RoamHeightFunc height_func, gpointer user_data, gboolean update); + void gis_opengl_redraw(GisOpenGL *opengl); void gis_opengl_begin(GisOpenGL *opengl); diff --git a/src/gis-tile.c b/src/gis-tile.c index 80772e6..7a0f0b8 100644 --- a/src/gis-tile.c +++ b/src/gis-tile.c @@ -128,6 +128,28 @@ void gis_tile_update(GisTile *self, } } +GisTile *gis_tile_find(GisTile *self, gdouble lat, gdouble lon) +{ + gint rows = G_N_ELEMENTS(self->children); + gint cols = G_N_ELEMENTS(self->children[0]); + + gdouble lat_step = (self->edge.n - self->edge.s) / rows; + gdouble lon_step = (self->edge.e - self->edge.w) / cols; + + gdouble lat_offset = self->edge.n - lat;; + gdouble lon_offset = lon - self->edge.w; + + gint row = lat_offset / lat_step; + gint col = lon_offset / lon_step; + + if (row < 0 || row >= rows || col < 0 || col >= cols) + return NULL; + else if (self->children[row][col] && self->children[row][col]->data) + return gis_tile_find(self->children[row][col], lat, lon); + else + return self; +} + GisTile *gis_tile_gc(GisTile *self, time_t atime, GisTileFreeFunc free_func, gpointer user_data) { diff --git a/src/gis-tile.h b/src/gis-tile.h index cda4442..4db4f4b 100644 --- a/src/gis-tile.h +++ b/src/gis-tile.h @@ -69,6 +69,9 @@ void gis_tile_update(GisTile *root, gdouble lat, gdouble lon, gdouble elev, GisTileLoadFunc load_func, gpointer user_data); +/* Find the leaf tile containing lat-lon */ +GisTile *gis_tile_find(GisTile *root, gdouble lat, gdouble lon); + /* Delete nodes that haven't been accessed since atime */ GisTile *gis_tile_gc(GisTile *root, time_t atime, GisTileFreeFunc free_func, gpointer user_data); diff --git a/src/gis_test.c b/src/gis_test.c index 35f2901..df2692f 100644 --- a/src/gis_test.c +++ b/src/gis_test.c @@ -56,8 +56,8 @@ int main(int argc, char **argv) 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_plugins_load(plugins, "bmng", world, view, opengl, prefs); - //gis_plugins_load(plugins, "srtm", world, view, opengl, prefs); + //gis_plugins_load(plugins, "bmng", world, view, opengl, prefs); + gis_plugins_load(plugins, "srtm", world, view, opengl, prefs); gis_view_set_site(view, "KLSX"); diff --git a/src/plugins/srtm.c b/src/plugins/srtm.c index 3beb2bc..6960241 100644 --- a/src/plugins/srtm.c +++ b/src/plugins/srtm.c @@ -22,19 +22,187 @@ #include "srtm.h" -/*********** - * Helpers * - ***********/ -static gboolean rotate(gpointer _self) +#define MAX_RESOLUTION 500 +#define TILE_WIDTH 1024 +#define TILE_HEIGHT 512 + +struct _TileData { + /* OpenGL has to be first to make gis_opengl_render_tiles happy */ + guint opengl; + guint16 *bil; +}; + +static gdouble _height_func(gdouble lat, gdouble lon, gpointer _self) { GisPluginSrtm *self = _self; - if (gtk_toggle_button_get_active(self->button)) { - self->rotation += 1.0; - gis_opengl_redraw(self->opengl); + if (!self) return 0; + + GisTile *tile = gis_tile_find(self->tiles, lat, lon); + if (!tile) return 0; + + struct _TileData *data = tile->data; + if (!data) return 0; + + guint16 *bil = data->bil; + if (!bil) return 0; + + gint w = TILE_WIDTH; + gint h = TILE_HEIGHT; + + gdouble ymin = tile->edge.s; + gdouble ymax = tile->edge.n; + gdouble xmin = tile->edge.w; + gdouble xmax = tile->edge.e; + + 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[MIN((y_flr ),h-1)*w + MIN((x_flr ),w-1)]; + gint16 px10 = bil[MIN((y_flr ),h-1)*w + MIN((x_flr+1),w-1)]; + gint16 px01 = bil[MIN((y_flr+1),h-1)*w + MIN((x_flr ),w-1)]; + gint16 px11 = bil[MIN((y_flr+1),h-1)*w + MIN((x_flr+1),w-1)]; + + gdouble 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); + return elev; +} + +/********************** + * Loader and Freeers * + **********************/ +#define LOAD_BIL TRUE +#define LOAD_OPENGL TRUE +static guint16 *_load_bil(gchar *path) +{ + gchar *data; + g_file_get_contents(path, &data, NULL, NULL); + g_debug("GisPluginSrtm: load_bil %p", data); + return (guint16*)data; +} +static GdkPixbuf *_load_pixbuf(guint16 *bil) +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, TILE_WIDTH, TILE_HEIGHT); + guchar *pixels = gdk_pixbuf_get_pixels(pixbuf); + gint stride = gdk_pixbuf_get_rowstride(pixbuf); + gint nchan = gdk_pixbuf_get_n_channels(pixbuf); + + for (int r = 0; r < TILE_HEIGHT; r++) { + for (int c = 0; c < TILE_WIDTH; c++) { + gint16 value = bil[r*TILE_WIDTH + c]; + //guchar color = (float)(MAX(value,0))/8848 * 255; + guchar color = (float)value/8848 * 255; + pixels[r*stride + c*nchan + 0] = color; + pixels[r*stride + c*nchan + 1] = color; + pixels[r*stride + c*nchan + 2] = color; + if (nchan == 4) + pixels[r*stride + c*nchan + 3] = 128; + } } - return TRUE; + g_debug("GisPluginSrtm: load_pixbuf %p", pixbuf); + return pixbuf; } +static guint _load_opengl(GdkPixbuf *pixbuf) +{ + /* Load image */ + guchar *pixels = gdk_pixbuf_get_pixels(pixbuf); + gint alpha = gdk_pixbuf_get_has_alpha(pixbuf); + gint nchan = 4; // gdk_pixbuf_get_n_channels(pixbuf); + gint width = gdk_pixbuf_get_width(pixbuf); + gint height = gdk_pixbuf_get_height(pixbuf); + + /* Create Texture */ + guint opengl; + glGenTextures(1, &opengl); + glBindTexture(GL_TEXTURE_2D, opengl); + 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_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + 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_debug("GisPluginSrtm: load_opengl %d", opengl); + return opengl; +} +static void _load_tile(GisTile *tile, gpointer _self) +{ + GisPluginSrtm *self = _self; + g_debug("GisPluginSrtm: _load_tile"); + + struct _TileData *data = g_new0(struct _TileData, 1); + gchar *path = gis_wms_make_local(self->wms, tile); + if (LOAD_BIL || LOAD_OPENGL) + data->bil = _load_bil(path); + g_free(path); + if (LOAD_OPENGL) { + GdkPixbuf *pixbuf = _load_pixbuf(data->bil); + data->opengl = _load_opengl(pixbuf); + g_object_unref(pixbuf); + } + + /* Do necessasairy processing */ + if (LOAD_BIL) + gis_opengl_set_height_func(self->opengl, tile, + _height_func, self, TRUE); + + /* Cleanup unneeded things */ + if (!LOAD_BIL) + g_free(data->bil); + + tile->data = data; +} + +static void _free_tile(GisTile *tile, gpointer _self) +{ + GisPluginSrtm *self = _self; + struct _TileData *data = tile->data; + g_debug("GisPluginSrtm: _free_tile: %p=%d", data, data->opengl); + if (LOAD_BIL) + g_free(data->bil); + if (LOAD_OPENGL) + glDeleteTextures(1, &data->opengl); + g_free(data); +} + +static gpointer _update_tiles(gpointer _self) +{ + GisPluginSrtm *self = _self; + gdouble lat, lon, elev; + gis_view_get_location(self->view, &lat, &lon, &elev); + gis_tile_update(self->tiles, + MAX_RESOLUTION, TILE_WIDTH, TILE_WIDTH, + lat, lon, elev, + _load_tile, self); + gis_tile_gc(self->tiles, time(NULL)-10, + _free_tile, self); + return NULL; +} + +/************* + * Callbacks * + *************/ +static void _on_location_changed(GisView *view, gdouble lat, gdouble lon, gdouble elev, + GisPluginSrtm *self) +{ + _update_tiles(self); +} /*********** * Methods * @@ -43,42 +211,26 @@ GisPluginSrtm *gis_plugin_srtm_new(GisWorld *world, GisView *view, GisOpenGL *op { g_debug("GisPluginSrtm: new"); GisPluginSrtm *self = g_object_new(GIS_TYPE_PLUGIN_SRTM, NULL); + self->view = view; self->opengl = opengl; - return self; -} + /* Load initial tiles */ + _load_tile(self->tiles, self); + _update_tiles(self); -static GtkWidget *gis_plugin_srtm_get_config(GisPlugin *_self) -{ - GisPluginSrtm *self = GIS_PLUGIN_SRTM(_self); - return GTK_WIDGET(self->button); + /* Connect signals */ + g_signal_connect(view, "location-changed", G_CALLBACK(_on_location_changed), self); + + return self; } static void gis_plugin_srtm_expose(GisPlugin *_self) { GisPluginSrtm *self = GIS_PLUGIN_SRTM(_self); - g_debug("GisPluginSrtm: expose"); - - 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_COLOR_MATERIAL); - - 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); + g_debug("GisPluginSrtm: expose tiles=%p data=%p", + self->tiles, self->tiles->data); + if (LOAD_OPENGL) + gis_opengl_render_tiles(self->opengl, self->tiles); } @@ -94,24 +246,22 @@ static void gis_plugin_srtm_plugin_init(GisPluginInterface *iface) { g_debug("GisPluginSrtm: plugin_init"); /* Add methods to the interface */ - iface->expose = gis_plugin_srtm_expose; - iface->get_config = gis_plugin_srtm_get_config; + iface->expose = gis_plugin_srtm_expose; } /* Class/Object init */ static void gis_plugin_srtm_init(GisPluginSrtm *self) { g_debug("GisPluginSrtm: 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; + self->tiles = gis_tile_new(NULL, NORTH, SOUTH, EAST, WEST); + self->wms = gis_wms_new( + "http://www.nasa.network.com/elev", "srtm30", "application/bil", + "srtm", ".bil", TILE_WIDTH, TILE_HEIGHT); } static void gis_plugin_srtm_dispose(GObject *gobject) { g_debug("GisPluginSrtm: dispose"); GisPluginSrtm *self = GIS_PLUGIN_SRTM(gobject); - g_source_remove(self->rotate_id); /* Drop references */ G_OBJECT_CLASS(gis_plugin_srtm_parent_class)->dispose(gobject); } @@ -120,6 +270,8 @@ static void gis_plugin_srtm_finalize(GObject *gobject) g_debug("GisPluginSrtm: finalize"); GisPluginSrtm *self = GIS_PLUGIN_SRTM(gobject); /* Free data */ + gis_tile_free(self->tiles, _free_tile, self); + gis_wms_free(self->wms); G_OBJECT_CLASS(gis_plugin_srtm_parent_class)->finalize(gobject); } diff --git a/src/plugins/srtm.h b/src/plugins/srtm.h index 8953788..7af659e 100644 --- a/src/plugins/srtm.h +++ b/src/plugins/srtm.h @@ -34,10 +34,10 @@ struct _GisPluginSrtm { GObject parent_instance; /* instance members */ - GtkToggleButton *button; - guint rotate_id; - float rotation; - GisOpenGL *opengl; + GisView *view; + GisOpenGL *opengl; + GisTile *tiles; + GisWms *wms; }; struct _GisPluginSrtmClass { -- 2.43.2