X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=src%2Fwms.c;fp=src%2Fwms.c;h=92a3476a8422c73e0422e84acaa330aa026ac884;hb=14cdbb4a9c369576a5485315260fad5285935e80;hp=0000000000000000000000000000000000000000;hpb=42eaa69adc4578f47225ce8e1a7f89fdfaedffa4;p=grits diff --git a/src/wms.c b/src/wms.c new file mode 100644 index 0000000..92a3476 --- /dev/null +++ b/src/wms.c @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2009 Andy Spencer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * http://www.nasa.network.com/elev? + * SERVICE=WMS& + * VERSION=1.1.0& + * REQUEST=GetMap& + * LAYERS=bmng200406& + * STYLES=& + * SRS=EPSG:4326& + * BBOX=-180,-90,180,90& + * FORMAT=image/jpeg& + * WIDTH=600& + * HEIGHT=300 + * + * http://www.nasa.network.com/elev? + * SERVICE=WMS& + * VERSION=1.1.0& + * REQUEST=GetMap& + * LAYERS=srtm30& + * STYLES=& + * SRS=EPSG:4326& + * BBOX=-180,-90,180,90& + * FORMAT=application/bil32& + * WIDTH=600& + * HEIGHT=300 + */ + +#include +#include +#include +#include +#include +#include + +#include "wms.h" + +/* TODO: try to remove these */ +#include "gis-world.h" +#include + +gchar *wms_make_uri(WmsInfo *info, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax) +{ + return g_strdup_printf( + "%s?" + "SERVICE=WMS&" + "VERSION=1.1.0&" + "REQUEST=GetMap&" + "LAYERS=%s&" + "STYLES=&" + "SRS=EPSG:4326&" + "BBOX=%f,%f,%f,%f&" + "FORMAT=%s&" + "WIDTH=%d&" + "HEIGHT=%d", + info->uri_prefix, + info->uri_layer, + xmin, ymin, xmax, ymax, + info->uri_format, + info->width, + info->height); +} + +/**************** + * WmsCacheNode * + ****************/ +WmsCacheNode *wms_cache_node_new(WmsCacheNode *parent, + gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax, gint width) +{ + 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; + self->parent = parent; + if (ymin <= 0 && ymax >= 0) + self->res = ll2m(xmax-xmin, 0)/width; + else + self->res = ll2m(xmax-xmin, MIN(ABS(ymin),ABS(ymax)))/width; + 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 = 60; + self->atime = time(NULL); + self->gc_source = g_timeout_add_seconds(1, (GSourceFunc)wms_info_gc, self); + self->cache_root = wms_cache_node_new(NULL, -180, -90, 180, 90, width); + 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(target_node, + xmin, ymin, xmax, ymax, info->width); + 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); + } +} +/* TODO: + * - Store WmsCacheNode in point and then use parent pointers to go up/down + * - If resolution doesn't change, tell caller to skip remaining calculations + */ +WmsCacheNode *wms_info_fetch(WmsInfo *info, WmsCacheNode *root, + gdouble resolution, gdouble lat, gdouble lon, + gboolean *correct) +{ + if (root && root->data && !root->caching && + root->latlon[0] <= lon && lon <= root->latlon[2] && + root->latlon[1] <= lat && lat <= root->latlon[3] && + root->res <= resolution && + (!root->parent || root->parent->res > resolution)) { + *correct = TRUE; + info->atime = time(NULL); + root->atime = info->atime; + return root; + } + + if (info->cache_root == NULL) { + *correct = FALSE; + return NULL; + } + WmsCacheNode *node = info->cache_root; + WmsCacheNode *best = (node && node->data ? node : NULL); + gdouble xmin=-180, ymin=-90, xmax=180, ymax=90, xdist=360, ydist=180; + gdouble cur_lat = 0; + int xpos=0, ypos=0; + gdouble cur_res = ll2m(xdist, cur_lat)/info->width; + while (cur_res > resolution && + cur_res > info->resolution) { + + xpos = ((lon - xmin) / xdist) * 4; + ypos = ((lat - 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); + cur_lat = MIN(ABS(ymin), ABS(ymax)); + + node = node->children[xpos][ypos]; + if (node == NULL) + break; + if (node->data) + best = node; + + cur_res = ll2m(xdist, cur_lat)/info->width; + } + 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, WmsCacheNode *root, + gdouble res, gdouble lat, gdouble lon, + WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, gpointer user_data) +{ + /* Fetch a node, if it isn't cached, cache it, also keep it's parent cached */ + gboolean correct; + WmsCacheNode *node = wms_info_fetch(info, root, res, lat, lon, &correct); + if (!node || !correct) + wms_info_cache(info, res, lat, lon, chunk_callback, done_callback, user_data); + //else if (node->parent && node->parent->data == NULL) + // wms_info_cache(info, node->parent->res, lat, lon, chunk_callback, done_callback, user_data); + //else if (node->parent) + // node->parent->atime = node->atime; + return node; +} + +/* Delete unused nodes and prune empty branches */ +static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node) +{ + gboolean empty = FALSE; + if (self->atime - node->atime > self->max_age && + node->data && node != self->cache_root && !node->caching) { + 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); + /* + * TODO: Don't prune nodes while we're caching WmsCacheNodes in the Roam triangles + * and points + g_free(node); + return NULL; + */ + return node; + } 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); + gchar **char_data = (gchar**)&bil->data; + g_file_get_contents(path, char_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; + gchar **char_data = (gchar**)&data; + g_file_get_contents(path, char_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); +}