2 * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * http://www.nasa.network.com/elev?
26 * BBOX=-180,-90,180,90&
31 * http://www.nasa.network.com/elev?
38 * BBOX=-180,-90,180,90&
39 * FORMAT=application/bil32&
48 #include <glib/gstdio.h>
49 #include <libsoup/soup.h>
53 /* TODO: try to remove these */
54 #include "gis-world.h"
57 gchar *wms_make_uri(WmsInfo *info, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
59 return g_strdup_printf(
73 xmin, ymin, xmax, ymax,
82 WmsCacheNode *wms_cache_node_new(WmsCacheNode *parent,
83 gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax, gint width)
85 WmsCacheNode *self = g_new0(WmsCacheNode, 1);
86 //g_debug("WmsCacheNode: new - %p %+7.3f,%+7.3f,%+7.3f,%+7.3f",
87 // self, xmin, ymin, xmax, ymax);
88 self->latlon[0] = xmin;
89 self->latlon[1] = ymin;
90 self->latlon[2] = xmax;
91 self->latlon[3] = ymax;
92 self->parent = parent;
93 if (ymin <= 0 && ymax >= 0)
94 self->res = ll2m(xmax-xmin, 0)/width;
96 self->res = ll2m(xmax-xmin, MIN(ABS(ymin),ABS(ymax)))/width;
100 void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer)
102 //g_debug("WmsCacheNode: free - %p", node);
107 for (int x = 0; x < 4; x++)
108 for (int y = 0; y < 4; y++)
109 if (node->children[x][y])
110 wms_cache_node_free(node->children[x][y], freeer);
117 WmsInfo *wms_info_new(WmsLoader loader, WmsFreeer freeer,
118 gchar *uri_prefix, gchar *uri_layer, gchar *uri_format,
119 gchar *cache_prefix, gchar *cache_ext,
120 gint resolution, gint width, gint height
122 WmsInfo *self = g_new0(WmsInfo, 1);
123 self->loader = loader;
124 self->freeer = freeer;
125 self->uri_prefix = uri_prefix;
126 self->uri_layer = uri_layer;
127 self->uri_format = uri_format;
128 self->cache_prefix = cache_prefix;
129 self->cache_ext = cache_ext;
130 self->resolution = resolution;
132 self->height = height;
135 self->atime = time(NULL);
136 self->gc_source = g_timeout_add_seconds(1, (GSourceFunc)wms_info_gc, self);
137 self->cache_root = wms_cache_node_new(NULL, -180, -90, 180, 90, width);
138 self->soup = soup_session_async_new();
142 struct _CacheImageState {
147 WmsChunkCallback user_chunk_cb;
148 WmsDoneCallback user_done_cb;
151 void wms_info_soup_chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _state)
153 struct _CacheImageState *state = _state;
154 if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
157 goffset total = soup_message_headers_get_content_length(message->response_headers);
158 if (fwrite(chunk->data, chunk->length, 1, state->output) != 1)
159 g_warning("WmsInfo: soup_chunk_cb - eror writing data");
161 gdouble cur = (gdouble)ftell(state->output);
162 if (state->user_chunk_cb)
163 state->user_chunk_cb(cur, total, state->user_data);
165 void wms_info_soup_done_cb(SoupSession *session, SoupMessage *message, gpointer _state)
167 struct _CacheImageState *state = _state;
168 if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
170 gchar *dest = g_strndup(state->path, strlen(state->path)-5);
171 g_rename(state->path, dest);
172 state->node->atime = time(NULL);
173 state->info->loader(state->node, dest, state->info->width, state->info->height);
174 if (state->user_done_cb)
175 state->user_done_cb(state->node, state->user_data);
176 state->node->caching = FALSE;
177 fclose(state->output);
182 gboolean wms_info_cache_loader_cb(gpointer _state)
184 struct _CacheImageState *state = _state;
185 state->node->atime = time(NULL);
186 state->info->loader(state->node, state->path, state->info->width, state->info->height);
187 if (state->user_done_cb)
188 state->user_done_cb(state->node, state->user_data);
189 state->node->caching = FALSE;
195 * Cache required tiles
196 * 1. Load closest tile that's stored on disk
197 * 2. Fetch the correct tile from the remote server
199 void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
200 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback,
203 /* Base cache path */
204 gdouble x=lon, y=lat;
205 gdouble xmin=-180, ymin=-90, xmax=180, ymax=90;
206 gdouble xdist = xmax - xmin;
207 gdouble ydist = ymax - ymin;
211 WmsCacheNode *target_node = info->cache_root;
212 WmsCacheNode *approx_node = NULL;
214 GString *target_path = g_string_new(g_get_user_cache_dir());
215 g_string_append(target_path, G_DIR_SEPARATOR_S);
216 g_string_append(target_path, "wms");
217 g_string_append(target_path, G_DIR_SEPARATOR_S);
218 g_string_append(target_path, info->cache_prefix);
219 gchar *approx_path = NULL;
221 /* Create nodes to tiles, determine paths and lat-lon coords */
223 /* Update the best approximation if it exists on disk */
224 gchar *tmp = g_strconcat(target_path->str, info->cache_ext, NULL);
225 if (g_file_test(tmp, G_FILE_TEST_EXISTS)) {
227 approx_node = target_node;
233 /* Break if current resolution (m/px) is good enough */
234 if (ll2m(xdist, cur_lat)/info->width <= resolution ||
235 ll2m(xdist, cur_lat)/info->width <= info->resolution)
238 /* Get locations for the correct sub-tile */
239 xpos = (int)(((x - xmin) / xdist) * 4);
240 ypos = (int)(((y - ymin) / ydist) * 4);
241 if (xpos == 4) xpos--;
242 if (ypos == 4) ypos--;
245 xmin = xmin + xdist*(xpos+0);
246 ymin = ymin + ydist*(ypos+0);
249 cur_lat = MIN(ABS(ymin), ABS(ymax));
251 /* Update target for correct sub-tile */
252 g_string_append_printf(target_path, "/%d%d", xpos, ypos);
253 if (target_node->children[xpos][ypos] == NULL)
254 target_node->children[xpos][ypos] =
255 wms_cache_node_new(target_node,
256 xmin, ymin, xmax, ymax, info->width);
257 target_node = target_node->children[xpos][ypos];
260 /* Load disk on-disk approximation, TODO: async */
261 if (approx_node && !approx_node->data && !approx_node->caching) {
262 approx_node->caching = TRUE;
263 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
265 state->path = approx_path;
266 state->node = approx_node;
267 state->user_done_cb = done_callback;
268 state->user_data = user_data;
269 g_idle_add(wms_info_cache_loader_cb, state);
274 /* If target image is not on-disk, download it now */
275 if (target_node != approx_node && !target_node->caching) {
276 target_node->caching = TRUE;
277 g_string_append(target_path, info->cache_ext);
278 g_string_append(target_path, ".part");
280 gchar *dirname = g_path_get_dirname(target_path->str);
281 g_mkdir_with_parents(dirname, 0755);
284 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
286 state->path = target_path->str;
287 state->output = fopen(target_path->str, "a");
288 state->node = target_node;
289 state->user_chunk_cb = chunk_callback;
290 state->user_done_cb = done_callback;
291 state->user_data = user_data;
293 gchar *uri = wms_make_uri(info, xmin, ymin, xmax, ymax);
294 SoupMessage *message = soup_message_new("GET", uri);
295 g_signal_connect(message, "got-chunk", G_CALLBACK(wms_info_soup_chunk_cb), state);
296 soup_message_headers_set_range(message->request_headers, ftell(state->output), -1);
298 soup_session_queue_message(info->soup, message, wms_info_soup_done_cb, state);
300 g_debug("Caching file: %s -> %s", uri, state->path);
302 g_string_free(target_path, FALSE);
304 g_string_free(target_path, TRUE);
308 * - Store WmsCacheNode in point and then use parent pointers to go up/down
309 * - If resolution doesn't change, tell caller to skip remaining calculations
311 WmsCacheNode *wms_info_fetch(WmsInfo *info, WmsCacheNode *root,
312 gdouble resolution, gdouble lat, gdouble lon,
315 if (root && root->data && !root->caching &&
316 root->latlon[0] <= lon && lon <= root->latlon[2] &&
317 root->latlon[1] <= lat && lat <= root->latlon[3] &&
318 root->res <= resolution &&
319 (!root->parent || root->parent->res > resolution)) {
321 info->atime = time(NULL);
322 root->atime = info->atime;
326 if (info->cache_root == NULL) {
330 WmsCacheNode *node = info->cache_root;
331 WmsCacheNode *best = (node && node->data ? node : NULL);
332 gdouble xmin=-180, ymin=-90, xmax=180, ymax=90, xdist=360, ydist=180;
335 gdouble cur_res = ll2m(xdist, cur_lat)/info->width;
336 while (cur_res > resolution &&
337 cur_res > info->resolution) {
339 xpos = ((lon - xmin) / xdist) * 4;
340 ypos = ((lat - ymin) / ydist) * 4;
341 if (xpos == 4) xpos--;
342 if (ypos == 4) ypos--;
345 xmin = xmin + xdist*(xpos+0);
346 ymin = ymin + ydist*(ypos+0);
347 cur_lat = MIN(ABS(ymin), ABS(ymax));
349 node = node->children[xpos][ypos];
355 cur_res = ll2m(xdist, cur_lat)/info->width;
358 *correct = (node && node == best);
359 info->atime = time(NULL);
361 best->atime = info->atime;
365 WmsCacheNode *wms_info_fetch_cache(WmsInfo *info, WmsCacheNode *root,
366 gdouble res, gdouble lat, gdouble lon,
367 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, gpointer user_data)
369 /* Fetch a node, if it isn't cached, cache it, also keep it's parent cached */
371 WmsCacheNode *node = wms_info_fetch(info, root, res, lat, lon, &correct);
372 if (!node || !correct)
373 wms_info_cache(info, res, lat, lon, chunk_callback, done_callback, user_data);
374 //else if (node->parent && node->parent->data == NULL)
375 // wms_info_cache(info, node->parent->res, lat, lon, chunk_callback, done_callback, user_data);
376 //else if (node->parent)
377 // node->parent->atime = node->atime;
381 /* Delete unused nodes and prune empty branches */
382 static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node)
384 gboolean empty = FALSE;
385 if (self->atime - node->atime > self->max_age &&
386 node->data && node != self->cache_root && !node->caching) {
387 g_debug("WmsInfo: gc - expired node %p", node);
392 for (int x = 0; x < 4; x++)
393 for (int y = 0; y < 4; y++)
394 if (node->children[x][y]) {
395 node->children[x][y] =
396 wms_info_gc_cb(self, node->children[x][y]);
400 g_debug("WmsInfo: gc - empty branch %p", node);
402 * TODO: Don't prune nodes while we're caching WmsCacheNodes in the Roam triangles
413 gboolean wms_info_gc(WmsInfo *self)
415 if (!wms_info_gc_cb(self, self->cache_root))
416 g_warning("WmsInfo: gc - root not should not be empty");
420 void wms_info_free(WmsInfo *self)
422 wms_cache_node_free(self->cache_root, self->freeer);
423 g_object_unref(self->soup);
428 /************************
429 * Blue Marble Next Gen *
430 ************************/
431 void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
433 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
434 node->data = g_new0(guint, 1);
437 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
438 int alpha = gdk_pixbuf_get_has_alpha(pixbuf);
439 int nchan = 4; // gdk_pixbuf_get_n_channels(pixbuf);
442 glGenTextures(1, node->data);
443 glBindTexture(GL_TEXTURE_2D, *(guint*)node->data);
445 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
446 glPixelStorei(GL_PACK_ALIGNMENT, 1);
447 glTexImage2D(GL_TEXTURE_2D, 0, nchan, width, height, 0,
448 (alpha ? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, pixels);
449 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
450 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
451 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
452 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
454 g_object_unref(pixbuf);
455 g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
457 void bmng_opengl_freeer(WmsCacheNode *node)
459 g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
460 glDeleteTextures(1, node->data);
464 void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
466 node->data = gdk_pixbuf_new_from_file(path, NULL);
467 g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
469 void bmng_pixbuf_freeer(WmsCacheNode *node)
471 g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
472 g_object_unref(node->data);
475 WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer)
477 loader = loader ?: bmng_opengl_loader;
478 freeer = freeer ?: bmng_opengl_freeer;
479 return wms_info_new(loader, freeer,
480 "http://www.nasa.network.com/wms", "bmng200406", "image/jpeg",
481 "bmng", ".jpg", 500, 512, 256);
484 /********************************************
485 * Shuttle Radar Topography Mission 30 Plus *
486 ********************************************/
487 void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
489 WmsBil *bil = g_new0(WmsBil, 1);
490 gchar **char_data = (gchar**)&bil->data;
491 g_file_get_contents(path, char_data, NULL, NULL);
493 bil->height = height;
495 g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
497 void srtm_bil_freeer(WmsCacheNode *node)
499 g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
500 g_free(((WmsBil*)node->data)->data);
504 void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
506 GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
507 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
508 gint stride = gdk_pixbuf_get_rowstride(pixbuf);
511 gchar **char_data = (gchar**)&data;
512 g_file_get_contents(path, char_data, NULL, NULL);
513 for (int r = 0; r < height; r++) {
514 for (int c = 0; c < width; c++) {
515 gint16 value = data[r*width + c];
516 //guchar color = (float)(MAX(value,0))/8848 * 255;
517 guchar color = (float)value/8848 * 255;
518 pixels[r*stride + c*3 + 0] = color;
519 pixels[r*stride + c*3 + 1] = color;
520 pixels[r*stride + c*3 + 2] = color;
526 g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
528 void srtm_pixbuf_freeer(WmsCacheNode *node)
530 g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
531 g_object_unref(node->data);
534 WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer)
536 loader = loader ?: srtm_bil_loader;
537 freeer = freeer ?: srtm_bil_freeer;
538 return wms_info_new(loader, freeer,
539 "http://www.nasa.network.com/elev", "srtm30", "application/bil",
540 "srtm", ".bil", 500, 512, 256);