]> git.street.me.uk Git - andy/viking.git/blob - src/vikmapniklayer.c
[QA] Fix clang warning: format string is not a string literal
[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         else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
235                 // Current Debian location
236                 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
237         else
238                 data.s = g_strdup ( "" );
239 #endif
240         return data;
241 }
242
243 static VikLayerParamData fonts_default ( void )
244 {
245         // Possibly should be string list to allow loading from multiple directories
246         VikLayerParamData data;
247 #ifdef WINDOWS
248         data.s = g_strdup ( "C:\\Windows\\Fonts" );
249 #elif defined __APPLE__
250         data.s = g_strdup ( "/Library/Fonts" );
251 #else
252         data.s = g_strdup ( "/usr/share/fonts" );
253 #endif
254         return data;
255 }
256
257 static VikLayerParam prefs[] = {
258         // Changing these values only applies before first mapnik layer is 'created'
259         { 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 },
260         { 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 },
261         { 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 },
262         { 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 },
263         // Changeable any time
264         { 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 },
265 };
266
267 static time_t planet_import_time;
268
269 static GMutex *tp_mutex;
270 static GHashTable *requests = NULL;
271
272 /**
273  * vik_mapnik_layer_init:
274  *
275  * Mostly to initialize preferences
276  */
277 void vik_mapnik_layer_init (void)
278 {
279         a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
280
281         guint i = 0;
282         VikLayerParamData tmp = plugins_default();
283         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
284
285         tmp = fonts_default();
286         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
287
288         tmp.b = TRUE;
289         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
290
291         tmp.u = 168; // One week
292         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
293
294         tmp.s = "carto";
295         a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
296
297         tp_mutex = vik_mutex_new();
298
299         // Just storing keys only
300         requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
301
302         guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
303         GDateTime *now = g_date_time_new_now_local ();
304         GDateTime *then = g_date_time_add_hours (now, -hours);
305         planet_import_time = g_date_time_to_unix (then);
306         g_date_time_unref ( now );
307         g_date_time_unref ( then );
308
309         GStatBuf gsb;
310         // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
311         gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
312         if ( g_stat ( import_time_file, &gsb ) == 0 ) {
313                 // Only update if newer
314                 if ( planet_import_time > gsb.st_mtime )
315                         planet_import_time = gsb.st_mtime;
316         }
317         g_free ( import_time_file );
318 }
319
320 void vik_mapnik_layer_uninit ()
321 {
322         vik_mutex_free (tp_mutex);
323 }
324
325 // NB Only performed once per program run
326 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
327 {
328         mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
329                                       a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
330                                       a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
331 }
332
333 GType vik_mapnik_layer_get_type ()
334 {
335         static GType vml_type = 0;
336
337         if (!vml_type) {
338                 static const GTypeInfo vml_info = {
339                         sizeof (VikMapnikLayerClass),
340                         NULL, /* base_init */
341                         NULL, /* base_finalize */
342                         (GClassInitFunc) mapnik_layer_class_init, /* class init */
343                         NULL, /* class_finalize */
344                         NULL, /* class_data */
345                         sizeof (VikMapnikLayer),
346                         0,
347                         NULL /* instance init */
348                 };
349                 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
350         }
351
352         return vml_type;
353 }
354
355 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
356 {
357         return vml->filename_xml;
358 }
359
360 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
361 {
362         if ( vml->filename_xml )
363                 g_free (vml->filename_xml);
364         // Mapnik doesn't seem to cope with relative filenames
365         if ( g_strcmp0 (name, "" ) )
366                 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
367         else
368                 vml->filename_xml = g_strdup (name);
369 }
370
371 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
372 {
373         if ( vml->filename_css )
374                 g_free (vml->filename_css);
375         vml->filename_css = g_strdup (name);
376 }
377
378 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
379 {
380         if ( vml->file_cache_dir )
381                 g_free (vml->file_cache_dir);
382         vml->file_cache_dir = g_strdup (name);
383 }
384
385 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
386 {
387         vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
388 }
389
390 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
391 {
392         VikMapnikLayer *rv = mapnik_layer_new ( vvp );
393         vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
394         return rv;
395 }
396
397 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
398 {
399         switch ( id ) {
400                 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
401                 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
402                 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
403                 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
404                 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
405                 default: break;
406         }
407         return TRUE;
408 }
409
410 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
411 {
412         VikLayerParamData data;
413         switch ( id ) {
414                 case PARAM_CONFIG_CSS: {
415                         data.s = vml->filename_css;
416                         gboolean set = FALSE;
417                         if ( is_file_operation ) {
418                                 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
419                                         gchar *cwd = g_get_current_dir();
420                                         if ( cwd ) {
421                                                 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
422                                                 if ( !data.s ) data.s = "";
423                                                 set = TRUE;
424                                         }
425                                 }
426                         }
427                         if ( !set )
428                                 data.s = vml->filename_css ? vml->filename_css : "";
429                         break;
430                 }
431                 case PARAM_CONFIG_XML: {
432                         data.s = vml->filename_xml;
433                         gboolean set = FALSE;
434                         if ( is_file_operation ) {
435                                 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
436                                         gchar *cwd = g_get_current_dir();
437                                         if ( cwd ) {
438                                                 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
439                                                 if ( !data.s ) data.s = "";
440                                                 set = TRUE;
441                                         }
442                                 }
443                         }
444                         if ( !set )
445                                 data.s = vml->filename_xml ? vml->filename_xml : "";
446                         break;
447                 }
448                 case PARAM_ALPHA: data.u = vml->alpha; break;
449                 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
450                 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
451                 default: break;
452         }
453         return data;
454 }
455
456 /**
457  *
458  */
459 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
460 {
461         VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
462         vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
463         vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
464         vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
465         vml->loaded = FALSE;
466         vml->mi = mapnik_interface_new();
467         return vml;
468 }
469
470 /**
471  * Run carto command
472  * ATM don't have any version issues AFAIK
473  * Tested with carto 0.14.0
474  */
475 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
476 {
477         gchar *mystdout = NULL;
478         gchar *mystderr = NULL;
479         GError *error = NULL;
480
481         VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
482         gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
483
484         gboolean answer = TRUE;
485         //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
486         //GPid pid;
487         //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
488         // cf code in babel.c to handle stdout
489
490         // NB Running carto may take several seconds
491         //  especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
492         VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
493         if ( vw ) {
494                 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
495                 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
496                 vik_window_set_busy_cursor ( vw );
497         }
498
499         gint64 tt1 = 0;
500         gint64 tt2 = 0;
501         // You won't get a sensible timing measurement if running too old a GLIB
502 #if GLIB_CHECK_VERSION (2, 28, 0)
503         tt1 = g_get_real_time ();
504 #endif
505
506         if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
507 #if GLIB_CHECK_VERSION (2, 28, 0)
508                 tt2 = g_get_real_time ();
509 #endif
510                 if ( mystderr )
511                         if ( strlen(mystderr) > 1 ) {
512                                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
513                                 answer = FALSE;
514                         }
515                 if ( mystdout ) {
516                         // NB This will overwrite the specified XML file
517                         if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
518                                 // XML Not specified so try to create based on CSS file name
519                                 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
520                                 if ( error )
521                                         g_critical ("%s: %s", __FUNCTION__, error->message );
522                                 if ( vml->filename_xml )
523                                         g_free (vml->filename_xml);
524                                 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
525                                 if ( error )
526                                         g_warning ("%s: %s", __FUNCTION__, error->message );
527                                 // Prevent overwriting self
528                                 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
529                                         vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
530                                 }
531                         }
532                         if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error)  ) {
533                                 g_warning ("%s: %s", __FUNCTION__, error->message );
534                                 g_error_free (error);
535                         }
536                 }
537                 g_free ( mystdout );
538                 g_free ( mystderr );
539         }
540         else {
541                 g_warning ("%s: %s", __FUNCTION__, error->message );
542                 g_error_free (error);
543         }
544         g_free ( command );
545
546         if ( vw ) {
547                 gchar *msg = g_strdup_printf ( "%s %s %.1f %s",  vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
548                 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
549                 g_free ( msg );
550                 vik_window_clear_busy_cursor ( vw );
551         }
552         return answer;
553 }
554
555 /**
556  *
557  */
558 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
559 {
560         VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
561
562         // Determine if carto needs to be run
563         gboolean do_carto = FALSE;
564         if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
565                 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
566                         // Compare timestamps
567                         GStatBuf gsb1;
568                         if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
569                                 GStatBuf gsb2;
570                                 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
571                                         // Is CSS file newer than the XML file
572                                         if ( gsb2.st_mtime > gsb1.st_mtime )
573                                                 do_carto = TRUE;
574                                         else
575                                                 g_debug ( "No need to run carto" );
576                                 }
577                         }
578                         else {
579                                 // XML file doesn't exist
580                                 do_carto = TRUE;
581                         }
582                 }
583                 else {
584                         // No XML specified thus need to generate
585                         do_carto = TRUE;
586                 }
587         }
588
589         if ( do_carto )
590                 // Don't load the XML config if carto load fails
591                 if ( !carto_load ( vml, vvp ) )
592                         return;
593
594         gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
595         if ( ans ) {
596                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
597                                            _("Mapnik error loading configuration file:\n%s"),
598                                            ans );
599                 g_free ( ans );
600         }
601         else {
602                 vml->loaded = TRUE;
603                 if ( !from_file )
604                         ui_add_recent_file ( vml->filename_xml );
605         }
606 }
607
608 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
609
610 // Free returned string after use
611 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
612 {
613         return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
614 }
615
616 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
617 {
618         if ( vml->use_file_cache ) {
619                 if ( vml->file_cache_dir ) {
620                         GError *error = NULL;
621                         gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
622
623                         gchar *dir = g_path_get_dirname ( filename );
624                         if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
625                                 g_mkdir_with_parents ( dir , 0777 );
626                         g_free ( dir );
627
628                         if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
629                                 g_warning ("%s: %s", __FUNCTION__, error->message );
630                                 g_error_free (error);
631                         }
632                         g_free (filename);
633                 }
634         }
635 }
636
637 typedef struct
638 {
639         VikMapnikLayer *vml;
640         VikCoord *ul;
641         VikCoord *br;
642         MapCoord *ulmc;
643         const gchar* request;
644 } RenderInfo;
645
646 /**
647  * render:
648  *
649  * Common render function which can run in separate thread
650  */
651 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
652 {
653         gint64 tt1 = g_get_real_time ();
654         GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
655         gint64 tt2 = g_get_real_time ();
656         gdouble tt = (gdouble)(tt2-tt1)/1000000;
657         g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
658         if ( !pixbuf ) {
659                 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
660                 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
661         }
662         possibly_save_pixbuf ( vml, pixbuf, ulm );
663
664         // NB Mapnik can apply alpha, but use our own function for now
665         if ( vml->alpha < 255 )
666                 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
667         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 );
668 }
669
670 static void render_info_free ( RenderInfo *data )
671 {
672         g_free ( data->ul );
673         g_free ( data->br );
674         g_free ( data->ulmc );
675         // NB No need to free the request/key - as this is freed by the hash table destructor
676         g_free ( data );
677 }
678
679 static void background ( RenderInfo *data, gpointer threaddata )
680 {
681         int res = a_background_thread_progress ( threaddata, 0 );
682         if (res == 0) {
683                 render ( data->vml, data->ul, data->br, data->ulmc );
684         }
685
686         g_mutex_lock(tp_mutex);
687         g_hash_table_remove (requests, data->request);
688         g_mutex_unlock(tp_mutex);
689
690         if (res == 0)
691                 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
692 }
693
694 static void render_cancel_cleanup (RenderInfo *data)
695 {
696         // Anything?
697 }
698
699 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
700
701 /**
702  * Thread
703  */
704 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
705 {
706         // Create request
707         guint nn = name ? g_str_hash ( name ) : 0;
708         gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
709
710         g_mutex_lock(tp_mutex);
711
712         if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
713                 g_free ( request );
714                 g_mutex_unlock (tp_mutex);
715                 return;
716         }
717
718         RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
719         ri->vml = vml;
720         ri->ul = g_malloc ( sizeof(VikCoord) );
721         ri->br = g_malloc ( sizeof(VikCoord) );
722         ri->ulmc = g_malloc ( sizeof(MapCoord) );
723         memcpy(ri->ul, ul, sizeof(VikCoord));
724         memcpy(ri->br, br, sizeof(VikCoord));
725         memcpy(ri->ulmc, mul, sizeof(MapCoord));
726         ri->request = request;
727
728         g_hash_table_insert ( requests, request, NULL );
729
730         g_mutex_unlock (tp_mutex);
731
732         gchar *basename = g_path_get_basename (name);
733         gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
734         g_free ( basename );
735         a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
736                               VIK_GTK_WINDOW_FROM_LAYER(vml),
737                               description,
738                               (vik_thr_func) background,
739                               ri,
740                               (vik_thr_free_func) render_info_free,
741                               (vik_thr_free_func) render_cancel_cleanup,
742                               1 );
743         g_free ( description );
744 }
745
746 /**
747  * load_pixbuf:
748  */
749 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
750 {
751         *rerender = FALSE;
752         GdkPixbuf *pixbuf = NULL;
753         gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
754
755         GStatBuf gsb;
756         if ( g_stat ( filename, &gsb ) == 0 ) {
757                 // Get from disk
758                 GError *error = NULL;
759                 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
760                 if ( error ) {
761                         g_warning ("%s: %s", __FUNCTION__, error->message );
762                         g_error_free ( error );
763                 }
764                 else {
765                         if ( vml->alpha < 255 )
766                                 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
767                         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 );
768                 }
769                 // If file is too old mark for rerendering
770                 if ( planet_import_time < gsb.st_mtime ) {
771                         *rerender = TRUE;
772                 }
773         }
774         g_free ( filename );
775
776         return pixbuf;
777 }
778
779 /**
780  *
781  */
782 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
783 {
784         VikCoord ul; VikCoord br;
785         GdkPixbuf *pixbuf = NULL;
786
787         map_utils_iTMS_to_vikcoord (ulm, &ul);
788         map_utils_iTMS_to_vikcoord (brm, &br);
789
790         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 );
791
792         if ( ! pixbuf ) {
793                 gboolean rerender = FALSE;
794                 if ( vml->use_file_cache && vml->file_cache_dir )
795                         pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
796                 if ( ! pixbuf || rerender ) {
797                         if ( TRUE )
798                                 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
799                         else {
800                                 // Run in the foreground
801                                 render ( vml, &ul, &br, ulm );
802                                 vik_layer_emit_update ( VIK_LAYER(vml) );
803                         }
804                 }
805         }
806
807         return pixbuf;
808 }
809
810 /**
811  *
812  */
813 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
814 {
815         if ( !vml->loaded )
816                 return;
817
818         if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
819                 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
820                                             VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
821                 return;
822         }
823
824         if ( vml->mi ) {
825                 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
826                 if ( copyright ) {
827                         vik_viewport_add_copyright ( vvp, copyright );
828                 }
829         }
830
831         VikCoord ul, br;
832         ul.mode = VIK_COORD_LATLON;
833         br.mode = VIK_COORD_LATLON;
834         vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
835         vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
836
837         gdouble xzoom = vik_viewport_get_xmpp ( vvp );
838         gdouble yzoom = vik_viewport_get_ympp ( vvp );
839
840         MapCoord ulm, brm;
841
842         if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
843              map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
844                 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
845                 GdkPixbuf *pixbuf;
846                 VikCoord coord;
847                 gint xx, yy;
848
849                 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
850                 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
851
852                 // Split rendering into a grid for the current viewport
853                 //  thus each individual 'tile' can then be stored in the map cache
854                 for (gint x = xmin; x <= xmax; x++ ) {
855                         for (gint y = ymin; y <= ymax; y++ ) {
856                                 ulm.x = x;
857                                 ulm.y = y;
858                                 brm.x = x+1;
859                                 brm.y = y+1;
860
861                                 pixbuf = get_pixbuf ( vml, &ulm, &brm );
862
863                                 if ( pixbuf ) {
864                                         map_utils_iTMS_to_vikcoord ( &ulm, &coord );
865                                         vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
866                                         vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
867                                 }
868                         }
869                 }
870
871                 // Done after so drawn on top
872                 // Just a handy guide to tile blocks.
873                 if ( vik_debug && vik_verbose ) {
874                         GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
875                         gint width = vik_viewport_get_width(vvp);
876                         gint height = vik_viewport_get_height(vvp);
877                         gint xx, yy;
878                         ulm.x = xmin; ulm.y = ymin;
879                         map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
880                         vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
881                         xx = xx - (vml->tile_size_x/2);
882                         yy = yy - (vml->tile_size_x/2); // Yes use X ATM
883                         for (gint x = xmin; x <= xmax; x++ ) {
884                                 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
885                                 xx += vml->tile_size_x;
886                         }
887                         for (gint y = ymin; y <= ymax; y++ ) {
888                                 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
889                                 yy += vml->tile_size_x; // Yes use X ATM
890                         }
891                 }
892         }
893 }
894
895 /**
896  *
897  */
898 static void mapnik_layer_free ( VikMapnikLayer *vml )
899 {
900         mapnik_interface_free ( vml->mi );
901         if ( vml->filename_css )
902                 g_free ( vml->filename_css );
903         if ( vml->filename_xml )
904                 g_free ( vml->filename_xml );
905 }
906
907 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
908 {
909         return mapnik_layer_new ( vp );
910 }
911
912 typedef enum {
913         MA_VML = 0,
914         MA_VVP,
915         MA_LAST
916 } menu_array_index;
917
918 typedef gpointer menu_array_values[MA_LAST];
919
920 /**
921  *
922  */
923 static void mapnik_layer_flush_memory ( menu_array_values values )
924 {
925         a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
926 }
927
928 /**
929  *
930  */
931 static void mapnik_layer_reload ( menu_array_values values )
932 {
933         VikMapnikLayer *vml = values[MA_VML];
934         VikViewport *vvp = values[MA_VVP];
935         mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
936         mapnik_layer_draw ( vml, vvp );
937 }
938
939 /**
940  * Force carto run
941  *
942  * Most carto projects will consist of many files
943  * ATM don't have a way of detecting when any of the included files have changed
944  * Thus allow a manual method to force re-running carto
945  */
946 static void mapnik_layer_carto ( menu_array_values values )
947 {
948         VikMapnikLayer *vml = values[MA_VML];
949         VikViewport *vvp = values[MA_VVP];
950
951         // Don't load the XML config if carto load fails
952         if ( !carto_load ( vml, vvp ) )
953                 return;
954
955         gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
956         if ( ans ) {
957                 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
958                                            _("Mapnik error loading configuration file:\n%s"),
959                                            ans );
960                 g_free ( ans );
961         }
962         else
963                 mapnik_layer_draw ( vml, vvp );
964 }
965
966 /**
967  * Show Mapnik configuration parameters
968  */
969 static void mapnik_layer_information ( menu_array_values values )
970 {
971         VikMapnikLayer *vml = values[MA_VML];
972         if ( !vml->mi )
973                 return;
974         GArray *array = mapnik_interface_get_parameters( vml->mi );
975         if ( array->len ) {
976                 a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
977                 // Free the copied strings
978                 for ( int i = 0; i < array->len; i++ )
979                         g_free ( g_array_index(array,gchar*,i) );
980         }
981         g_array_free ( array, FALSE );
982 }
983
984 /**
985  *
986  */
987 static void mapnik_layer_about ( menu_array_values values )
988 {
989         VikMapnikLayer *vml = values[MA_VML];
990         gchar *msg = mapnik_interface_about();
991         a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml),  msg );
992         g_free ( msg );
993 }
994
995 /**
996  *
997  */
998 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
999 {
1000         static menu_array_values values;
1001         values[MA_VML] = vml;
1002         values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1003
1004         GtkWidget *item = gtk_menu_item_new();
1005         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1006         gtk_widget_show ( item );
1007
1008         // Typical users shouldn't need to use this functionality - so debug only ATM
1009         if ( vik_debug ) {
1010                 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1011                 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1012                 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1013                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1014                 gtk_widget_show ( item );
1015         }
1016
1017         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1018         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1019         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1020         gtk_widget_show ( item );
1021
1022         if ( g_strcmp0 ("", vml->filename_css) ) {
1023                 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1024                 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1025                 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1026                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1027                 gtk_widget_show ( item );
1028         }
1029
1030         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1031         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1032         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1033         gtk_widget_show ( item );
1034
1035         item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1036         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1037         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1038         gtk_widget_show ( item );
1039 }
1040
1041 /**
1042  * Rerender a specific tile
1043  */
1044 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1045 {
1046         MapCoord ulm;
1047         // Requested position to map coord
1048         map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1049         // Reconvert back - thus getting the coordinate at the tile *ul corner*
1050         map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1051         // Bottom right bound is simply +1 in TMS coords
1052         MapCoord brm = ulm;
1053         brm.x = brm.x+1;
1054         brm.y = brm.y+1;
1055         map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1056         thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1057 }
1058
1059 /**
1060  * Info
1061  */
1062 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1063 {
1064         MapCoord ulm;
1065         // Requested position to map coord
1066         map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1067
1068         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 );
1069
1070         gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1071         gchar *filemsg = NULL;
1072         gchar *timemsg = NULL;
1073
1074         if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1075                 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1076                 // Get some timestamp information of the tile
1077                 struct stat stat_buf;
1078                 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1079                         gchar time_buf[64];
1080                         strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1081                         timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1082                 }
1083                 else {
1084                         timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1085                 }
1086         }
1087         else {
1088                 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1089                 timemsg = g_strdup("");
1090         }
1091
1092         GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1093         g_array_append_val ( array, filemsg );
1094         g_array_append_val ( array, timemsg );
1095
1096         gchar *rendmsg = NULL;
1097         // Show the info
1098         if ( extra.duration > 0.0 ) {
1099                 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1100                 g_array_append_val ( array, rendmsg );
1101         }
1102
1103         a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1104         g_array_free ( array, FALSE );
1105
1106         g_free ( rendmsg );
1107         g_free ( timemsg );
1108         g_free ( filemsg );
1109         g_free ( filename );
1110 }
1111
1112 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1113 {
1114         if ( !vml )
1115                 return FALSE;
1116         if ( event->button == 3 ) {
1117                 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1118                 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1119
1120                 if ( ! vml->right_click_menu ) {
1121                         GtkWidget *item;
1122                         vml->right_click_menu = gtk_menu_new ();
1123
1124                         item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1125                         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1126                         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1127                         gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1128
1129                         item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1130                         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1131                         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1132                         gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1133                 }
1134
1135                 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1136                 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );
1137         }
1138
1139         return FALSE;
1140 }