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(gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
84 WmsCacheNode *self = g_new0(WmsCacheNode, 1);
85 //g_debug("WmsCacheNode: new - %p %+7.3f,%+7.3f,%+7.3f,%+7.3f",
86 // self, xmin, ymin, xmax, ymax);
87 self->latlon[0] = xmin;
88 self->latlon[1] = ymin;
89 self->latlon[2] = xmax;
90 self->latlon[3] = ymax;
94 void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer)
96 //g_debug("WmsCacheNode: free - %p", node);
101 for (int x = 0; x < 4; x++)
102 for (int y = 0; y < 4; y++)
103 if (node->children[x][y])
104 wms_cache_node_free(node->children[x][y], freeer);
111 WmsInfo *wms_info_new(WmsLoader loader, WmsFreeer freeer,
112 gchar *uri_prefix, gchar *uri_layer, gchar *uri_format,
113 gchar *cache_prefix, gchar *cache_ext,
114 gint resolution, gint width, gint height
116 WmsInfo *self = g_new0(WmsInfo, 1);
117 self->loader = loader;
118 self->freeer = freeer;
119 self->uri_prefix = uri_prefix;
120 self->uri_layer = uri_layer;
121 self->uri_format = uri_format;
122 self->cache_prefix = cache_prefix;
123 self->cache_ext = cache_ext;
124 self->resolution = resolution;
126 self->height = height;
129 self->atime = time(NULL);
130 self->gc_source = g_timeout_add_seconds(1, (GSourceFunc)wms_info_gc, self);
131 self->cache_root = wms_cache_node_new(-180, -90, 180, 90);
132 self->soup = soup_session_async_new();
136 struct _CacheImageState {
141 WmsChunkCallback user_chunk_cb;
142 WmsDoneCallback user_done_cb;
145 void wms_info_soup_chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _state)
147 struct _CacheImageState *state = _state;
148 if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
151 goffset total = soup_message_headers_get_content_length(message->response_headers);
152 if (fwrite(chunk->data, chunk->length, 1, state->output) != 1)
153 g_warning("WmsInfo: soup_chunk_cb - eror writing data");
155 gdouble cur = (gdouble)ftell(state->output);
156 if (state->user_chunk_cb)
157 state->user_chunk_cb(cur, total, state->user_data);
159 void wms_info_soup_done_cb(SoupSession *session, SoupMessage *message, gpointer _state)
161 struct _CacheImageState *state = _state;
162 if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
164 gchar *dest = g_strndup(state->path, strlen(state->path)-5);
165 g_rename(state->path, dest);
166 state->node->atime = time(NULL);
167 state->info->loader(state->node, dest, state->info->width, state->info->height);
168 if (state->user_done_cb)
169 state->user_done_cb(state->node, state->user_data);
170 state->node->caching = FALSE;
171 fclose(state->output);
176 gboolean wms_info_cache_loader_cb(gpointer _state)
178 struct _CacheImageState *state = _state;
179 state->node->atime = time(NULL);
180 state->info->loader(state->node, state->path, state->info->width, state->info->height);
181 if (state->user_done_cb)
182 state->user_done_cb(state->node, state->user_data);
183 state->node->caching = FALSE;
189 * Cache required tiles
190 * 1. Load closest tile that's stored on disk
191 * 2. Fetch the correct tile from the remote server
193 void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
194 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback,
197 /* Base cache path */
198 gdouble x=lon, y=lat;
199 gdouble xmin=-180, ymin=-90, xmax=180, ymax=90;
200 gdouble xdist = xmax - xmin;
201 gdouble ydist = ymax - ymin;
205 WmsCacheNode *target_node = info->cache_root;
206 WmsCacheNode *approx_node = NULL;
208 GString *target_path = g_string_new(g_get_user_cache_dir());
209 g_string_append(target_path, G_DIR_SEPARATOR_S);
210 g_string_append(target_path, "wms");
211 g_string_append(target_path, G_DIR_SEPARATOR_S);
212 g_string_append(target_path, info->cache_prefix);
213 gchar *approx_path = NULL;
215 /* Create nodes to tiles, determine paths and lat-lon coords */
217 /* Update the best approximation if it exists on disk */
218 gchar *tmp = g_strconcat(target_path->str, info->cache_ext, NULL);
219 if (g_file_test(tmp, G_FILE_TEST_EXISTS)) {
221 approx_node = target_node;
227 /* Break if current resolution (m/px) is good enough */
228 if (ll2m(xdist, cur_lat)/info->width < resolution ||
229 ll2m(xdist, cur_lat)/info->width < info->resolution)
232 /* Get locations for the correct sub-tile */
233 xpos = (int)(((x - xmin) / xdist) * 4);
234 ypos = (int)(((y - ymin) / ydist) * 4);
235 if (xpos == 4) xpos--;
236 if (ypos == 4) ypos--;
239 xmin = xmin + xdist*(xpos+0);
240 ymin = ymin + ydist*(ypos+0);
243 cur_lat = MIN(ABS(ymin), ABS(ymax));
245 /* Update target for correct sub-tile */
246 g_string_append_printf(target_path, "/%d%d", xpos, ypos);
247 if (target_node->children[xpos][ypos] == NULL)
248 target_node->children[xpos][ypos] =
249 wms_cache_node_new(xmin, ymin, xmax, ymax);
250 target_node = target_node->children[xpos][ypos];
253 /* Load disk on-disk approximation, TODO: async */
254 if (approx_node && !approx_node->data && !approx_node->caching) {
255 approx_node->caching = TRUE;
256 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
258 state->path = approx_path;
259 state->node = approx_node;
260 state->user_done_cb = done_callback;
261 state->user_data = user_data;
262 g_idle_add(wms_info_cache_loader_cb, state);
267 /* If target image is not on-disk, download it now */
268 if (target_node != approx_node && !target_node->caching) {
269 target_node->caching = TRUE;
270 g_string_append(target_path, info->cache_ext);
271 g_string_append(target_path, ".part");
273 gchar *dirname = g_path_get_dirname(target_path->str);
274 g_mkdir_with_parents(dirname, 0755);
277 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
279 state->path = target_path->str;
280 state->output = fopen(target_path->str, "a");
281 state->node = target_node;
282 state->user_chunk_cb = chunk_callback;
283 state->user_done_cb = done_callback;
284 state->user_data = user_data;
286 gchar *uri = wms_make_uri(info, xmin, ymin, xmax, ymax);
287 SoupMessage *message = soup_message_new("GET", uri);
288 g_signal_connect(message, "got-chunk", G_CALLBACK(wms_info_soup_chunk_cb), state);
289 soup_message_headers_set_range(message->request_headers, ftell(state->output), -1);
291 soup_session_queue_message(info->soup, message, wms_info_soup_done_cb, state);
293 g_debug("Caching file: %s -> %s", uri, state->path);
295 g_string_free(target_path, FALSE);
297 g_string_free(target_path, TRUE);
300 WmsCacheNode *wms_info_fetch(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
303 if (info->cache_root == NULL) {
307 WmsCacheNode *node = info->cache_root;
308 WmsCacheNode *best = (node && node->data ? node : NULL);
309 gdouble x=lon, y=lat;
310 gdouble xmin=-180, ymin=-90, xmax=180, ymax=90;
311 gdouble xdist = xmax - xmin;
312 gdouble ydist = ymax - ymin;
315 while (ll2m(xdist, cur_lat)/info->width > resolution &&
316 ll2m(xdist, cur_lat)/info->width > info->resolution) {
318 xpos = (int)(((x - xmin) / xdist) * 4);
319 ypos = (int)(((y - ymin) / ydist) * 4);
320 //g_message("%d = (int)(((%f - %f) / %f) * 4)",
321 // xpos, x, xmin, xdist);
322 if (xpos == 4) xpos--;
323 if (ypos == 4) ypos--;
326 xmin = xmin + xdist*(xpos+0);
327 ymin = ymin + ydist*(ypos+0);
330 cur_lat = MIN(ABS(ymin), ABS(ymax));
332 node = node->children[xpos][ypos];
339 *correct = (node && node == best);
340 info->atime = time(NULL);
342 best->atime = info->atime;
346 WmsCacheNode *wms_info_fetch_cache(WmsInfo *info, gdouble res, gdouble lat, gdouble lon,
347 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, gpointer user_data)
350 WmsCacheNode *node = wms_info_fetch(info, res, lat, lon, &correct);
351 if (!node || !correct)
352 wms_info_cache(info, res, lat, lon, chunk_callback, done_callback, user_data);
356 /* Delete unused nodes and prune empty branches */
357 static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node)
359 gboolean empty = FALSE;
360 if (node->data && !node->caching &&
361 self->atime - node->atime > self->max_age) {
362 g_debug("WmsInfo: gc - expired node %p", node);
367 for (int x = 0; x < 4; x++)
368 for (int y = 0; y < 4; y++)
369 if (node->children[x][y]) {
370 node->children[x][y] =
371 wms_info_gc_cb(self, node->children[x][y]);
375 g_debug("WmsInfo: gc - empty branch %p", node);
383 gboolean wms_info_gc(WmsInfo *self)
385 if (!wms_info_gc_cb(self, self->cache_root))
386 g_warning("WmsInfo: gc - root not should not be empty");
390 void wms_info_free(WmsInfo *self)
392 wms_cache_node_free(self->cache_root, self->freeer);
393 g_object_unref(self->soup);
398 /************************
399 * Blue Marble Next Gen *
400 ************************/
401 void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
403 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
404 node->data = g_new0(guint, 1);
407 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
408 int alpha = gdk_pixbuf_get_has_alpha(pixbuf);
409 int nchan = 4; // gdk_pixbuf_get_n_channels(pixbuf);
412 glGenTextures(1, node->data);
413 glBindTexture(GL_TEXTURE_2D, *(guint*)node->data);
415 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
416 glPixelStorei(GL_PACK_ALIGNMENT, 1);
417 glTexImage2D(GL_TEXTURE_2D, 0, nchan, width, height, 0,
418 (alpha ? GL_RGBA : GL_RGB), GL_UNSIGNED_BYTE, pixels);
419 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
420 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
421 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
422 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
424 g_object_unref(pixbuf);
425 g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
427 void bmng_opengl_freeer(WmsCacheNode *node)
429 g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
430 glDeleteTextures(1, node->data);
434 void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
436 node->data = gdk_pixbuf_new_from_file(path, NULL);
437 g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
439 void bmng_pixbuf_freeer(WmsCacheNode *node)
441 g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
442 g_object_unref(node->data);
445 WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer)
447 loader = loader ?: bmng_opengl_loader;
448 freeer = freeer ?: bmng_opengl_freeer;
449 return wms_info_new(loader, freeer,
450 "http://www.nasa.network.com/wms", "bmng200406", "image/jpeg",
451 "bmng", ".jpg", 500, 512, 256);
454 /********************************************
455 * Shuttle Radar Topography Mission 30 Plus *
456 ********************************************/
457 void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
459 WmsBil *bil = g_new0(WmsBil, 1);
460 gchar **char_data = (gchar**)&bil->data;
461 g_file_get_contents(path, char_data, NULL, NULL);
463 bil->height = height;
465 g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
467 void srtm_bil_freeer(WmsCacheNode *node)
469 g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
470 g_free(((WmsBil*)node->data)->data);
474 void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
476 GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
477 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
478 gint stride = gdk_pixbuf_get_rowstride(pixbuf);
481 gchar **char_data = (gchar**)&data;
482 g_file_get_contents(path, char_data, NULL, NULL);
483 for (int r = 0; r < height; r++) {
484 for (int c = 0; c < width; c++) {
485 gint16 value = data[r*width + c];
486 //guchar color = (float)(MAX(value,0))/8848 * 255;
487 guchar color = (float)value/8848 * 255;
488 pixels[r*stride + c*3 + 0] = color;
489 pixels[r*stride + c*3 + 1] = color;
490 pixels[r*stride + c*3 + 2] = color;
496 g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
498 void srtm_pixbuf_freeer(WmsCacheNode *node)
500 g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
501 g_object_unref(node->data);
504 WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer)
506 loader = loader ?: srtm_bil_loader;
507 freeer = freeer ?: srtm_bil_freeer;
508 return wms_info_new(loader, freeer,
509 "http://www.nasa.network.com/elev", "srtm30", "application/bil",
510 "srtm", ".bil", 500, 512, 256);