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