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