2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (c) 2015, Rob Norris <rw_norris@hotmail.com>
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.
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.
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
29 #include <glib/gstdio.h>
30 #include <glib/gi18n.h>
43 #include "preferences.h"
44 #include "icons/icons.h"
45 #include "mapnik_interface.h"
46 #include "background.h"
48 #include "vikmapslayer.h"
50 #if !GLIB_CHECK_VERSION(2,26,0)
51 typedef struct stat GStatBuf;
54 struct _VikMapnikLayerClass
56 VikLayerClass object_class;
59 static VikLayerParamData file_default ( void )
61 VikLayerParamData data;
66 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
67 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
69 static VikLayerParamData cache_dir_default ( void )
71 VikLayerParamData data;
72 data.s = g_strconcat ( maps_layer_default_dir(), "MapnikRendering", NULL );
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
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 },
100 PARAM_FILE_CACHE_DIR,
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 );
114 static gpointer mapnik_feature_create ( VikWindow *vw, VikViewport *vvp)
119 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp );
121 // See comment in viktrwlayer.c for advice on values used
123 static VikToolInterface mapnik_tools[] = {
126 { { "MapnikFeatures", GTK_STOCK_INFO, N_("_Mapnik Features"), NULL, N_("Mapnik Features"), 0 },
127 (VikToolConstructorFunc) mapnik_feature_create,
133 (VikToolMouseFunc) mapnik_feature_release,
136 GDK_LEFT_PTR, NULL, NULL },
139 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
141 VikLayerInterface vik_mapnik_layer_interface = {
143 N_("Mapnik Rendering"),
145 &vikmapniklayer_pixbuf, // icon
148 sizeof(mapnik_tools) / sizeof(VikToolInterface),
157 (VikLayerFuncCreate) mapnik_layer_create,
158 (VikLayerFuncRealize) NULL,
159 (VikLayerFuncPostRead) mapnik_layer_post_read,
160 (VikLayerFuncFree) mapnik_layer_free,
162 (VikLayerFuncProperties) NULL,
163 (VikLayerFuncDraw) mapnik_layer_draw,
164 (VikLayerFuncChangeCoordMode) NULL,
166 (VikLayerFuncSetMenuItemsSelection) NULL,
167 (VikLayerFuncGetMenuItemsSelection) NULL,
169 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
170 (VikLayerFuncSublayerAddMenuItems) NULL,
172 (VikLayerFuncSublayerRenameRequest) NULL,
173 (VikLayerFuncSublayerToggleVisible) NULL,
174 (VikLayerFuncSublayerTooltip) NULL,
175 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
176 (VikLayerFuncLayerSelected) NULL,
178 (VikLayerFuncMarshall) mapnik_layer_marshall,
179 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
181 (VikLayerFuncSetParam) mapnik_layer_set_param,
182 (VikLayerFuncGetParam) mapnik_layer_get_param,
183 (VikLayerFuncChangeParam) NULL,
185 (VikLayerFuncReadFileData) NULL,
186 (VikLayerFuncWriteFileData) NULL,
188 (VikLayerFuncDeleteItem) NULL,
189 (VikLayerFuncCutItem) NULL,
190 (VikLayerFuncCopyItem) NULL,
191 (VikLayerFuncPasteItem) NULL,
192 (VikLayerFuncFreeCopiedItem) NULL,
193 (VikLayerFuncDragDropRequest) NULL,
195 (VikLayerFuncSelectClick) NULL,
196 (VikLayerFuncSelectMove) NULL,
197 (VikLayerFuncSelectRelease) NULL,
198 (VikLayerFuncSelectedViewportMenu) NULL,
201 struct _VikMapnikLayer {
203 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
207 guint tile_size_x; // Y is the same as X ATM
210 guint rerender_timeout;
212 gboolean use_file_cache;
213 gchar *file_cache_dir;
215 VikCoord rerender_ul;
216 VikCoord rerender_br;
217 gdouble rerender_zoom;
218 GtkWidget *right_click_menu;
221 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
222 #define MAPNIK_PREFS_NAMESPACE "mapnik."
224 static VikLayerParamData plugins_default ( void )
226 VikLayerParamData data;
228 data.s = g_strdup ( "input" );
230 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
231 data.s = g_strdup ( "/usr/lib/mapnik/input" );
232 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
233 // Current Debian location
234 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
236 data.s = g_strdup ( "" );
241 static VikLayerParamData fonts_default ( void )
243 // Possibly should be string list to allow loading from multiple directories
244 VikLayerParamData data;
246 data.s = g_strdup ( "C:\\Windows\\Fonts" );
247 #elif defined __APPLE__
248 data.s = g_strdup ( "/Library/Fonts" );
250 data.s = g_strdup ( "/usr/share/fonts" );
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 },
265 static time_t planet_import_time;
267 static GMutex *tp_mutex;
268 static GHashTable *requests = NULL;
271 * vik_mapnik_layer_init:
273 * Mostly to initialize preferences
275 void vik_mapnik_layer_init (void)
277 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
280 VikLayerParamData tmp = plugins_default();
281 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
283 tmp = fonts_default();
284 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
287 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
289 tmp.u = 168; // One week
290 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
293 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
295 tp_mutex = vik_mutex_new();
297 // Just storing keys only
298 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
300 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
301 GDateTime *now = g_date_time_new_now_local ();
302 GDateTime *then = g_date_time_add_hours (now, -hours);
303 planet_import_time = g_date_time_to_unix (then);
304 g_date_time_unref ( now );
305 g_date_time_unref ( then );
308 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
309 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
310 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
311 // Only update if newer
312 if ( planet_import_time > gsb.st_mtime )
313 planet_import_time = gsb.st_mtime;
315 g_free ( import_time_file );
318 void vik_mapnik_layer_uninit ()
320 vik_mutex_free (tp_mutex);
323 // NB Only performed once per program run
324 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
326 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
327 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
328 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
331 GType vik_mapnik_layer_get_type ()
333 static GType vml_type = 0;
336 static const GTypeInfo vml_info = {
337 sizeof (VikMapnikLayerClass),
338 NULL, /* base_init */
339 NULL, /* base_finalize */
340 (GClassInitFunc) mapnik_layer_class_init, /* class init */
341 NULL, /* class_finalize */
342 NULL, /* class_data */
343 sizeof (VikMapnikLayer),
345 NULL /* instance init */
347 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
353 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
355 return vml->filename_xml;
358 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
360 if ( vml->filename_xml )
361 g_free (vml->filename_xml);
362 vml->filename_xml = g_strdup (name);
365 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
367 if ( vml->filename_css )
368 g_free (vml->filename_css);
369 vml->filename_css = g_strdup (name);
372 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
374 if ( vml->file_cache_dir )
375 g_free (vml->file_cache_dir);
376 vml->file_cache_dir = g_strdup (name);
379 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
381 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
384 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
386 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
387 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
391 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
394 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
395 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
396 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
397 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
398 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
404 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
406 VikLayerParamData data;
408 case PARAM_CONFIG_CSS: {
409 data.s = vml->filename_css;
410 gboolean set = FALSE;
411 if ( is_file_operation ) {
412 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
413 gchar *cwd = g_get_current_dir();
415 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
416 if ( !data.s ) data.s = "";
422 data.s = vml->filename_css ? vml->filename_css : "";
425 case PARAM_CONFIG_XML: {
426 data.s = vml->filename_xml;
427 gboolean set = FALSE;
428 if ( is_file_operation ) {
429 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
430 gchar *cwd = g_get_current_dir();
432 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
433 if ( !data.s ) data.s = "";
439 data.s = vml->filename_xml ? vml->filename_xml : "";
442 case PARAM_ALPHA: data.u = vml->alpha; break;
443 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
444 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
453 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
455 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
456 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
457 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
458 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
460 vml->mi = mapnik_interface_new();
466 * ATM don't have any version issues AFAIK
467 * Tested with carto 0.14.0
469 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
471 gchar *mystdout = NULL;
472 gchar *mystderr = NULL;
473 GError *error = NULL;
475 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
476 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
478 gboolean answer = TRUE;
479 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
481 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
482 // cf code in babel.c to handle stdout
484 // NB Running carto may take several seconds
485 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
486 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
488 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
489 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
490 vik_window_set_busy_cursor ( vw );
495 // You won't get a sensible timing measurement if running too old a GLIB
496 #if GLIB_CHECK_VERSION (2, 28, 0)
497 tt1 = g_get_real_time ();
500 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
501 #if GLIB_CHECK_VERSION (2, 28, 0)
502 tt2 = g_get_real_time ();
505 if ( strlen(mystderr) > 1 ) {
506 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
510 // NB This will overwrite the specified XML file
511 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
512 // XML Not specified so try to create based on CSS file name
513 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
515 g_critical ("%s: %s", __FUNCTION__, error->message );
516 if ( vml->filename_xml )
517 g_free (vml->filename_xml);
518 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
520 g_warning ("%s: %s", __FUNCTION__, error->message );
521 // Prevent overwriting self
522 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
523 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
526 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
527 g_warning ("%s: %s", __FUNCTION__, error->message );
528 g_error_free (error);
535 g_warning ("%s: %s", __FUNCTION__, error->message );
536 g_error_free (error);
541 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
542 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
544 vik_window_clear_busy_cursor ( vw );
552 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
554 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
556 // Determine if carto needs to be run
557 gboolean do_carto = FALSE;
558 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
559 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
560 // Compare timestamps
562 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
564 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
565 // Is CSS file newer than the XML file
566 if ( gsb2.st_mtime > gsb1.st_mtime )
569 g_debug ( "No need to run carto" );
573 // XML file doesn't exist
578 // No XML specified thus need to generate
584 // Don't load the XML config if carto load fails
585 if ( !carto_load ( vml, vvp ) )
588 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
590 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
591 _("Mapnik error loading configuration file:\n%s"),
598 ui_add_recent_file ( vml->filename_xml );
602 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
604 // Free returned string after use
605 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
607 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
610 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
612 if ( vml->use_file_cache ) {
613 if ( vml->file_cache_dir ) {
614 GError *error = NULL;
615 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
617 gchar *dir = g_path_get_dirname ( filename );
618 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
619 g_mkdir_with_parents ( dir , 0777 );
622 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
623 g_warning ("%s: %s", __FUNCTION__, error->message );
624 g_error_free (error);
637 const gchar* request;
643 * Common render function which can run in separate thread
645 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
647 gint64 tt1 = g_get_real_time ();
648 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
649 gint64 tt2 = g_get_real_time ();
650 gdouble tt = (gdouble)(tt2-tt1)/1000000;
651 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
653 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
654 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
656 possibly_save_pixbuf ( vml, pixbuf, ulm );
658 // NB Mapnik can apply alpha, but use our own function for now
659 if ( vml->alpha < 255 )
660 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
661 a_mapcache_add ( pixbuf, (mapcache_extra_t){ tt }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
664 static void render_info_free ( RenderInfo *data )
668 g_free ( data->ulmc );
669 // NB No need to free the request/key - as this is freed by the hash table destructor
673 static void background ( RenderInfo *data, gpointer threaddata )
675 int res = a_background_thread_progress ( threaddata, 0 );
677 render ( data->vml, data->ul, data->br, data->ulmc );
680 g_mutex_lock(tp_mutex);
681 g_hash_table_remove (requests, data->request);
682 g_mutex_unlock(tp_mutex);
685 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
688 static void render_cancel_cleanup (RenderInfo *data)
693 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
698 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
701 guint nn = name ? g_str_hash ( name ) : 0;
702 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
704 g_mutex_lock(tp_mutex);
706 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
708 g_mutex_unlock (tp_mutex);
712 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
714 ri->ul = g_malloc ( sizeof(VikCoord) );
715 ri->br = g_malloc ( sizeof(VikCoord) );
716 ri->ulmc = g_malloc ( sizeof(MapCoord) );
717 memcpy(ri->ul, ul, sizeof(VikCoord));
718 memcpy(ri->br, br, sizeof(VikCoord));
719 memcpy(ri->ulmc, mul, sizeof(MapCoord));
720 ri->request = request;
722 g_hash_table_insert ( requests, request, NULL );
724 g_mutex_unlock (tp_mutex);
726 gchar *basename = g_path_get_basename (name);
727 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
729 a_background_thread ( BACKGROUND_POOL_LOCAL,
730 VIK_GTK_WINDOW_FROM_LAYER(vml),
732 (vik_thr_func) background,
734 (vik_thr_free_func) render_info_free,
735 (vik_thr_free_func) render_cancel_cleanup,
737 g_free ( description );
743 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
746 GdkPixbuf *pixbuf = NULL;
747 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
750 if ( g_stat ( filename, &gsb ) == 0 ) {
752 GError *error = NULL;
753 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
755 g_warning ("%s: %s", __FUNCTION__, error->message );
756 g_error_free ( error );
759 if ( vml->alpha < 255 )
760 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
761 a_mapcache_add ( pixbuf, (mapcache_extra_t) { -42.0 }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
763 // If file is too old mark for rerendering
764 if ( planet_import_time < gsb.st_mtime ) {
776 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
778 VikCoord ul; VikCoord br;
779 GdkPixbuf *pixbuf = NULL;
781 map_utils_iTMS_to_vikcoord (ulm, &ul);
782 map_utils_iTMS_to_vikcoord (brm, &br);
784 pixbuf = a_mapcache_get ( ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
787 gboolean rerender = FALSE;
788 if ( vml->use_file_cache && vml->file_cache_dir )
789 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
790 if ( ! pixbuf || rerender ) {
792 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
794 // Run in the foreground
795 render ( vml, &ul, &br, ulm );
796 vik_layer_emit_update ( VIK_LAYER(vml) );
807 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
812 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
813 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
814 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
819 ul.mode = VIK_COORD_LATLON;
820 br.mode = VIK_COORD_LATLON;
821 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
822 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
824 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
825 gdouble yzoom = vik_viewport_get_ympp ( vvp );
829 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
830 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
831 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
836 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
837 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
839 // Split rendering into a grid for the current viewport
840 // thus each individual 'tile' can then be stored in the map cache
841 for (gint x = xmin; x <= xmax; x++ ) {
842 for (gint y = ymin; y <= ymax; y++ ) {
848 pixbuf = get_pixbuf ( vml, &ulm, &brm );
851 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
852 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
853 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
858 // Done after so drawn on top
859 // Just a handy guide to tile blocks.
860 if ( vik_debug && vik_verbose ) {
861 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
862 gint width = vik_viewport_get_width(vvp);
863 gint height = vik_viewport_get_height(vvp);
865 ulm.x = xmin; ulm.y = ymin;
866 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
867 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
868 xx = xx - (vml->tile_size_x/2);
869 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
870 for (gint x = xmin; x <= xmax; x++ ) {
871 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
872 xx += vml->tile_size_x;
874 for (gint y = ymin; y <= ymax; y++ ) {
875 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
876 yy += vml->tile_size_x; // Yes use X ATM
885 static void mapnik_layer_free ( VikMapnikLayer *vml )
887 mapnik_interface_free ( vml->mi );
888 if ( vml->filename_css )
889 g_free ( vml->filename_css );
890 if ( vml->filename_xml )
891 g_free ( vml->filename_xml );
894 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
896 return mapnik_layer_new ( vp );
905 typedef gpointer menu_array_values[MA_LAST];
910 static void mapnik_layer_flush_memory ( menu_array_values values )
912 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
918 static void mapnik_layer_reload ( menu_array_values values )
920 VikMapnikLayer *vml = values[MA_VML];
921 VikViewport *vvp = values[MA_VVP];
922 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
923 mapnik_layer_draw ( vml, vvp );
929 * Most carto projects will consist of many files
930 * ATM don't have a way of detecting when any of the included files have changed
931 * Thus allow a manual method to force re-running carto
933 static void mapnik_layer_carto ( menu_array_values values )
935 VikMapnikLayer *vml = values[MA_VML];
936 VikViewport *vvp = values[MA_VVP];
938 // Don't load the XML config if carto load fails
939 if ( !carto_load ( vml, vvp ) )
942 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
944 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
945 _("Mapnik error loading configuration file:\n%s"),
950 mapnik_layer_draw ( vml, vvp );
956 static void mapnik_layer_about ( menu_array_values values )
958 VikMapnikLayer *vml = values[MA_VML];
959 gchar *msg = mapnik_interface_about();
960 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
967 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
969 static menu_array_values values;
970 values[MA_VML] = vml;
971 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
973 GtkWidget *item = gtk_menu_item_new();
974 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
975 gtk_widget_show ( item );
977 // Typical users shouldn't need to use this functionality - so debug only ATM
979 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
980 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
981 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
982 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
983 gtk_widget_show ( item );
986 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
987 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
988 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
989 gtk_widget_show ( item );
991 if ( g_strcmp0 ("", vml->filename_css) ) {
992 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
993 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
994 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
995 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
996 gtk_widget_show ( item );
999 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1000 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1001 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1002 gtk_widget_show ( item );
1006 * Rerender a specific tile
1008 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1011 // Requested position to map coord
1012 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1013 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1014 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1015 // Bottom right bound is simply +1 in TMS coords
1019 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1020 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1026 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1029 // Requested position to map coord
1030 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1032 mapcache_extra_t extra = a_mapcache_get_extra ( ulm.x, ulm.y, ulm.z, MAP_ID_MAPNIK_RENDER, ulm.scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
1034 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1035 gchar *filemsg = NULL;
1036 gchar *timemsg = NULL;
1038 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1039 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1040 // Get some timestamp information of the tile
1041 struct stat stat_buf;
1042 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1044 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1045 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1048 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1052 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1053 timemsg = g_strdup("");
1056 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1057 g_array_append_val ( array, filemsg );
1058 g_array_append_val ( array, timemsg );
1060 gchar *rendmsg = NULL;
1062 if ( extra.duration > 0.0 ) {
1063 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1064 g_array_append_val ( array, rendmsg );
1067 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1068 g_array_free ( array, FALSE );
1073 g_free ( filename );
1076 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1080 if ( event->button == 3 ) {
1081 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1082 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1084 if ( ! vml->right_click_menu ) {
1086 vml->right_click_menu = gtk_menu_new ();
1088 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1089 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1090 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1091 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1093 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1094 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1095 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1096 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1099 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1100 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );