]> git.street.me.uk Git - andy/viking.git/blobdiff - src/vikmapniklayer.c
Only call gps_close() after a successful gps_open().
[andy/viking.git] / src / vikmapniklayer.c
index 028fc658d3c006fee0041ea8d40fd72f8f3d82f6..38587d14ec674df3038abab29a0e9bbce482bfe5 100644 (file)
 #include "maputils.h"
 #include "mapcoord.h"
 #include "mapcache.h"
+#include "dir.h"
 #include "util.h"
 #include "ui_util.h"
 #include "preferences.h"
 #include "icons/icons.h"
 #include "mapnik_interface.h"
+#include "background.h"
+
+#include "vikmapslayer.h"
 
 struct _VikMapnikLayerClass
 {
@@ -58,9 +62,17 @@ static VikLayerParamData file_default ( void )
 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
 
+static VikLayerParamData cache_dir_default ( void )
+{
+       VikLayerParamData data;
+       data.s = g_strconcat ( maps_layer_default_dir(), "MapnikRendering", NULL );
+       return data;
+}
+
 static VikLayerParamScale scales[] = {
        { 0, 255, 5, 0 }, // Alpha
        { 64, 1024, 8, 0 }, // Tile size
+       { 0, 1024, 12, 0 }, // Rerender timeout hours
 };
 
 VikLayerParam mapnik_layer_params[] = {
@@ -70,12 +82,18 @@ VikLayerParam mapnik_layer_params[] = {
     N_("Mapnik XML configuration file"), file_default, NULL, NULL },
   { VIK_LAYER_MAPNIK, "alpha", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Alpha:"), VIK_LAYER_WIDGET_HSCALE, &scales[0], NULL,
     NULL, alpha_default, NULL, NULL },
+  { VIK_LAYER_MAPNIK, "use-file-cache", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Use File Cache:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL,
+    NULL, vik_lpd_true_default, NULL, NULL },
+  { VIK_LAYER_MAPNIK, "file-cache-dir", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("File Cache Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL,
+    NULL, cache_dir_default, NULL, NULL },
 };
 
 enum {
   PARAM_CONFIG_CSS = 0,
   PARAM_CONFIG_XML,
   PARAM_ALPHA,
+  PARAM_USE_FILE_CACHE,
+  PARAM_FILE_CACHE_DIR,
   NUM_PARAMS };
 
 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
@@ -89,11 +107,29 @@ static void mapnik_layer_free ( VikMapnikLayer *vml );
 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
 
+static gpointer mapnik_feature_create ( VikWindow *vw, VikViewport *vvp)
+{
+  return vvp;
+}
+
+static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp );
+
 // See comment in viktrwlayer.c for advice on values used
 // FUTURE:
 static VikToolInterface mapnik_tools[] = {
        // Layer Info
        // Zoom All?
+  { { "MapnikFeatures", GTK_STOCK_INFO, N_("_Mapnik Features"), NULL, N_("Mapnik Features"), 0 },
+    (VikToolConstructorFunc) mapnik_feature_create,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    (VikToolMouseFunc) mapnik_feature_release,
+    NULL,
+    FALSE,
+    GDK_LEFT_PTR, NULL, NULL },
 };
 
 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
@@ -123,6 +159,8 @@ VikLayerInterface vik_mapnik_layer_interface = {
        (VikLayerFuncDraw)                    mapnik_layer_draw,
        (VikLayerFuncChangeCoordMode)         NULL,
 
+       (VikLayerFuncGetTimestamp)            NULL,
+
        (VikLayerFuncSetMenuItemsSelection)   NULL,
        (VikLayerFuncGetMenuItemsSelection)   NULL,
 
@@ -167,6 +205,15 @@ struct _VikMapnikLayer {
        guint tile_size_x; // Y is the same as X ATM
        gboolean loaded;
        MapnikInterface* mi;
+       guint rerender_timeout;
+
+       gboolean use_file_cache;
+       gchar *file_cache_dir;
+
+       VikCoord rerender_ul;
+       VikCoord rerender_br;
+       gdouble rerender_zoom;
+       GtkWidget *right_click_menu;
 };
 
 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
@@ -180,8 +227,10 @@ static VikLayerParamData plugins_default ( void )
 #else
        if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
                data.s = g_strdup ( "/usr/lib/mapnik/input" );
+               // Current Debian locations
+       else if ( g_file_test ( "/usr/lib/mapnik/3.0/input", G_FILE_TEST_EXISTS ) )
+               data.s = g_strdup ( "/usr/lib/mapnik/3.0/input" );
        else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
-               // Current Debian location
                data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
        else
                data.s = g_strdup ( "" );
@@ -208,18 +257,21 @@ static VikLayerParam prefs[] = {
        { 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 },
        { 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 },
        { 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 },
+       { 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 },
        // Changeable any time
        { 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 },
 };
 
-// NB Only performed once per program run
-static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
-{
-       mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
-                                     a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
-                                     a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
-}
+static time_t planet_import_time;
+
+static GMutex *tp_mutex;
+static GHashTable *requests = NULL;
 
+/**
+ * vik_mapnik_layer_init:
+ *
+ * Just initialize preferences
+ */
 void vik_mapnik_layer_init (void)
 {
        a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
@@ -234,10 +286,61 @@ void vik_mapnik_layer_init (void)
        tmp.b = TRUE;
        a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
 
+       tmp.u = 168; // One week
+       a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
+
        tmp.s = "carto";
        a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
 }
 
+/**
+ * vik_mapnik_layer_post_init:
+ *
+ * Initialize data structures - now that reading preferences is OK to perform
+ */
+void vik_mapnik_layer_post_init (void)
+{
+       tp_mutex = vik_mutex_new();
+
+       // Just storing keys only
+       requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
+
+       guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
+       GDateTime *now = g_date_time_new_now_local ();
+       GDateTime *then = g_date_time_add_hours (now, -hours);
+       planet_import_time = g_date_time_to_unix (then);
+       g_date_time_unref ( now );
+       g_date_time_unref ( then );
+
+       GStatBuf gsb;
+       // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
+       gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
+       if ( g_stat ( import_time_file, &gsb ) == 0 ) {
+               // Only update if newer
+               if ( planet_import_time > gsb.st_mtime )
+                       planet_import_time = gsb.st_mtime;
+       }
+       g_free ( import_time_file );
+}
+
+void vik_mapnik_layer_uninit ()
+{
+       vik_mutex_free (tp_mutex);
+}
+
+// NB Only performed once per program run
+static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
+{
+       VikLayerParamData *pd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory");
+       VikLayerParamData *fd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory");
+       VikLayerParamData *rfd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory");
+
+       if ( pd && fd && rfd )
+               mapnik_interface_initialize ( pd->s, fd->s, rfd->b );
+       else
+               g_critical ( "Unable to initialize mapnik interface from preferences" );
+}
+
 GType vik_mapnik_layer_get_type ()
 {
        static GType vml_type = 0;
@@ -269,7 +372,11 @@ static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
 {
        if ( vml->filename_xml )
                g_free (vml->filename_xml);
-       vml->filename_xml = g_strdup (name);
+       // Mapnik doesn't seem to cope with relative filenames
+       if ( g_strcmp0 (name, "" ) )
+               vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
+       else
+               vml->filename_xml = g_strdup (name);
 }
 
 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
@@ -279,6 +386,13 @@ static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
        vml->filename_css = g_strdup (name);
 }
 
+static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
+{
+       if ( vml->file_cache_dir )
+               g_free (vml->file_cache_dir);
+       vml->file_cache_dir = g_strdup (name);
+}
+
 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
 {
        vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
@@ -297,6 +411,8 @@ static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLay
                case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
                case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
                case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
+               case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
+               case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
                default: break;
        }
        return TRUE;
@@ -341,7 +457,8 @@ static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 i
                        break;
                }
                case PARAM_ALPHA: data.u = vml->alpha; break;
-               //case PARAM_TILE_X: data.u = vml->tile_size_x; break;
+               case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
+               case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
                default: break;
        }
        return data;
@@ -422,6 +539,7 @@ gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
                                if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
                                        vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
                                }
+                               g_regex_unref ( regex );
                        }
                        if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error)  ) {
                                g_warning ("%s: %s", __FUNCTION__, error->message );
@@ -446,10 +564,6 @@ gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
        return answer;
 }
 
-#if !GLIB_CHECK_VERSION(2,26,0)
-typedef struct stat GStatBuf;
-#endif
-
 /**
  *
  */
@@ -488,13 +602,13 @@ static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean fro
                // Don't load the XML config if carto load fails
                if ( !carto_load ( vml, vvp ) )
                        return;
-       if ( mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x ) ) {
-               if ( !from_file )
-                       a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
-                                                _("Mapnik error loading configuration file: %s"),
-                                                vml->filename_xml );
-               else
-                       g_warning ( _("Mapnik error loading configuration file: %s"), vml->filename_xml );
+
+       gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
+       if ( ans ) {
+               a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
+                                          _("Mapnik error loading configuration file:\n%s"),
+                                          ans );
+               g_free ( ans );
        }
        else {
                vml->loaded = TRUE;
@@ -503,13 +617,190 @@ static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean fro
        }
 }
 
+#define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
+
+// Free returned string after use
+static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
+{
+       return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
+}
+
+static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
+{
+       if ( vml->use_file_cache ) {
+               if ( vml->file_cache_dir ) {
+                       GError *error = NULL;
+                       gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
+
+                       gchar *dir = g_path_get_dirname ( filename );
+                       if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
+                               if ( g_mkdir_with_parents ( dir , 0777 ) != 0 )
+                                       g_warning ("%s: Failed to mkdir %s", __FUNCTION__, dir );
+                       g_free ( dir );
+
+                       if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
+                               g_warning ("%s: %s", __FUNCTION__, error->message );
+                               g_error_free (error);
+                       }
+                       g_free (filename);
+               }
+       }
+}
+
+typedef struct
+{
+       VikMapnikLayer *vml;
+       VikCoord *ul;
+       VikCoord *br;
+       MapCoord *ulmc;
+       const gchar* request;
+} RenderInfo;
+
+/**
+ * render:
+ *
+ * Common render function which can run in separate thread
+ */
+static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
+{
+       gint64 tt1 = g_get_real_time ();
+       GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
+       gint64 tt2 = g_get_real_time ();
+       gdouble tt = (gdouble)(tt2-tt1)/1000000;
+       g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
+       if ( !pixbuf ) {
+               // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
+               pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
+       }
+       possibly_save_pixbuf ( vml, pixbuf, ulm );
+
+       // NB Mapnik can apply alpha, but use our own function for now
+       if ( vml->alpha < 255 )
+               pixbuf = ui_pixbuf_scale_alpha ( pixbuf, vml->alpha );
+       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 );
+       g_object_unref(pixbuf);
+}
+
+static void render_info_free ( RenderInfo *data )
+{
+       g_free ( data->ul );
+       g_free ( data->br );
+       g_free ( data->ulmc );
+       // NB No need to free the request/key - as this is freed by the hash table destructor
+       g_free ( data );
+}
+
+static void background ( RenderInfo *data, gpointer threaddata )
+{
+       int res = a_background_thread_progress ( threaddata, 0 );
+       if (res == 0) {
+               render ( data->vml, data->ul, data->br, data->ulmc );
+       }
+
+       g_mutex_lock(tp_mutex);
+       g_hash_table_remove (requests, data->request);
+       g_mutex_unlock(tp_mutex);
+
+       if (res == 0)
+               vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
+}
+
+static void render_cancel_cleanup (RenderInfo *data)
+{
+       // Anything?
+}
+
+#define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
+
+/**
+ * Thread
+ */
+static void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
+{
+       // Create request
+       guint nn = name ? g_str_hash ( name ) : 0;
+       gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
+
+       g_mutex_lock(tp_mutex);
+
+       if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
+               g_free ( request );
+               g_mutex_unlock (tp_mutex);
+               return;
+       }
+
+       RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
+       ri->vml = vml;
+       ri->ul = g_malloc ( sizeof(VikCoord) );
+       ri->br = g_malloc ( sizeof(VikCoord) );
+       ri->ulmc = g_malloc ( sizeof(MapCoord) );
+       memcpy(ri->ul, ul, sizeof(VikCoord));
+       memcpy(ri->br, br, sizeof(VikCoord));
+       memcpy(ri->ulmc, mul, sizeof(MapCoord));
+       ri->request = request;
+
+       g_hash_table_insert ( requests, request, NULL );
+
+       g_mutex_unlock (tp_mutex);
+
+       gchar *basename = g_path_get_basename (name);
+       gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
+       g_free ( basename );
+       a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
+                             VIK_GTK_WINDOW_FROM_LAYER(vml),
+                             description,
+                             (vik_thr_func) background,
+                             ri,
+                             (vik_thr_free_func) render_info_free,
+                             (vik_thr_free_func) render_cancel_cleanup,
+                             1 );
+       g_free ( description );
+}
+
 /**
+ * load_pixbuf:
  *
+ * If function returns GdkPixbuf properly, reference counter to this
+ * buffer has to be decreased, when buffer is no longer needed.
+ */
+static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
+{
+       *rerender = FALSE;
+       GdkPixbuf *pixbuf = NULL;
+       gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
+
+       GStatBuf gsb;
+       if ( g_stat ( filename, &gsb ) == 0 ) {
+               // Get from disk
+               GError *error = NULL;
+               pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
+               if ( error ) {
+                       g_warning ("%s: %s", __FUNCTION__, error->message );
+                       g_error_free ( error );
+               }
+               else {
+                       if ( vml->alpha < 255 )
+                               pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
+                       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 );
+               }
+               // If file is too old mark for rerendering
+               if ( planet_import_time < gsb.st_mtime ) {
+                       *rerender = TRUE;
+               }
+       }
+       g_free ( filename );
+
+       return pixbuf;
+}
+
+/**
+ * Caller has to decrease reference counter of returned
+ * GdkPixbuf, when buffer is no longer needed.
  */
 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
 {
        VikCoord ul; VikCoord br;
-       GdkPixbuf *pixbuf;
+       GdkPixbuf *pixbuf = NULL;
 
        map_utils_iTMS_to_vikcoord (ulm, &ul);
        map_utils_iTMS_to_vikcoord (brm, &br);
@@ -517,12 +808,17 @@ static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm
        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 );
 
        if ( ! pixbuf ) {
-               pixbuf = mapnik_interface_render ( vml->mi, ul.north_south, ul.east_west, br.north_south, br.east_west );
-               if ( pixbuf ) {
-                       // NB Mapnik can apply alpha, but use our own function for now
-                       if ( vml->alpha < 255 )
-                               pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
-                       a_mapcache_add ( pixbuf, ulm->x, ulm->y, ulm->z, MAPNIK_LAYER_MAP_TYPE, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
+               gboolean rerender = FALSE;
+               if ( vml->use_file_cache && vml->file_cache_dir )
+                       pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
+               if ( ! pixbuf || rerender ) {
+                       if ( TRUE )
+                               thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
+                       else {
+                               // Run in the foreground
+                               render ( vml, &ul, &br, ulm );
+                               vik_layer_emit_update ( VIK_LAYER(vml) );
+                       }
                }
        }
 
@@ -543,6 +839,13 @@ static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
                return;
        }
 
+       if ( vml->mi ) {
+               gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
+               if ( copyright ) {
+                       vik_viewport_add_copyright ( vvp, copyright );
+               }
+       }
+
        VikCoord ul, br;
        ul.mode = VIK_COORD_LATLON;
        br.mode = VIK_COORD_LATLON;
@@ -566,8 +869,6 @@ static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
 
                // Split rendering into a grid for the current viewport
                //  thus each individual 'tile' can then be stored in the map cache
-               // TODO: Also potentially allows using multi threads for each individual tile
-               //   this is more important for complicated stylesheets/datasources as each rendering may take some time
                for (gint x = xmin; x <= xmax; x++ ) {
                        for (gint y = ymin; y <= ymax; y++ ) {
                                ulm.x = x;
@@ -581,6 +882,7 @@ static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
                                        map_utils_iTMS_to_vikcoord ( &ulm, &coord );
                                        vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
                                        vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
+                                       g_object_unref(pixbuf);
                                }
                        }
                }
@@ -642,6 +944,73 @@ static void mapnik_layer_flush_memory ( menu_array_values values )
        a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
 }
 
+/**
+ *
+ */
+static void mapnik_layer_reload ( menu_array_values values )
+{
+       VikMapnikLayer *vml = values[MA_VML];
+       VikViewport *vvp = values[MA_VVP];
+       mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
+       mapnik_layer_draw ( vml, vvp );
+}
+
+/**
+ * Force carto run
+ *
+ * Most carto projects will consist of many files
+ * ATM don't have a way of detecting when any of the included files have changed
+ * Thus allow a manual method to force re-running carto
+ */
+static void mapnik_layer_carto ( menu_array_values values )
+{
+       VikMapnikLayer *vml = values[MA_VML];
+       VikViewport *vvp = values[MA_VVP];
+
+       // Don't load the XML config if carto load fails
+       if ( !carto_load ( vml, vvp ) )
+               return;
+
+       gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
+       if ( ans ) {
+               a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
+                                          _("Mapnik error loading configuration file:\n%s"),
+                                          ans );
+               g_free ( ans );
+       }
+       else
+               mapnik_layer_draw ( vml, vvp );
+}
+
+/**
+ * Show Mapnik configuration parameters
+ */
+static void mapnik_layer_information ( menu_array_values values )
+{
+       VikMapnikLayer *vml = values[MA_VML];
+       if ( !vml->mi )
+               return;
+       GArray *array = mapnik_interface_get_parameters( vml->mi );
+       if ( array->len ) {
+               a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
+               // Free the copied strings
+               for ( int i = 0; i < array->len; i++ )
+                       g_free ( g_array_index(array,gchar*,i) );
+       }
+       g_array_free ( array, FALSE );
+}
+
+/**
+ *
+ */
+static void mapnik_layer_about ( menu_array_values values )
+{
+       VikMapnikLayer *vml = values[MA_VML];
+       gchar *msg = mapnik_interface_about();
+       a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml),  msg );
+       g_free ( msg );
+}
+
 /**
  *
  */
@@ -663,4 +1032,128 @@ static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gp
                gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
                gtk_widget_show ( item );
        }
+
+       item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
+       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       gtk_widget_show ( item );
+
+       if ( g_strcmp0 ("", vml->filename_css) ) {
+               item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
+               gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
+               g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
+               gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+               gtk_widget_show ( item );
+       }
+
+       item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
+       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       gtk_widget_show ( item );
+
+       item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
+       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       gtk_widget_show ( item );
+}
+
+/**
+ * Rerender a specific tile
+ */
+static void mapnik_layer_rerender ( VikMapnikLayer *vml )
+{
+       MapCoord ulm;
+       // Requested position to map coord
+       map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
+       // Reconvert back - thus getting the coordinate at the tile *ul corner*
+       map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
+       // Bottom right bound is simply +1 in TMS coords
+       MapCoord brm = ulm;
+       brm.x = brm.x+1;
+       brm.y = brm.y+1;
+       map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
+       thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
+}
+
+/**
+ * Info
+ */
+static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
+{
+       MapCoord ulm;
+       // Requested position to map coord
+       map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
+
+       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 );
+
+       gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
+       gchar *filemsg = NULL;
+       gchar *timemsg = NULL;
+
+       if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
+               filemsg = g_strconcat ( "Tile File: ", filename, NULL );
+               // Get some timestamp information of the tile
+               GStatBuf stat_buf;
+               if ( g_stat ( filename, &stat_buf ) == 0 ) {
+                       gchar time_buf[64];
+                       strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
+                       timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
+               }
+               else {
+                       timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
+               }
+       }
+       else {
+               filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
+               timemsg = g_strdup("");
+       }
+
+       GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
+       g_array_append_val ( array, filemsg );
+       g_array_append_val ( array, timemsg );
+
+       gchar *rendmsg = NULL;
+       // Show the info
+       if ( extra.duration > 0.0 ) {
+               rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
+               g_array_append_val ( array, rendmsg );
+       }
+
+       a_dialog_list (  VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
+       g_array_free ( array, FALSE );
+
+       g_free ( rendmsg );
+       g_free ( timemsg );
+       g_free ( filemsg );
+       g_free ( filename );
+}
+
+static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
+{
+       if ( !vml )
+               return FALSE;
+       if ( event->button == 3 ) {
+               vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
+               vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
+
+               if ( ! vml->right_click_menu ) {
+                       GtkWidget *item;
+                       vml->right_click_menu = gtk_menu_new ();
+
+                       item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
+                       gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
+                       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
+                       gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
+
+                       item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
+                       gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
+                       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
+                       gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
+               }
+
+               gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
+               gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );
+       }
+
+       return FALSE;
 }