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