]> git.street.me.uk Git - andy/viking.git/blob - src/vikmapniklayer.c
Ensure calculation of the waypoint bounds even when the layer is not currently visible.
[andy/viking.git] / src / vikmapniklayer.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (c) 2015, Rob Norris <rw_norris@hotmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "viking.h"
27 #include "vikutils.h"
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n.h>
31 #include <string.h>
32 #include <math.h>
33 #include <stdlib.h>
34 #include <ctype.h>
35
36 #include "map_ids.h"
37 #include "maputils.h"
38 #include "mapcoord.h"
39 #include "mapcache.h"
40 #include "dir.h"
41 #include "util.h"
42 #include "ui_util.h"
43 #include "preferences.h"
44 #include "icons/icons.h"
45 #include "mapnik_interface.h"
46 #include "background.h"
47
48 #include "vikmapslayer.h"
49
50 #if !GLIB_CHECK_VERSION(2,26,0)
51 typedef struct stat GStatBuf;
52 #endif
53
54 struct _VikMapnikLayerClass
55 {
56         VikLayerClass object_class;
57 };
58
59 static VikLayerParamData file_default ( void )
60 {
61         VikLayerParamData data;
62         data.s = "";
63         return data;
64 }
65
66 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
67 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
68
69 static VikLayerParamData cache_dir_default ( void )
70 {
71         VikLayerParamData data;
72         data.s = g_strconcat ( maps_layer_default_dir(), "MapnikRendering", NULL );
73         return data;
74 }
75
76 static VikLayerParamScale scales[] = {
77         { 0, 255, 5, 0 }, // Alpha
78         { 64, 1024, 8, 0 }, // Tile size
79         { 0, 1024, 12, 0 }, // Rerender timeout hours
80 };
81
82 VikLayerParam mapnik_layer_params[] = {
83   { VIK_LAYER_MAPNIK, "config-file-mml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CSS (MML) Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_CARTO), NULL,
84     N_("CartoCSS configuration file"), file_default, NULL, NULL },
85   { VIK_LAYER_MAPNIK, "config-file-xml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("XML Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_XML), NULL,
86     N_("Mapnik XML configuration file"), file_default, NULL, NULL },
87   { VIK_LAYER_MAPNIK, "alpha", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Alpha:"), VIK_LAYER_WIDGET_HSCALE, &scales[0], NULL,
88     NULL, alpha_default, NULL, NULL },
89   { VIK_LAYER_MAPNIK, "use-file-cache", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Use File Cache:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL,
90     NULL, vik_lpd_true_default, NULL, NULL },
91   { VIK_LAYER_MAPNIK, "file-cache-dir", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("File Cache Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL,
92     NULL, cache_dir_default, NULL, NULL },
93 };
94
95 enum {
96   PARAM_CONFIG_CSS = 0,
97   PARAM_CONFIG_XML,
98   PARAM_ALPHA,
99   PARAM_USE_FILE_CACHE,
100   PARAM_FILE_CACHE_DIR,
101   NUM_PARAMS };
102
103 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
104 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
105 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
106 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
107 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
108 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
109 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
110 static void mapnik_layer_free ( VikMapnikLayer *vml );
111 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
112 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
113
114 static gpointer mapnik_feature_create ( VikWindow *vw, VikViewport *vvp)
115 {
116   return vvp;
117 }
118
119 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp );
120
121 // See comment in viktrwlayer.c for advice on values used
122 // FUTURE:
123 static VikToolInterface mapnik_tools[] = {
124         // Layer Info
125         // Zoom All?
126   { { "MapnikFeatures", GTK_STOCK_INFO, N_("_Mapnik Features"), NULL, N_("Mapnik Features"), 0 },
127     (VikToolConstructorFunc) mapnik_feature_create,
128     NULL,
129     NULL,
130     NULL,
131     NULL,
132     NULL,
133     (VikToolMouseFunc) mapnik_feature_release,
134     NULL,
135     FALSE,
136     GDK_LEFT_PTR, NULL, NULL },
137 };
138
139 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
140
141 VikLayerInterface vik_mapnik_layer_interface = {
142         "Mapnik Rendering",
143         N_("Mapnik Rendering"),
144         NULL,
145         &vikmapniklayer_pixbuf, // icon
146
147         mapnik_tools,
148         sizeof(mapnik_tools) / sizeof(VikToolInterface),
149
150         mapnik_layer_params,
151         NUM_PARAMS,
152         NULL,
153         0,
154
155         VIK_MENU_ITEM_ALL,
156
157         (VikLayerFuncCreate)                  mapnik_layer_create,
158         (VikLayerFuncRealize)                 NULL,
159         (VikLayerFuncPostRead)                mapnik_layer_post_read,
160         (VikLayerFuncFree)                    mapnik_layer_free,
161
162         (VikLayerFuncProperties)              NULL,
163         (VikLayerFuncDraw)                    mapnik_layer_draw,
164         (VikLayerFuncChangeCoordMode)         NULL,
165
166         (VikLayerFuncGetTimestamp)            NULL,
167
168         (VikLayerFuncSetMenuItemsSelection)   NULL,
169         (VikLayerFuncGetMenuItemsSelection)   NULL,
170
171         (VikLayerFuncAddMenuItems)            mapnik_layer_add_menu_items,
172         (VikLayerFuncSublayerAddMenuItems)    NULL,
173
174         (VikLayerFuncSublayerRenameRequest)   NULL,
175         (VikLayerFuncSublayerToggleVisible)   NULL,
176         (VikLayerFuncSublayerTooltip)         NULL,
177         (VikLayerFuncLayerTooltip)            mapnik_layer_tooltip,
178         (VikLayerFuncLayerSelected)           NULL,
179
180         (VikLayerFuncMarshall)                mapnik_layer_marshall,
181         (VikLayerFuncUnmarshall)              mapnik_layer_unmarshall,
182
183         (VikLayerFuncSetParam)                mapnik_layer_set_param,
184         (VikLayerFuncGetParam)                mapnik_layer_get_param,
185         (VikLayerFuncChangeParam)             NULL,
186
187         (VikLayerFuncReadFileData)            NULL,
188         (VikLayerFuncWriteFileData)           NULL,
189
190         (VikLayerFuncDeleteItem)              NULL,
191         (VikLayerFuncCutItem)                 NULL,
192         (VikLayerFuncCopyItem)                NULL,
193         (VikLayerFuncPasteItem)               NULL,
194         (VikLayerFuncFreeCopiedItem)          NULL,
195         (VikLayerFuncDragDropRequest)         NULL,
196
197         (VikLayerFuncSelectClick)             NULL,
198         (VikLayerFuncSelectMove)              NULL,
199         (VikLayerFuncSelectRelease)           NULL,
200         (VikLayerFuncSelectedViewportMenu)    NULL,
201 };
202
203 struct _VikMapnikLayer {
204         VikLayer vl;
205         gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
206         gchar *filename_xml;
207         guint8 alpha;
208
209         guint tile_size_x; // Y is the same as X ATM
210         gboolean loaded;
211         MapnikInterface* mi;
212         guint rerender_timeout;
213
214         gboolean use_file_cache;
215         gchar *file_cache_dir;
216
217         VikCoord rerender_ul;
218         VikCoord rerender_br;
219         gdouble rerender_zoom;
220         GtkWidget *right_click_menu;
221 };
222
223 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
224 #define MAPNIK_PREFS_NAMESPACE "mapnik."
225
226 static VikLayerParamData plugins_default ( void )
227 {
228         VikLayerParamData data;
229 #ifdef WINDOWS
230         data.s = g_strdup ( "input" );
231 #else
232         if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
233                 data.s = g_strdup ( "/usr/lib/mapnik/input" );
234                 // Current Debian locations
235         else if ( g_file_test ( "/usr/lib/mapnik/3.0/input", G_FILE_TEST_EXISTS ) )
236                 data.s = g_strdup ( "/usr/lib/mapnik/3.0/input" );
237         else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
238                 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
239         else
240                 data.s = g_strdup ( "" );
241 #endif
242         return data;
243 }
244
245 static VikLayerParamData fonts_default ( void )
246 {
247         // Possibly should be string list to allow loading from multiple directories
248         VikLayerParamData data;
249 #ifdef WINDOWS
250         data.s = g_strdup ( "C:\\Windows\\Fonts" );
251 #elif defined __APPLE__
252         data.s = g_strdup ( "/Library/Fonts" );
253 #else
254         data.s = g_strdup ( "/usr/share/fonts" );
255 #endif
256         return data;
257 }
258
259 static VikLayerParam prefs[] = {
260         // Changing these values only applies before first mapnik layer is 'created'
261         { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"plugins_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Plugins Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), plugins_default, NULL, NULL },
262         { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"fonts_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Fonts Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), fonts_default, NULL, NULL },
263         { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Recurse Fonts Directory:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), vik_lpd_true_default, NULL, NULL },
264         { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"rerender_after", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Rerender Timeout (hours):"), VIK_LAYER_WIDGET_SPINBUTTON, &scales[2], NULL, N_("You need to restart Viking for a change to this value to be used"), NULL, NULL, NULL },
265         // Changeable any time
266         { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"carto", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CartoCSS:"), VIK_LAYER_WIDGET_FILEENTRY, NULL, NULL,  N_("The program to convert CartoCSS files into Mapnik XML"), NULL, NULL, NULL },
267 };
268
269 static time_t planet_import_time;
270
271 static GMutex *tp_mutex;
272 static GHashTable *requests = NULL;
273
274 /**
275  * vik_mapnik_layer_init:
276  *
277  * Just initialize preferences
278  */
279 void vik_mapnik_layer_init (void)
280 {
281         a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
282
283         guint i = 0;
284         VikLayerParamData tmp = plugins_default();
285         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
286
287         tmp = fonts_default();
288         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
289
290         tmp.b = TRUE;
291         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
292
293         tmp.u = 168; // One week
294         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
295
296         tmp.s = "carto";
297         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
298 }
299
300 /**
301  * vik_mapnik_layer_post_init:
302  *
303  * Initialize data structures - now that reading preferences is OK to perform
304  */
305 void vik_mapnik_layer_post_init (void)
306 {
307         tp_mutex = vik_mutex_new();
308
309         // Just storing keys only
310         requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
311
312         guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
313         GDateTime *now = g_date_time_new_now_local ();
314         GDateTime *then = g_date_time_add_hours (now, -hours);
315         planet_import_time = g_date_time_to_unix (then);
316         g_date_time_unref ( now );
317         g_date_time_unref ( then );
318
319         GStatBuf gsb;
320         // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
321         gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
322         if ( g_stat ( import_time_file, &gsb ) == 0 ) {
323                 // Only update if newer
324                 if ( planet_import_time > gsb.st_mtime )
325                         planet_import_time = gsb.st_mtime;
326         }
327         g_free ( import_time_file );
328 }
329
330 void vik_mapnik_layer_uninit ()
331 {
332         vik_mutex_free (tp_mutex);
333 }
334
335 // NB Only performed once per program run
336 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
337 {
338         VikLayerParamData *pd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory");
339         VikLayerParamData *fd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory");
340         VikLayerParamData *rfd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory");
341
342         if ( pd && fd && rfd )
343                 mapnik_interface_initialize ( pd->s, fd->s, rfd->b );
344         else
345                 g_critical ( "Unable to initialize mapnik interface from preferences" );
346 }
347
348 GType vik_mapnik_layer_get_type ()
349 {
350         static GType vml_type = 0;
351
352         if (!vml_type) {
353                 static const GTypeInfo vml_info = {
354                         sizeof (VikMapnikLayerClass),
355                         NULL, /* base_init */
356                         NULL, /* base_finalize */
357                         (GClassInitFunc) mapnik_layer_class_init, /* class init */
358                         NULL, /* class_finalize */
359                         NULL, /* class_data */
360                         sizeof (VikMapnikLayer),
361                         0,
362                         NULL /* instance init */
363                 };
364                 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
365         }
366
367         return vml_type;
368 }
369
370 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
371 {
372         return vml->filename_xml;
373 }
374
375 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
376 {
377         if ( vml->filename_xml )
378                 g_free (vml->filename_xml);
379         // Mapnik doesn't seem to cope with relative filenames
380         if ( g_strcmp0 (name, "" ) )
381                 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
382         else
383                 vml->filename_xml = g_strdup (name);
384 }
385
386 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
387 {
388         if ( vml->filename_css )
389                 g_free (vml->filename_css);
390         vml->filename_css = g_strdup (name);
391 }
392
393 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
394 {
395         if ( vml->file_cache_dir )
396                 g_free (vml->file_cache_dir);
397         vml->file_cache_dir = g_strdup (name);
398 }
399
400 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
401 {
402         vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
403 }
404
405 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
406 {
407         VikMapnikLayer *rv = mapnik_layer_new ( vvp );
408         vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
409         return rv;
410 }
411
412 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
413 {
414         switch ( id ) {
415                 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
416                 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
417                 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
418                 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
419                 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
420                 default: break;
421         }
422         return TRUE;
423 }
424
425 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
426 {
427         VikLayerParamData data;
428         switch ( id ) {
429                 case PARAM_CONFIG_CSS: {
430                         data.s = vml->filename_css;
431                         gboolean set = FALSE;
432                         if ( is_file_operation ) {
433                                 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
434                                         gchar *cwd = g_get_current_dir();
435                                         if ( cwd ) {
436                                                 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
437                                                 if ( !data.s ) data.s = "";
438                                                 set = TRUE;
439                                         }
440                                 }
441                         }
442                         if ( !set )
443                                 data.s = vml->filename_css ? vml->filename_css : "";
444                         break;
445                 }
446                 case PARAM_CONFIG_XML: {
447                         data.s = vml->filename_xml;
448                         gboolean set = FALSE;
449                         if ( is_file_operation ) {
450                                 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
451                                         gchar *cwd = g_get_current_dir();
452                                         if ( cwd ) {
453                                                 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
454                                                 if ( !data.s ) data.s = "";
455                                                 set = TRUE;
456                                         }
457                                 }
458                         }
459                         if ( !set )
460                                 data.s = vml->filename_xml ? vml->filename_xml : "";
461                         break;
462                 }
463                 case PARAM_ALPHA: data.u = vml->alpha; break;
464                 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
465                 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
466                 default: break;
467         }
468         return data;
469 }
470
471 /**
472  *
473  */
474 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
475 {
476         VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
477         vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
478         vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
479         vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
480         vml->loaded = FALSE;
481         vml->mi = mapnik_interface_new();
482         return vml;
483 }
484
485 /**
486  * Run carto command
487  * ATM don't have any version issues AFAIK
488  * Tested with carto 0.14.0
489  */
490 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
491 {
492         gchar *mystdout = NULL;
493         gchar *mystderr = NULL;
494         GError *error = NULL;
495
496         VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
497         gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
498
499         gboolean answer = TRUE;
500         //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
501         //GPid pid;
502         //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
503         // cf code in babel.c to handle stdout
504
505         // NB Running carto may take several seconds
506         //  especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
507         VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
508         if ( vw ) {
509                 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
510                 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
511                 vik_window_set_busy_cursor ( vw );
512         }
513
514         gint64 tt1 = 0;
515         gint64 tt2 = 0;
516         // You won't get a sensible timing measurement if running too old a GLIB
517 #if GLIB_CHECK_VERSION (2, 28, 0)
518         tt1 = g_get_real_time ();
519 #endif
520
521         if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
522 #if GLIB_CHECK_VERSION (2, 28, 0)
523                 tt2 = g_get_real_time ();
524 #endif
525                 if ( mystderr )
526                         if ( strlen(mystderr) > 1 ) {
527                                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
528                                 answer = FALSE;
529                         }
530                 if ( mystdout ) {
531                         // NB This will overwrite the specified XML file
532                         if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
533                                 // XML Not specified so try to create based on CSS file name
534                                 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
535                                 if ( error )
536                                         g_critical ("%s: %s", __FUNCTION__, error->message );
537                                 if ( vml->filename_xml )
538                                         g_free (vml->filename_xml);
539                                 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
540                                 if ( error )
541                                         g_warning ("%s: %s", __FUNCTION__, error->message );
542                                 // Prevent overwriting self
543                                 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
544                                         vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
545                                 }
546                         }
547                         if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error)  ) {
548                                 g_warning ("%s: %s", __FUNCTION__, error->message );
549                                 g_error_free (error);
550                         }
551                 }
552                 g_free ( mystdout );
553                 g_free ( mystderr );
554         }
555         else {
556                 g_warning ("%s: %s", __FUNCTION__, error->message );
557                 g_error_free (error);
558         }
559         g_free ( command );
560
561         if ( vw ) {
562                 gchar *msg = g_strdup_printf ( "%s %s %.1f %s",  vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
563                 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
564                 g_free ( msg );
565                 vik_window_clear_busy_cursor ( vw );
566         }
567         return answer;
568 }
569
570 /**
571  *
572  */
573 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
574 {
575         VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
576
577         // Determine if carto needs to be run
578         gboolean do_carto = FALSE;
579         if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
580                 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
581                         // Compare timestamps
582                         GStatBuf gsb1;
583                         if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
584                                 GStatBuf gsb2;
585                                 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
586                                         // Is CSS file newer than the XML file
587                                         if ( gsb2.st_mtime > gsb1.st_mtime )
588                                                 do_carto = TRUE;
589                                         else
590                                                 g_debug ( "No need to run carto" );
591                                 }
592                         }
593                         else {
594                                 // XML file doesn't exist
595                                 do_carto = TRUE;
596                         }
597                 }
598                 else {
599                         // No XML specified thus need to generate
600                         do_carto = TRUE;
601                 }
602         }
603
604         if ( do_carto )
605                 // Don't load the XML config if carto load fails
606                 if ( !carto_load ( vml, vvp ) )
607                         return;
608
609         gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
610         if ( ans ) {
611                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
612                                            _("Mapnik error loading configuration file:\n%s"),
613                                            ans );
614                 g_free ( ans );
615         }
616         else {
617                 vml->loaded = TRUE;
618                 if ( !from_file )
619                         ui_add_recent_file ( vml->filename_xml );
620         }
621 }
622
623 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
624
625 // Free returned string after use
626 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
627 {
628         return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
629 }
630
631 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
632 {
633         if ( vml->use_file_cache ) {
634                 if ( vml->file_cache_dir ) {
635                         GError *error = NULL;
636                         gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
637
638                         gchar *dir = g_path_get_dirname ( filename );
639                         if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
640                                 if ( g_mkdir_with_parents ( dir , 0777 ) != 0 )
641                                         g_warning ("%s: Failed to mkdir %s", __FUNCTION__, dir );
642                         g_free ( dir );
643
644                         if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
645                                 g_warning ("%s: %s", __FUNCTION__, error->message );
646                                 g_error_free (error);
647                         }
648                         g_free (filename);
649                 }
650         }
651 }
652
653 typedef struct
654 {
655         VikMapnikLayer *vml;
656         VikCoord *ul;
657         VikCoord *br;
658         MapCoord *ulmc;
659         const gchar* request;
660 } RenderInfo;
661
662 /**
663  * render:
664  *
665  * Common render function which can run in separate thread
666  */
667 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
668 {
669         gint64 tt1 = g_get_real_time ();
670         GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
671         gint64 tt2 = g_get_real_time ();
672         gdouble tt = (gdouble)(tt2-tt1)/1000000;
673         g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
674         if ( !pixbuf ) {
675                 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
676                 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
677         }
678         possibly_save_pixbuf ( vml, pixbuf, ulm );
679
680         // NB Mapnik can apply alpha, but use our own function for now
681         if ( vml->alpha < 255 )
682                 pixbuf = ui_pixbuf_scale_alpha ( pixbuf, vml->alpha );
683         a_mapcache_add ( pixbuf, (mapcache_extra_t){ tt }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
684         g_object_unref(pixbuf);
685 }
686
687 static void render_info_free ( RenderInfo *data )
688 {
689         g_free ( data->ul );
690         g_free ( data->br );
691         g_free ( data->ulmc );
692         // NB No need to free the request/key - as this is freed by the hash table destructor
693         g_free ( data );
694 }
695
696 static void background ( RenderInfo *data, gpointer threaddata )
697 {
698         int res = a_background_thread_progress ( threaddata, 0 );
699         if (res == 0) {
700                 render ( data->vml, data->ul, data->br, data->ulmc );
701         }
702
703         g_mutex_lock(tp_mutex);
704         g_hash_table_remove (requests, data->request);
705         g_mutex_unlock(tp_mutex);
706
707         if (res == 0)
708                 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
709 }
710
711 static void render_cancel_cleanup (RenderInfo *data)
712 {
713         // Anything?
714 }
715
716 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
717
718 /**
719  * Thread
720  */
721 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
722 {
723         // Create request
724         guint nn = name ? g_str_hash ( name ) : 0;
725         gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
726
727         g_mutex_lock(tp_mutex);
728
729         if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
730                 g_free ( request );
731                 g_mutex_unlock (tp_mutex);
732                 return;
733         }
734
735         RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
736         ri->vml = vml;
737         ri->ul = g_malloc ( sizeof(VikCoord) );
738         ri->br = g_malloc ( sizeof(VikCoord) );
739         ri->ulmc = g_malloc ( sizeof(MapCoord) );
740         memcpy(ri->ul, ul, sizeof(VikCoord));
741         memcpy(ri->br, br, sizeof(VikCoord));
742         memcpy(ri->ulmc, mul, sizeof(MapCoord));
743         ri->request = request;
744
745         g_hash_table_insert ( requests, request, NULL );
746
747         g_mutex_unlock (tp_mutex);
748
749         gchar *basename = g_path_get_basename (name);
750         gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
751         g_free ( basename );
752         a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
753                               VIK_GTK_WINDOW_FROM_LAYER(vml),
754                               description,
755                               (vik_thr_func) background,
756                               ri,
757                               (vik_thr_free_func) render_info_free,
758                               (vik_thr_free_func) render_cancel_cleanup,
759                               1 );
760         g_free ( description );
761 }
762
763 /**
764  * load_pixbuf:
765  *
766  * If function returns GdkPixbuf properly, reference counter to this
767  * buffer has to be decreased, when buffer is no longer needed.
768  */
769 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
770 {
771         *rerender = FALSE;
772         GdkPixbuf *pixbuf = NULL;
773         gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
774
775         GStatBuf gsb;
776         if ( g_stat ( filename, &gsb ) == 0 ) {
777                 // Get from disk
778                 GError *error = NULL;
779                 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
780                 if ( error ) {
781                         g_warning ("%s: %s", __FUNCTION__, error->message );
782                         g_error_free ( error );
783                 }
784                 else {
785                         if ( vml->alpha < 255 )
786                                 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
787                         a_mapcache_add ( pixbuf, (mapcache_extra_t) { -42.0 }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
788                 }
789                 // If file is too old mark for rerendering
790                 if ( planet_import_time < gsb.st_mtime ) {
791                         *rerender = TRUE;
792                 }
793         }
794         g_free ( filename );
795
796         return pixbuf;
797 }
798
799 /**
800  * Caller has to decrease reference counter of returned
801  * GdkPixbuf, when buffer is no longer needed.
802  */
803 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
804 {
805         VikCoord ul; VikCoord br;
806         GdkPixbuf *pixbuf = NULL;
807
808         map_utils_iTMS_to_vikcoord (ulm, &ul);
809         map_utils_iTMS_to_vikcoord (brm, &br);
810
811         pixbuf = a_mapcache_get ( ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
812
813         if ( ! pixbuf ) {
814                 gboolean rerender = FALSE;
815                 if ( vml->use_file_cache && vml->file_cache_dir )
816                         pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
817                 if ( ! pixbuf || rerender ) {
818                         if ( TRUE )
819                                 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
820                         else {
821                                 // Run in the foreground
822                                 render ( vml, &ul, &br, ulm );
823                                 vik_layer_emit_update ( VIK_LAYER(vml) );
824                         }
825                 }
826         }
827
828         return pixbuf;
829 }
830
831 /**
832  *
833  */
834 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
835 {
836         if ( !vml->loaded )
837                 return;
838
839         if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
840                 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
841                                             VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
842                 return;
843         }
844
845         if ( vml->mi ) {
846                 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
847                 if ( copyright ) {
848                         vik_viewport_add_copyright ( vvp, copyright );
849                 }
850         }
851
852         VikCoord ul, br;
853         ul.mode = VIK_COORD_LATLON;
854         br.mode = VIK_COORD_LATLON;
855         vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
856         vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
857
858         gdouble xzoom = vik_viewport_get_xmpp ( vvp );
859         gdouble yzoom = vik_viewport_get_ympp ( vvp );
860
861         MapCoord ulm, brm;
862
863         if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
864              map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
865                 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
866                 GdkPixbuf *pixbuf;
867                 VikCoord coord;
868                 gint xx, yy;
869
870                 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
871                 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
872
873                 // Split rendering into a grid for the current viewport
874                 //  thus each individual 'tile' can then be stored in the map cache
875                 for (gint x = xmin; x <= xmax; x++ ) {
876                         for (gint y = ymin; y <= ymax; y++ ) {
877                                 ulm.x = x;
878                                 ulm.y = y;
879                                 brm.x = x+1;
880                                 brm.y = y+1;
881
882                                 pixbuf = get_pixbuf ( vml, &ulm, &brm );
883
884                                 if ( pixbuf ) {
885                                         map_utils_iTMS_to_vikcoord ( &ulm, &coord );
886                                         vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
887                                         vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
888                                         g_object_unref(pixbuf);
889                                 }
890                         }
891                 }
892
893                 // Done after so drawn on top
894                 // Just a handy guide to tile blocks.
895                 if ( vik_debug && vik_verbose ) {
896                         GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
897                         gint width = vik_viewport_get_width(vvp);
898                         gint height = vik_viewport_get_height(vvp);
899                         gint xx, yy;
900                         ulm.x = xmin; ulm.y = ymin;
901                         map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
902                         vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
903                         xx = xx - (vml->tile_size_x/2);
904                         yy = yy - (vml->tile_size_x/2); // Yes use X ATM
905                         for (gint x = xmin; x <= xmax; x++ ) {
906                                 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
907                                 xx += vml->tile_size_x;
908                         }
909                         for (gint y = ymin; y <= ymax; y++ ) {
910                                 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
911                                 yy += vml->tile_size_x; // Yes use X ATM
912                         }
913                 }
914         }
915 }
916
917 /**
918  *
919  */
920 static void mapnik_layer_free ( VikMapnikLayer *vml )
921 {
922         mapnik_interface_free ( vml->mi );
923         if ( vml->filename_css )
924                 g_free ( vml->filename_css );
925         if ( vml->filename_xml )
926                 g_free ( vml->filename_xml );
927 }
928
929 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
930 {
931         return mapnik_layer_new ( vp );
932 }
933
934 typedef enum {
935         MA_VML = 0,
936         MA_VVP,
937         MA_LAST
938 } menu_array_index;
939
940 typedef gpointer menu_array_values[MA_LAST];
941
942 /**
943  *
944  */
945 static void mapnik_layer_flush_memory ( menu_array_values values )
946 {
947         a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
948 }
949
950 /**
951  *
952  */
953 static void mapnik_layer_reload ( menu_array_values values )
954 {
955         VikMapnikLayer *vml = values[MA_VML];
956         VikViewport *vvp = values[MA_VVP];
957         mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
958         mapnik_layer_draw ( vml, vvp );
959 }
960
961 /**
962  * Force carto run
963  *
964  * Most carto projects will consist of many files
965  * ATM don't have a way of detecting when any of the included files have changed
966  * Thus allow a manual method to force re-running carto
967  */
968 static void mapnik_layer_carto ( menu_array_values values )
969 {
970         VikMapnikLayer *vml = values[MA_VML];
971         VikViewport *vvp = values[MA_VVP];
972
973         // Don't load the XML config if carto load fails
974         if ( !carto_load ( vml, vvp ) )
975                 return;
976
977         gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
978         if ( ans ) {
979                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
980                                            _("Mapnik error loading configuration file:\n%s"),
981                                            ans );
982                 g_free ( ans );
983         }
984         else
985                 mapnik_layer_draw ( vml, vvp );
986 }
987
988 /**
989  * Show Mapnik configuration parameters
990  */
991 static void mapnik_layer_information ( menu_array_values values )
992 {
993         VikMapnikLayer *vml = values[MA_VML];
994         if ( !vml->mi )
995                 return;
996         GArray *array = mapnik_interface_get_parameters( vml->mi );
997         if ( array->len ) {
998                 a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
999                 // Free the copied strings
1000                 for ( int i = 0; i < array->len; i++ )
1001                         g_free ( g_array_index(array,gchar*,i) );
1002         }
1003         g_array_free ( array, FALSE );
1004 }
1005
1006 /**
1007  *
1008  */
1009 static void mapnik_layer_about ( menu_array_values values )
1010 {
1011         VikMapnikLayer *vml = values[MA_VML];
1012         gchar *msg = mapnik_interface_about();
1013         a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml),  msg );
1014         g_free ( msg );
1015 }
1016
1017 /**
1018  *
1019  */
1020 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
1021 {
1022         static menu_array_values values;
1023         values[MA_VML] = vml;
1024         values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1025
1026         GtkWidget *item = gtk_menu_item_new();
1027         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1028         gtk_widget_show ( item );
1029
1030         // Typical users shouldn't need to use this functionality - so debug only ATM
1031         if ( vik_debug ) {
1032                 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1033                 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1034                 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1035                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1036                 gtk_widget_show ( item );
1037         }
1038
1039         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1040         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1041         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1042         gtk_widget_show ( item );
1043
1044         if ( g_strcmp0 ("", vml->filename_css) ) {
1045                 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1046                 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1047                 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1048                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1049                 gtk_widget_show ( item );
1050         }
1051
1052         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1053         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1054         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1055         gtk_widget_show ( item );
1056
1057         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1058         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1059         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1060         gtk_widget_show ( item );
1061 }
1062
1063 /**
1064  * Rerender a specific tile
1065  */
1066 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1067 {
1068         MapCoord ulm;
1069         // Requested position to map coord
1070         map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1071         // Reconvert back - thus getting the coordinate at the tile *ul corner*
1072         map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1073         // Bottom right bound is simply +1 in TMS coords
1074         MapCoord brm = ulm;
1075         brm.x = brm.x+1;
1076         brm.y = brm.y+1;
1077         map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1078         thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1079 }
1080
1081 /**
1082  * Info
1083  */
1084 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1085 {
1086         MapCoord ulm;
1087         // Requested position to map coord
1088         map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1089
1090         mapcache_extra_t extra = a_mapcache_get_extra ( ulm.x, ulm.y, ulm.z, MAP_ID_MAPNIK_RENDER, ulm.scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
1091
1092         gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1093         gchar *filemsg = NULL;
1094         gchar *timemsg = NULL;
1095
1096         if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1097                 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1098                 // Get some timestamp information of the tile
1099                 struct stat stat_buf;
1100                 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1101                         gchar time_buf[64];
1102                         strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1103                         timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1104                 }
1105                 else {
1106                         timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1107                 }
1108         }
1109         else {
1110                 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1111                 timemsg = g_strdup("");
1112         }
1113
1114         GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1115         g_array_append_val ( array, filemsg );
1116         g_array_append_val ( array, timemsg );
1117
1118         gchar *rendmsg = NULL;
1119         // Show the info
1120         if ( extra.duration > 0.0 ) {
1121                 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1122                 g_array_append_val ( array, rendmsg );
1123         }
1124
1125         a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1126         g_array_free ( array, FALSE );
1127
1128         g_free ( rendmsg );
1129         g_free ( timemsg );
1130         g_free ( filemsg );
1131         g_free ( filename );
1132 }
1133
1134 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1135 {
1136         if ( !vml )
1137                 return FALSE;
1138         if ( event->button == 3 ) {
1139                 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1140                 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1141
1142                 if ( ! vml->right_click_menu ) {
1143                         GtkWidget *item;
1144                         vml->right_click_menu = gtk_menu_new ();
1145
1146                         item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1147                         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1148                         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1149                         gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1150
1151                         item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1152                         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1153                         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1154                         gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1155                 }
1156
1157                 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1158                 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );
1159         }
1160
1161         return FALSE;
1162 }