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