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