]> Pileus Git - grits/blob - src/gis/wms.c
Lots of work on libGIS
[grits] / src / gis / 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(gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
83 {
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;
91         return self;
92 }
93
94 void wms_cache_node_free(WmsCacheNode *node, WmsFreeer freeer)
95 {
96         //g_debug("WmsCacheNode: free - %p", node);
97         if (node->data) {
98                 freeer(node);
99                 node->data = NULL;
100         }
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);
105         g_free(node);
106 }
107
108 /***********
109  * WmsInfo *
110  ***********/
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
115 ) {
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;
125         self->width        = width;
126         self->height       = height;
127
128         self->max_age      = 30;
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();
133         return self;
134 }
135
136 struct _CacheImageState {
137         WmsInfo *info;
138         gchar *path;
139         FILE *output;
140         WmsCacheNode *node;
141         WmsChunkCallback user_chunk_cb;
142         WmsDoneCallback user_done_cb;
143         gpointer user_data;
144 };
145 void wms_info_soup_chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _state)
146 {
147         struct _CacheImageState *state = _state;
148         if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
149                 return;
150
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");
154
155         gdouble cur = (gdouble)ftell(state->output);
156         if (state->user_chunk_cb)
157                 state->user_chunk_cb(cur, total, state->user_data);
158 }
159 void wms_info_soup_done_cb(SoupSession *session, SoupMessage *message, gpointer _state)
160 {
161         struct _CacheImageState *state = _state;
162         if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code))
163                 return;
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);
172         g_free(state->path);
173         g_free(dest);
174         g_free(state);
175 }
176 gboolean wms_info_cache_loader_cb(gpointer _state)
177 {
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;
184         g_free(state->path);
185         g_free(state);
186         return FALSE;
187 }
188 /**
189  * Cache required tiles
190  * 1. Load closest tile that's stored on disk
191  * 2. Fetch the correct tile from the remote server
192  */
193 void wms_info_cache(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
194                 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback,
195                 gpointer user_data)
196 {
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;
202         int xpos=0, ypos=0;
203         gdouble cur_lat = 0;
204
205         WmsCacheNode *target_node = info->cache_root;
206         WmsCacheNode *approx_node = NULL;
207
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;
214
215         /* Create nodes to tiles, determine paths and lat-lon coords */
216         while (TRUE) {
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)) {
220                         g_free(approx_path);
221                         approx_node = target_node;
222                         approx_path = tmp;
223                 } else {
224                         g_free(tmp);
225                 }
226
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)
230                         break;
231
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--;
237                 xdist /= 4;
238                 ydist /= 4;
239                 xmin = xmin + xdist*(xpos+0);
240                 ymin = ymin + ydist*(ypos+0);
241                 xmax = xmin + xdist;
242                 ymax = ymin + ydist; 
243                 cur_lat = MIN(ABS(ymin), ABS(ymax));
244
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];
251         }
252
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);
257                 state->info          = info;
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);
263         } else { 
264                 g_free(approx_path);
265         }
266
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");
272
273                 gchar *dirname = g_path_get_dirname(target_path->str);
274                 g_mkdir_with_parents(dirname, 0755);
275                 g_free(dirname);
276
277                 struct _CacheImageState *state = g_new0(struct _CacheImageState, 1);
278                 state->info          = info;
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;
285
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);
290
291                 soup_session_queue_message(info->soup, message, wms_info_soup_done_cb, state);
292
293                 g_debug("Caching file: %s -> %s", uri, state->path);
294                 g_free(uri);
295                 g_string_free(target_path, FALSE);
296         } else {
297                 g_string_free(target_path, TRUE);
298         }
299 }
300 WmsCacheNode *wms_info_fetch(WmsInfo *info, gdouble resolution, gdouble lat, gdouble lon,
301                 gboolean *correct)
302 {
303         if (info->cache_root == NULL) {
304                 *correct = FALSE;
305                 return NULL;
306         }
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;
313         gdouble cur_lat = 0;
314         int xpos=0, ypos=0;
315         while (ll2m(xdist, cur_lat)/info->width > resolution  &&
316                ll2m(xdist, cur_lat)/info->width > info->resolution) {
317
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--;
324                 xdist /= 4;
325                 ydist /= 4;
326                 xmin = xmin + xdist*(xpos+0);
327                 ymin = ymin + ydist*(ypos+0);
328                 xmax = xmin + xdist;
329                 ymax = ymin + ydist; 
330                 cur_lat = MIN(ABS(ymin), ABS(ymax));
331
332                 node = node->children[xpos][ypos];
333                 if (node == NULL)
334                         break;
335                 if (node->data)
336                         best = node;
337         }
338         if (correct)
339                 *correct = (node && node == best);
340         info->atime = time(NULL);
341         if (best)
342                 best->atime = info->atime;
343         return best;
344 }
345
346 WmsCacheNode *wms_info_fetch_cache(WmsInfo *info, gdouble res, gdouble lat, gdouble lon,
347                 WmsChunkCallback chunk_callback, WmsDoneCallback done_callback, gpointer user_data)
348 {
349         gboolean correct;
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);
353         return node;
354 }
355
356 /* Delete unused nodes and prune empty branches */
357 static WmsCacheNode *wms_info_gc_cb(WmsInfo *self, WmsCacheNode *node)
358 {
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);
363                 self->freeer(node);
364                 node->data = NULL;
365                 empty = TRUE;
366         }
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]);
372                                 empty = FALSE;
373                         }
374         if (empty) {
375                 g_debug("WmsInfo: gc - empty branch %p", node);
376                 g_free(node);
377                 return NULL;
378         } else {
379                 return node;
380         }
381 }
382
383 gboolean wms_info_gc(WmsInfo *self)
384 {
385         if (!wms_info_gc_cb(self, self->cache_root))
386                 g_warning("WmsInfo: gc - root not should not be empty");
387         return TRUE;
388 }
389
390 void wms_info_free(WmsInfo *self)
391 {
392         wms_cache_node_free(self->cache_root, self->freeer);
393         g_object_unref(self->soup);
394         g_free(self);
395 }
396
397
398 /************************
399  * Blue Marble Next Gen *
400  ************************/
401 void bmng_opengl_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
402 {
403         GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
404         node->data = g_new0(guint, 1);
405
406         /* Load image */
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);
410
411         /* Create Texture */
412         glGenTextures(1, node->data);
413         glBindTexture(GL_TEXTURE_2D, *(guint*)node->data);
414
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);
423
424         g_object_unref(pixbuf);
425         g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
426 }
427 void bmng_opengl_freeer(WmsCacheNode *node)
428 {
429         g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
430         glDeleteTextures(1, node->data);
431         g_free(node->data);
432 }
433
434 void bmng_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
435 {
436         node->data = gdk_pixbuf_new_from_file(path, NULL);
437         g_debug("WmsCacheNode: bmng_opengl_loader: %s -> %p", path, node->data);
438 }
439 void bmng_pixbuf_freeer(WmsCacheNode *node)
440 {
441         g_debug("WmsCacheNode: bmng_opengl_freeer: %p", node->data);
442         g_object_unref(node->data);
443 }
444
445 WmsInfo *wms_info_new_for_bmng(WmsLoader loader, WmsFreeer freeer)
446 {
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);
452 }
453
454 /********************************************
455  * Shuttle Radar Topography Mission 30 Plus *
456  ********************************************/
457 void srtm_bil_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
458 {
459         WmsBil *bil = g_new0(WmsBil, 1);
460         g_file_get_contents(path, (gchar**)&bil->data, NULL, NULL);
461         bil->width  = width;
462         bil->height = height;
463         node->data = bil;
464         g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
465 }
466 void srtm_bil_freeer(WmsCacheNode *node)
467 {
468         g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
469         g_free(((WmsBil*)node->data)->data);
470         g_free(node->data);
471 }
472
473 void srtm_pixbuf_loader(WmsCacheNode *node, const gchar *path, gint width, gint height)
474 {
475         GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
476         guchar    *pixels = gdk_pixbuf_get_pixels(pixbuf);
477         gint       stride = gdk_pixbuf_get_rowstride(pixbuf);
478
479         gint16 *data;
480         g_file_get_contents(path, (gchar**)&data, NULL, NULL);
481         for (int r = 0; r < height; r++) {
482                 for (int c = 0; c < width; c++) {
483                         gint16 value = data[r*width + c];
484                         //guchar color = (float)(MAX(value,0))/8848 * 255;
485                         guchar color = (float)value/8848 * 255;
486                         pixels[r*stride + c*3 + 0] = color;
487                         pixels[r*stride + c*3 + 1] = color;
488                         pixels[r*stride + c*3 + 2] = color;
489                 }
490         }
491         g_free(data);
492
493         node->data = pixbuf;
494         g_debug("WmsCacheNode: srtm_opengl_loader: %s -> %p", path, node->data);
495 }
496 void srtm_pixbuf_freeer(WmsCacheNode *node)
497 {
498         g_debug("WmsCacheNode: srtm_opengl_freeer: %p", node);
499         g_object_unref(node->data);
500 }
501
502 WmsInfo *wms_info_new_for_srtm(WmsLoader loader, WmsFreeer freeer)
503 {
504         loader = loader ?: srtm_bil_loader;
505         freeer = freeer ?: srtm_bil_freeer;
506         return wms_info_new(loader, freeer,
507                 "http://www.nasa.network.com/elev", "srtm30", "application/bil",
508                 "srtm", ".bil", 500, 512, 256);
509 }