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 struct _VikMapnikLayerClass
52 VikLayerClass object_class;
55 static VikLayerParamData file_default ( void )
57 VikLayerParamData data;
62 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
63 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
65 static VikLayerParamData cache_dir_default ( void )
67 VikLayerParamData data;
68 data.s = g_strconcat ( maps_layer_default_dir(), "MapnikRendering", NULL );
72 static VikLayerParamScale scales[] = {
73 { 0, 255, 5, 0 }, // Alpha
74 { 64, 1024, 8, 0 }, // Tile size
75 { 0, 1024, 12, 0 }, // Rerender timeout hours
78 VikLayerParam mapnik_layer_params[] = {
79 { VIK_LAYER_MAPNIK, "config-file-mml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CSS (MML) Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_CARTO), NULL,
80 N_("CartoCSS configuration file"), file_default, NULL, NULL },
81 { VIK_LAYER_MAPNIK, "config-file-xml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("XML Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_XML), NULL,
82 N_("Mapnik XML configuration file"), file_default, NULL, NULL },
83 { VIK_LAYER_MAPNIK, "alpha", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Alpha:"), VIK_LAYER_WIDGET_HSCALE, &scales[0], NULL,
84 NULL, alpha_default, NULL, NULL },
85 { VIK_LAYER_MAPNIK, "use-file-cache", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Use File Cache:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL,
86 NULL, vik_lpd_true_default, NULL, NULL },
87 { VIK_LAYER_MAPNIK, "file-cache-dir", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("File Cache Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL,
88 NULL, cache_dir_default, NULL, NULL },
99 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
100 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
101 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
102 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
103 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
104 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
105 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
106 static void mapnik_layer_free ( VikMapnikLayer *vml );
107 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
108 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
110 static gpointer mapnik_feature_create ( VikWindow *vw, VikViewport *vvp)
115 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp );
117 // See comment in viktrwlayer.c for advice on values used
119 static VikToolInterface mapnik_tools[] = {
122 { { "MapnikFeatures", GTK_STOCK_INFO, N_("_Mapnik Features"), NULL, N_("Mapnik Features"), 0 },
123 (VikToolConstructorFunc) mapnik_feature_create,
129 (VikToolMouseFunc) mapnik_feature_release,
132 GDK_LEFT_PTR, NULL, NULL },
135 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
137 VikLayerInterface vik_mapnik_layer_interface = {
139 N_("Mapnik Rendering"),
141 &vikmapniklayer_pixbuf, // icon
144 sizeof(mapnik_tools) / sizeof(VikToolInterface),
153 (VikLayerFuncCreate) mapnik_layer_create,
154 (VikLayerFuncRealize) NULL,
155 (VikLayerFuncPostRead) mapnik_layer_post_read,
156 (VikLayerFuncFree) mapnik_layer_free,
158 (VikLayerFuncProperties) NULL,
159 (VikLayerFuncDraw) mapnik_layer_draw,
160 (VikLayerFuncChangeCoordMode) NULL,
162 (VikLayerFuncGetTimestamp) NULL,
164 (VikLayerFuncSetMenuItemsSelection) NULL,
165 (VikLayerFuncGetMenuItemsSelection) NULL,
167 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
168 (VikLayerFuncSublayerAddMenuItems) NULL,
170 (VikLayerFuncSublayerRenameRequest) NULL,
171 (VikLayerFuncSublayerToggleVisible) NULL,
172 (VikLayerFuncSublayerTooltip) NULL,
173 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
174 (VikLayerFuncLayerSelected) NULL,
176 (VikLayerFuncMarshall) mapnik_layer_marshall,
177 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
179 (VikLayerFuncSetParam) mapnik_layer_set_param,
180 (VikLayerFuncGetParam) mapnik_layer_get_param,
181 (VikLayerFuncChangeParam) NULL,
183 (VikLayerFuncReadFileData) NULL,
184 (VikLayerFuncWriteFileData) NULL,
186 (VikLayerFuncDeleteItem) NULL,
187 (VikLayerFuncCutItem) NULL,
188 (VikLayerFuncCopyItem) NULL,
189 (VikLayerFuncPasteItem) NULL,
190 (VikLayerFuncFreeCopiedItem) NULL,
191 (VikLayerFuncDragDropRequest) NULL,
193 (VikLayerFuncSelectClick) NULL,
194 (VikLayerFuncSelectMove) NULL,
195 (VikLayerFuncSelectRelease) NULL,
196 (VikLayerFuncSelectedViewportMenu) NULL,
199 struct _VikMapnikLayer {
201 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
205 guint tile_size_x; // Y is the same as X ATM
208 guint rerender_timeout;
210 gboolean use_file_cache;
211 gchar *file_cache_dir;
213 VikCoord rerender_ul;
214 VikCoord rerender_br;
215 gdouble rerender_zoom;
216 GtkWidget *right_click_menu;
219 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
220 #define MAPNIK_PREFS_NAMESPACE "mapnik."
222 static VikLayerParamData plugins_default ( void )
224 VikLayerParamData data;
226 data.s = g_strdup ( "input" );
228 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
229 data.s = g_strdup ( "/usr/lib/mapnik/input" );
230 // Current Debian locations
231 else if ( g_file_test ( "/usr/lib/mapnik/3.0/input", G_FILE_TEST_EXISTS ) )
232 data.s = g_strdup ( "/usr/lib/mapnik/3.0/input" );
233 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
234 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
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 * Just 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);
297 * vik_mapnik_layer_post_init:
299 * Initialize data structures - now that reading preferences is OK to perform
301 void vik_mapnik_layer_post_init (void)
303 tp_mutex = vik_mutex_new();
305 // Just storing keys only
306 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
308 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
309 GDateTime *now = g_date_time_new_now_local ();
310 GDateTime *then = g_date_time_add_hours (now, -hours);
311 planet_import_time = g_date_time_to_unix (then);
312 g_date_time_unref ( now );
313 g_date_time_unref ( then );
316 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
317 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
318 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
319 // Only update if newer
320 if ( planet_import_time > gsb.st_mtime )
321 planet_import_time = gsb.st_mtime;
323 g_free ( import_time_file );
326 void vik_mapnik_layer_uninit ()
328 vik_mutex_free (tp_mutex);
331 // NB Only performed once per program run
332 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
334 VikLayerParamData *pd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory");
335 VikLayerParamData *fd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory");
336 VikLayerParamData *rfd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory");
338 if ( pd && fd && rfd )
339 mapnik_interface_initialize ( pd->s, fd->s, rfd->b );
341 g_critical ( "Unable to initialize mapnik interface from preferences" );
344 GType vik_mapnik_layer_get_type ()
346 static GType vml_type = 0;
349 static const GTypeInfo vml_info = {
350 sizeof (VikMapnikLayerClass),
351 NULL, /* base_init */
352 NULL, /* base_finalize */
353 (GClassInitFunc) mapnik_layer_class_init, /* class init */
354 NULL, /* class_finalize */
355 NULL, /* class_data */
356 sizeof (VikMapnikLayer),
358 NULL /* instance init */
360 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
366 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
368 return vml->filename_xml;
371 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
373 if ( vml->filename_xml )
374 g_free (vml->filename_xml);
375 // Mapnik doesn't seem to cope with relative filenames
376 if ( g_strcmp0 (name, "" ) )
377 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
379 vml->filename_xml = g_strdup (name);
382 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
384 if ( vml->filename_css )
385 g_free (vml->filename_css);
386 vml->filename_css = g_strdup (name);
389 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
391 if ( vml->file_cache_dir )
392 g_free (vml->file_cache_dir);
393 vml->file_cache_dir = g_strdup (name);
396 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
398 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
401 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
403 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
404 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
408 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
411 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
412 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
413 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
414 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
415 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
421 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
423 VikLayerParamData data;
425 case PARAM_CONFIG_CSS: {
426 data.s = vml->filename_css;
427 gboolean set = FALSE;
428 if ( is_file_operation ) {
429 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
430 gchar *cwd = g_get_current_dir();
432 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
433 if ( !data.s ) data.s = "";
439 data.s = vml->filename_css ? vml->filename_css : "";
442 case PARAM_CONFIG_XML: {
443 data.s = vml->filename_xml;
444 gboolean set = FALSE;
445 if ( is_file_operation ) {
446 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
447 gchar *cwd = g_get_current_dir();
449 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
450 if ( !data.s ) data.s = "";
456 data.s = vml->filename_xml ? vml->filename_xml : "";
459 case PARAM_ALPHA: data.u = vml->alpha; break;
460 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
461 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
470 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
472 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
473 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
474 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
475 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
477 vml->mi = mapnik_interface_new();
483 * ATM don't have any version issues AFAIK
484 * Tested with carto 0.14.0
486 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
488 gchar *mystdout = NULL;
489 gchar *mystderr = NULL;
490 GError *error = NULL;
492 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
493 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
495 gboolean answer = TRUE;
496 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
498 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
499 // cf code in babel.c to handle stdout
501 // NB Running carto may take several seconds
502 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
503 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
505 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
506 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
507 vik_window_set_busy_cursor ( vw );
513 // You won't get a sensible timing measurement if running too old a GLIB
514 #if GLIB_CHECK_VERSION (2, 28, 0)
515 tt1 = g_get_real_time ();
518 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
519 #if GLIB_CHECK_VERSION (2, 28, 0)
520 tt2 = g_get_real_time ();
523 if ( strlen(mystderr) > 1 ) {
524 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
528 // NB This will overwrite the specified XML file
529 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
530 // XML Not specified so try to create based on CSS file name
531 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
533 g_critical ("%s: %s", __FUNCTION__, error->message );
534 if ( vml->filename_xml )
535 g_free (vml->filename_xml);
536 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
538 g_warning ("%s: %s", __FUNCTION__, error->message );
539 // Prevent overwriting self
540 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
541 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
543 g_regex_unref ( regex );
545 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
546 g_warning ("%s: %s", __FUNCTION__, error->message );
547 g_error_free (error);
554 g_warning ("%s: %s", __FUNCTION__, error->message );
555 g_error_free (error);
560 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
561 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
563 vik_window_clear_busy_cursor ( vw );
571 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
573 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
575 // Determine if carto needs to be run
576 gboolean do_carto = FALSE;
577 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
578 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
579 // Compare timestamps
581 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
583 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
584 // Is CSS file newer than the XML file
585 if ( gsb2.st_mtime > gsb1.st_mtime )
588 g_debug ( "No need to run carto" );
592 // XML file doesn't exist
597 // No XML specified thus need to generate
603 // Don't load the XML config if carto load fails
604 if ( !carto_load ( vml, vvp ) )
607 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
609 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
610 _("Mapnik error loading configuration file:\n%s"),
617 ui_add_recent_file ( vml->filename_xml );
621 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
623 // Free returned string after use
624 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
626 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
629 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
631 if ( vml->use_file_cache ) {
632 if ( vml->file_cache_dir ) {
633 GError *error = NULL;
634 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
636 gchar *dir = g_path_get_dirname ( filename );
637 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
638 if ( g_mkdir_with_parents ( dir , 0777 ) != 0 )
639 g_warning ("%s: Failed to mkdir %s", __FUNCTION__, dir );
642 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
643 g_warning ("%s: %s", __FUNCTION__, error->message );
644 g_error_free (error);
657 const gchar* request;
663 * Common render function which can run in separate thread
665 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
667 gint64 tt1 = g_get_real_time ();
668 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
669 gint64 tt2 = g_get_real_time ();
670 gdouble tt = (gdouble)(tt2-tt1)/1000000;
671 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
673 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
674 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
676 possibly_save_pixbuf ( vml, pixbuf, ulm );
678 // NB Mapnik can apply alpha, but use our own function for now
679 if ( vml->alpha < 255 )
680 pixbuf = ui_pixbuf_scale_alpha ( pixbuf, vml->alpha );
681 a_mapcache_add ( pixbuf, (mapcache_extra_t){ tt }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
682 g_object_unref(pixbuf);
685 static void render_info_free ( RenderInfo *data )
689 g_free ( data->ulmc );
690 // NB No need to free the request/key - as this is freed by the hash table destructor
694 static void background ( RenderInfo *data, gpointer threaddata )
696 int res = a_background_thread_progress ( threaddata, 0 );
698 render ( data->vml, data->ul, data->br, data->ulmc );
701 g_mutex_lock(tp_mutex);
702 g_hash_table_remove (requests, data->request);
703 g_mutex_unlock(tp_mutex);
706 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
709 static void render_cancel_cleanup (RenderInfo *data)
714 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
719 static void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
722 guint nn = name ? g_str_hash ( name ) : 0;
723 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
725 g_mutex_lock(tp_mutex);
727 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
729 g_mutex_unlock (tp_mutex);
733 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
735 ri->ul = g_malloc ( sizeof(VikCoord) );
736 ri->br = g_malloc ( sizeof(VikCoord) );
737 ri->ulmc = g_malloc ( sizeof(MapCoord) );
738 memcpy(ri->ul, ul, sizeof(VikCoord));
739 memcpy(ri->br, br, sizeof(VikCoord));
740 memcpy(ri->ulmc, mul, sizeof(MapCoord));
741 ri->request = request;
743 g_hash_table_insert ( requests, request, NULL );
745 g_mutex_unlock (tp_mutex);
747 gchar *basename = g_path_get_basename (name);
748 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
750 a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
751 VIK_GTK_WINDOW_FROM_LAYER(vml),
753 (vik_thr_func) background,
755 (vik_thr_free_func) render_info_free,
756 (vik_thr_free_func) render_cancel_cleanup,
758 g_free ( description );
764 * If function returns GdkPixbuf properly, reference counter to this
765 * buffer has to be decreased, when buffer is no longer needed.
767 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
770 GdkPixbuf *pixbuf = NULL;
771 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
774 if ( g_stat ( filename, &gsb ) == 0 ) {
776 GError *error = NULL;
777 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
779 g_warning ("%s: %s", __FUNCTION__, error->message );
780 g_error_free ( error );
783 if ( vml->alpha < 255 )
784 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
785 a_mapcache_add ( pixbuf, (mapcache_extra_t) { -42.0 }, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
787 // If file is too old mark for rerendering
788 if ( planet_import_time < gsb.st_mtime ) {
798 * Caller has to decrease reference counter of returned
799 * GdkPixbuf, when buffer is no longer needed.
801 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
803 VikCoord ul; VikCoord br;
804 GdkPixbuf *pixbuf = NULL;
806 map_utils_iTMS_to_vikcoord (ulm, &ul);
807 map_utils_iTMS_to_vikcoord (brm, &br);
809 pixbuf = a_mapcache_get ( ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
812 gboolean rerender = FALSE;
813 if ( vml->use_file_cache && vml->file_cache_dir )
814 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
815 if ( ! pixbuf || rerender ) {
817 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
819 // Run in the foreground
820 render ( vml, &ul, &br, ulm );
821 vik_layer_emit_update ( VIK_LAYER(vml) );
832 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
837 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
838 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
839 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
844 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
846 vik_viewport_add_copyright ( vvp, copyright );
851 ul.mode = VIK_COORD_LATLON;
852 br.mode = VIK_COORD_LATLON;
853 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
854 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
856 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
857 gdouble yzoom = vik_viewport_get_ympp ( vvp );
861 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
862 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
863 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
868 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
869 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
871 // Split rendering into a grid for the current viewport
872 // thus each individual 'tile' can then be stored in the map cache
873 for (gint x = xmin; x <= xmax; x++ ) {
874 for (gint y = ymin; y <= ymax; y++ ) {
880 pixbuf = get_pixbuf ( vml, &ulm, &brm );
883 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
884 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
885 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
886 g_object_unref(pixbuf);
891 // Done after so drawn on top
892 // Just a handy guide to tile blocks.
893 if ( vik_debug && vik_verbose ) {
894 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
895 gint width = vik_viewport_get_width(vvp);
896 gint height = vik_viewport_get_height(vvp);
898 ulm.x = xmin; ulm.y = ymin;
899 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
900 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
901 xx = xx - (vml->tile_size_x/2);
902 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
903 for (gint x = xmin; x <= xmax; x++ ) {
904 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
905 xx += vml->tile_size_x;
907 for (gint y = ymin; y <= ymax; y++ ) {
908 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
909 yy += vml->tile_size_x; // Yes use X ATM
918 static void mapnik_layer_free ( VikMapnikLayer *vml )
920 mapnik_interface_free ( vml->mi );
921 if ( vml->filename_css )
922 g_free ( vml->filename_css );
923 if ( vml->filename_xml )
924 g_free ( vml->filename_xml );
927 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
929 return mapnik_layer_new ( vp );
938 typedef gpointer menu_array_values[MA_LAST];
943 static void mapnik_layer_flush_memory ( menu_array_values values )
945 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
951 static void mapnik_layer_reload ( menu_array_values values )
953 VikMapnikLayer *vml = values[MA_VML];
954 VikViewport *vvp = values[MA_VVP];
955 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
956 mapnik_layer_draw ( vml, vvp );
962 * Most carto projects will consist of many files
963 * ATM don't have a way of detecting when any of the included files have changed
964 * Thus allow a manual method to force re-running carto
966 static void mapnik_layer_carto ( menu_array_values values )
968 VikMapnikLayer *vml = values[MA_VML];
969 VikViewport *vvp = values[MA_VVP];
971 // Don't load the XML config if carto load fails
972 if ( !carto_load ( vml, vvp ) )
975 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
977 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
978 _("Mapnik error loading configuration file:\n%s"),
983 mapnik_layer_draw ( vml, vvp );
987 * Show Mapnik configuration parameters
989 static void mapnik_layer_information ( menu_array_values values )
991 VikMapnikLayer *vml = values[MA_VML];
994 GArray *array = mapnik_interface_get_parameters( vml->mi );
996 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
997 // Free the copied strings
998 for ( int i = 0; i < array->len; i++ )
999 g_free ( g_array_index(array,gchar*,i) );
1001 g_array_free ( array, FALSE );
1007 static void mapnik_layer_about ( menu_array_values values )
1009 VikMapnikLayer *vml = values[MA_VML];
1010 gchar *msg = mapnik_interface_about();
1011 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
1018 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
1020 static menu_array_values values;
1021 values[MA_VML] = vml;
1022 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1024 GtkWidget *item = gtk_menu_item_new();
1025 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1026 gtk_widget_show ( item );
1028 // Typical users shouldn't need to use this functionality - so debug only ATM
1030 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1031 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1032 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1033 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1034 gtk_widget_show ( item );
1037 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1038 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1039 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1040 gtk_widget_show ( item );
1042 if ( g_strcmp0 ("", vml->filename_css) ) {
1043 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1044 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1045 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1046 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1047 gtk_widget_show ( item );
1050 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1051 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1052 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1053 gtk_widget_show ( item );
1055 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1056 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1057 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1058 gtk_widget_show ( item );
1062 * Rerender a specific tile
1064 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1067 // Requested position to map coord
1068 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1069 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1070 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1071 // Bottom right bound is simply +1 in TMS coords
1075 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1076 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1082 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1085 // Requested position to map coord
1086 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1088 mapcache_extra_t extra = a_mapcache_get_extra ( ulm.x, ulm.y, ulm.z, MAP_ID_MAPNIK_RENDER, ulm.scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
1090 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1091 gchar *filemsg = NULL;
1092 gchar *timemsg = NULL;
1094 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1095 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1096 // Get some timestamp information of the tile
1098 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1100 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1101 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1104 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1108 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1109 timemsg = g_strdup("");
1112 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1113 g_array_append_val ( array, filemsg );
1114 g_array_append_val ( array, timemsg );
1116 gchar *rendmsg = NULL;
1118 if ( extra.duration > 0.0 ) {
1119 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1120 g_array_append_val ( array, rendmsg );
1123 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1124 g_array_free ( array, FALSE );
1129 g_free ( filename );
1132 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1136 if ( event->button == 3 ) {
1137 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1138 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1140 if ( ! vml->right_click_menu ) {
1142 vml->right_click_menu = gtk_menu_new ();
1144 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1145 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1146 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1147 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1149 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1150 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1151 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1152 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1155 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1156 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );