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