]> Pileus Git - grits/blob - src/wms.c
92a3476a8422c73e0422e84acaa330aa026ac884
[grits] / src / wms.c
1 /*
2  * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
3  * 
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.
8  * 
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.
13  * 
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/>.
16  */
17
18 /**
19  * http://www.nasa.network.com/elev?
20  * SERVICE=WMS&
21  * VERSION=1.1.0&
22  * REQUEST=GetMap&
23  * LAYERS=bmng200406&
24  * STYLES=&
25  * SRS=EPSG:4326&
26  * BBOX=-180,-90,180,90&
27  * FORMAT=image/jpeg&
28  * WIDTH=600&
29  * HEIGHT=300
30  * 
31  * http://www.nasa.network.com/elev?
32  * SERVICE=WMS&
33  * VERSION=1.1.0&
34  * REQUEST=GetMap&
35  * LAYERS=srtm30&
36  * STYLES=&
37  * SRS=EPSG:4326&
38  * BBOX=-180,-90,180,90&
39  * FORMAT=application/bil32&
40  * WIDTH=600&
41  * HEIGHT=300
42  */
43
44 #include <string.h>
45 #include <stdio.h>
46 #include <time.h>
47 #include <gtk/gtk.h>
48 #include <glib/gstdio.h>
49 #include <libsoup/soup.h>
50
51 #include "wms.h"
52
53 /* TODO: try to remove these */
54 #include "gis-world.h"
55 #include <GL/gl.h>
56
57 gchar *wms_make_uri(WmsInfo *info, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
58 {
59         return g_strdup_printf(
60                 "%s?"
61                 "SERVICE=WMS&"
62                 "VERSION=1.1.0&"
63                 "REQUEST=GetMap&"
64                 "LAYERS=%s&"
65                 "STYLES=&"
66                 "SRS=EPSG:4326&"
67                 "BBOX=%f,%f,%f,%f&"
68                 "FORMAT=%s&"
69                 "WIDTH=%d&"
70                 "HEIGHT=%d",
71                 info->uri_prefix,
72                 info->uri_layer,
73                 xmin, ymin, xmax, ymax,
74                 info->uri_format,
75                 info->width,
76                 info->height);
77 }
78
79 /****************
80  * WmsCacheNode *
81  ****************/
82 WmsCacheNode *wms_cache_node_new(WmsCacheNode *parent,
83                 gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax, gint width)
84 {
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;
95         else
96                 self->res = ll2m(xmax-xmin, MIN(ABS(ymin),ABS(ymax)))/width;
97         return self;
98 }
99
100 void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer)
101 {
102         //g_debug("WmsCacheNode: free - %p", node);
103         if (node->data) {
104                 freeer(node);
105                 node->data = NULL;
106         }
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);
111         g_free(node);
112 }
113
114 /***********
115  * WmsInfo *
116  ***********/
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
121 ) {
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;
131         self->width        = width;
132         self->height       = height;
133
134         self->max_age      = 60;
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();
139         return self;
140 }
141
142 struct _CacheImageState {
143         WmsInfo *info;
144         gchar *path;
145         FILE *output;
146         WmsCacheNode *node;
147         WmsChunkCallback user_chunk_cb;
148         WmsDoneCallback user_done_cb;
149         gpointer user_data;
150 };
151 void wms_info_soup_chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _state)
152 {
153         struct _CacheImageState *state = _state;
154         if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
155                 return;
156
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");
160
161         gdouble cur = (gdouble)ftell(state->output);
162         if (state->user_chunk_cb)
163                 state->user_chunk_cb(cur, total, state->user_data);
164 }
165 void wms_info_soup_done_cb(SoupSession *session, SoupMessage *message, gpointer _state)
166 {
167         struct _CacheImageState *state = _state;
168         if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
169                 return;
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);
178         g_free(state->path);
179         g_free(dest);
180         g_free(state);
181 }
182 gboolean wms_info_cache_loader_cb(gpointer _state)
183 {
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;
190         g_free(state->path);
191         g_free(state);
192         return FALSE;
193 }
194 /**
195  * Cache required tiles
196  * 1. Load closest tile that's stored on disk
197  * 2. Fetch the correct tile from the remote server
198  */
199 void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
200                 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback,
201                 gpointer user_data)
202 {
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;
208         int xpos=0, ypos=0;
209         gdouble cur_lat = 0;
210
211         WmsCacheNode *target_node = info->cache_root;
212         WmsCacheNode *approx_node = NULL;
213
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;
220
221         /* Create nodes to tiles, determine paths and lat-lon coords */
222         while (TRUE) {
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)) {
226                         g_free(approx_path);
227                         approx_node = target_node;
228                         approx_path = tmp;
229                 } else {
230                         g_free(tmp);
231                 }
232
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)
236                         break;
237
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--;
243                 xdist /= 4;
244                 ydist /= 4;
245                 xmin = xmin + xdist*(xpos+0);
246                 ymin = ymin + ydist*(ypos+0);
247                 xmax = xmin + xdist;
248                 ymax = ymin + ydist; 
249                 cur_lat = MIN(ABS(ymin), ABS(ymax));
250
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];
258         }
259
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);
264                 state->info          = info;
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);
270         } else { 
271                 g_free(approx_path);
272         }
273
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");
279
280                 gchar *dirname = g_path_get_dirname(target_path->str);
281                 g_mkdir_with_parents(dirname, 0755);
282                 g_free(dirname);
283
284                 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
285                 state->info          = info;
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;
292
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);
297
298                 soup_session_queue_message(info->soup, message, wms_info_soup_done_cb, state);
299
300                 g_debug("Caching file: %s -> %s", uri, state->path);
301                 g_free(uri);
302                 g_string_free(target_path, FALSE);
303         } else {
304                 g_string_free(target_path, TRUE);
305         }
306 }
307 /* TODO:
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
310  */
311 WmsCacheNode *wms_info_fetch(WmsInfo *info, WmsCacheNode *root,
312                 gdouble resolution, gdouble lat, gdouble lon,
313                 gboolean *correct)
314 {
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)) {
320                 *correct = TRUE;
321                 info->atime = time(NULL);
322                 root->atime = info->atime;
323                 return root;
324         }
325
326         if (info->cache_root == NULL) {
327                 *correct = FALSE;
328                 return NULL;
329         }
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;
333         gdouble cur_lat = 0;
334         int xpos=0, ypos=0;
335         gdouble cur_res = ll2m(xdist, cur_lat)/info->width;
336         while (cur_res > resolution  &&
337                cur_res > info->resolution) {
338
339                 xpos = ((lon - xmin) / xdist) * 4;
340                 ypos = ((lat - ymin) / ydist) * 4;
341                 if (xpos == 4) xpos--;
342                 if (ypos == 4) ypos--;
343                 xdist /= 4;
344                 ydist /= 4;
345                 xmin = xmin + xdist*(xpos+0);
346                 ymin = ymin + ydist*(ypos+0);
347                 cur_lat = MIN(ABS(ymin), ABS(ymax));
348
349                 node = node->children[xpos][ypos];
350                 if (node == NULL)
351                         break;
352                 if (node->data)
353                         best = node;
354
355                 cur_res = ll2m(xdist, cur_lat)/info->width;
356         }
357         if (correct)
358                 *correct = (node && node == best);
359         info->atime = time(NULL);
360         if (best)
361                 best->atime = info->atime;
362         return best;
363 }
364
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)
368 {
369         /* Fetch a node, if it isn't cached, cache it, also keep it's parent cached */
370         gboolean correct;
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;
378         return node;
379 }
380
381 /* Delete unused nodes and prune empty branches */
382 static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node)
383 {
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);
388                 self->freeer(node);
389                 node->data = NULL;
390                 empty = TRUE;
391         }
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]);
397                                 empty = FALSE;
398                         }
399         if (empty) {
400                 g_debug("WmsInfo: gc - empty branch %p", node);
401                 /* 
402                  * TODO: Don't prune nodes while we're caching WmsCacheNodes in the Roam triangles
403                  * and points
404                 g_free(node);
405                 return NULL;
406                 */
407                 return node;
408         } else {
409                 return node;
410         }
411 }
412
413 gboolean wms_info_gc(WmsInfo *self)
414 {
415         if (!wms_info_gc_cb(self, self->cache_root))
416                 g_warning("WmsInfo: gc - root not should not be empty");
417         return TRUE;
418 }
419
420 void wms_info_free(WmsInfo *self)
421 {
422         wms_cache_node_free(self->cache_root, self->freeer);
423         g_object_unref(self->soup);
424         g_free(self);
425 }
426
427
428 /************************
429  * Blue Marble Next Gen *
430  ************************/
431 void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
432 {
433         GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
434         node->data = g_new0(guint, 1);
435
436         /* Load image */
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);
440
441         /* Create Texture */
442         glGenTextures(1, node->data);
443         glBindTexture(GL_TEXTURE_2D, *(guint*)node->data);
444
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);
453
454         g_object_unref(pixbuf);
455         g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
456 }
457 void bmng_opengl_freeer(WmsCacheNode *node)
458 {
459         g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
460         glDeleteTextures(1, node->data);
461         g_free(node->data);
462 }
463
464 void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
465 {
466         node->data = gdk_pixbuf_new_from_file(path, NULL);
467         g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
468 }
469 void bmng_pixbuf_freeer(WmsCacheNode *node)
470 {
471         g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
472         g_object_unref(node->data);
473 }
474
475 WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer)
476 {
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);
482 }
483
484 /********************************************
485  * Shuttle Radar Topography Mission 30 Plus *
486  ********************************************/
487 void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
488 {
489         WmsBil *bil = g_new0(WmsBil, 1);
490         gchar **char_data = (gchar**)&bil->data;
491         g_file_get_contents(path, char_data, NULL, NULL);
492         bil->width  = width;
493         bil->height = height;
494         node->data = bil;
495         g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
496 }
497 void srtm_bil_freeer(WmsCacheNode *node)
498 {
499         g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
500         g_free(((WmsBil*)node->data)->data);
501         g_free(node->data);
502 }
503
504 void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
505 {
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);
509
510         gint16 *data; 
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;
521                 }
522         }
523         g_free(data);
524
525         node->data = pixbuf;
526         g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
527 }
528 void srtm_pixbuf_freeer(WmsCacheNode *node)
529 {
530         g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
531         g_object_unref(node->data);
532 }
533
534 WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer)
535 {
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);
541 }