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 (VikLayerFuncGetTimestamp) NULL,
168 (VikLayerFuncSetMenuItemsSelection) NULL,
169 (VikLayerFuncGetMenuItemsSelection) NULL,
171 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
172 (VikLayerFuncSublayerAddMenuItems) NULL,
174 (VikLayerFuncSublayerRenameRequest) NULL,
175 (VikLayerFuncSublayerToggleVisible) NULL,
176 (VikLayerFuncSublayerTooltip) NULL,
177 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
178 (VikLayerFuncLayerSelected) NULL,
180 (VikLayerFuncMarshall) mapnik_layer_marshall,
181 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
183 (VikLayerFuncSetParam) mapnik_layer_set_param,
184 (VikLayerFuncGetParam) mapnik_layer_get_param,
185 (VikLayerFuncChangeParam) NULL,
187 (VikLayerFuncReadFileData) NULL,
188 (VikLayerFuncWriteFileData) NULL,
190 (VikLayerFuncDeleteItem) NULL,
191 (VikLayerFuncCutItem) NULL,
192 (VikLayerFuncCopyItem) NULL,
193 (VikLayerFuncPasteItem) NULL,
194 (VikLayerFuncFreeCopiedItem) NULL,
195 (VikLayerFuncDragDropRequest) NULL,
197 (VikLayerFuncSelectClick) NULL,
198 (VikLayerFuncSelectMove) NULL,
199 (VikLayerFuncSelectRelease) NULL,
200 (VikLayerFuncSelectedViewportMenu) NULL,
203 struct _VikMapnikLayer {
205 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
209 guint tile_size_x; // Y is the same as X ATM
212 guint rerender_timeout;
214 gboolean use_file_cache;
215 gchar *file_cache_dir;
217 VikCoord rerender_ul;
218 VikCoord rerender_br;
219 gdouble rerender_zoom;
220 GtkWidget *right_click_menu;
223 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
224 #define MAPNIK_PREFS_NAMESPACE "mapnik."
226 static VikLayerParamData plugins_default ( void )
228 VikLayerParamData data;
230 data.s = g_strdup ( "input" );
232 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
233 data.s = g_strdup ( "/usr/lib/mapnik/input" );
234 // Current Debian locations
235 else if ( g_file_test ( "/usr/lib/mapnik/3.0/input", G_FILE_TEST_EXISTS ) )
236 data.s = g_strdup ( "/usr/lib/mapnik/3.0/input" );
237 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
238 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
240 data.s = g_strdup ( "" );
245 static VikLayerParamData fonts_default ( void )
247 // Possibly should be string list to allow loading from multiple directories
248 VikLayerParamData data;
250 data.s = g_strdup ( "C:\\Windows\\Fonts" );
251 #elif defined __APPLE__
252 data.s = g_strdup ( "/Library/Fonts" );
254 data.s = g_strdup ( "/usr/share/fonts" );
259 static VikLayerParam prefs[] = {
260 // Changing these values only applies before first mapnik layer is 'created'
261 { 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 },
262 { 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 },
263 { 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 },
264 { 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 },
265 // Changeable any time
266 { 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 },
269 static time_t planet_import_time;
271 static GMutex *tp_mutex;
272 static GHashTable *requests = NULL;
275 * vik_mapnik_layer_init:
277 * Just initialize preferences
279 void vik_mapnik_layer_init (void)
281 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
284 VikLayerParamData tmp = plugins_default();
285 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
287 tmp = fonts_default();
288 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
291 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
293 tmp.u = 168; // One week
294 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
297 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
301 * vik_mapnik_layer_post_init:
303 * Initialize data structures - now that reading preferences is OK to perform
305 void vik_mapnik_layer_post_init (void)
307 tp_mutex = vik_mutex_new();
309 // Just storing keys only
310 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
312 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
313 GDateTime *now = g_date_time_new_now_local ();
314 GDateTime *then = g_date_time_add_hours (now, -hours);
315 planet_import_time = g_date_time_to_unix (then);
316 g_date_time_unref ( now );
317 g_date_time_unref ( then );
320 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
321 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
322 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
323 // Only update if newer
324 if ( planet_import_time > gsb.st_mtime )
325 planet_import_time = gsb.st_mtime;
327 g_free ( import_time_file );
330 void vik_mapnik_layer_uninit ()
332 vik_mutex_free (tp_mutex);
335 // NB Only performed once per program run
336 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
338 VikLayerParamData *pd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory");
339 VikLayerParamData *fd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory");
340 VikLayerParamData *rfd = a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory");
342 if ( pd && fd && rfd )
343 mapnik_interface_initialize ( pd->s, fd->s, rfd->b );
345 g_critical ( "Unable to initialize mapnik interface from preferences" );
348 GType vik_mapnik_layer_get_type ()
350 static GType vml_type = 0;
353 static const GTypeInfo vml_info = {
354 sizeof (VikMapnikLayerClass),
355 NULL, /* base_init */
356 NULL, /* base_finalize */
357 (GClassInitFunc) mapnik_layer_class_init, /* class init */
358 NULL, /* class_finalize */
359 NULL, /* class_data */
360 sizeof (VikMapnikLayer),
362 NULL /* instance init */
364 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
370 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
372 return vml->filename_xml;
375 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
377 if ( vml->filename_xml )
378 g_free (vml->filename_xml);
379 // Mapnik doesn't seem to cope with relative filenames
380 if ( g_strcmp0 (name, "" ) )
381 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
383 vml->filename_xml = g_strdup (name);
386 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
388 if ( vml->filename_css )
389 g_free (vml->filename_css);
390 vml->filename_css = g_strdup (name);
393 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
395 if ( vml->file_cache_dir )
396 g_free (vml->file_cache_dir);
397 vml->file_cache_dir = g_strdup (name);
400 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
402 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
405 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
407 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
408 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
412 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
415 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
416 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
417 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
418 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
419 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
425 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
427 VikLayerParamData data;
429 case PARAM_CONFIG_CSS: {
430 data.s = vml->filename_css;
431 gboolean set = FALSE;
432 if ( is_file_operation ) {
433 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
434 gchar *cwd = g_get_current_dir();
436 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
437 if ( !data.s ) data.s = "";
443 data.s = vml->filename_css ? vml->filename_css : "";
446 case PARAM_CONFIG_XML: {
447 data.s = vml->filename_xml;
448 gboolean set = FALSE;
449 if ( is_file_operation ) {
450 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
451 gchar *cwd = g_get_current_dir();
453 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
454 if ( !data.s ) data.s = "";
460 data.s = vml->filename_xml ? vml->filename_xml : "";
463 case PARAM_ALPHA: data.u = vml->alpha; break;
464 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
465 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
474 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
476 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
477 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
478 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
479 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
481 vml->mi = mapnik_interface_new();
487 * ATM don't have any version issues AFAIK
488 * Tested with carto 0.14.0
490 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
492 gchar *mystdout = NULL;
493 gchar *mystderr = NULL;
494 GError *error = NULL;
496 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
497 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
499 gboolean answer = TRUE;
500 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
502 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
503 // cf code in babel.c to handle stdout
505 // NB Running carto may take several seconds
506 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
507 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
509 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
510 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
511 vik_window_set_busy_cursor ( vw );
516 // You won't get a sensible timing measurement if running too old a GLIB
517 #if GLIB_CHECK_VERSION (2, 28, 0)
518 tt1 = g_get_real_time ();
521 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
522 #if GLIB_CHECK_VERSION (2, 28, 0)
523 tt2 = g_get_real_time ();
526 if ( strlen(mystderr) > 1 ) {
527 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
531 // NB This will overwrite the specified XML file
532 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
533 // XML Not specified so try to create based on CSS file name
534 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
536 g_critical ("%s: %s", __FUNCTION__, error->message );
537 if ( vml->filename_xml )
538 g_free (vml->filename_xml);
539 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
541 g_warning ("%s: %s", __FUNCTION__, error->message );
542 // Prevent overwriting self
543 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
544 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
547 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
548 g_warning ("%s: %s", __FUNCTION__, error->message );
549 g_error_free (error);
556 g_warning ("%s: %s", __FUNCTION__, error->message );
557 g_error_free (error);
562 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
563 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
565 vik_window_clear_busy_cursor ( vw );
573 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
575 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
577 // Determine if carto needs to be run
578 gboolean do_carto = FALSE;
579 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
580 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
581 // Compare timestamps
583 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
585 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
586 // Is CSS file newer than the XML file
587 if ( gsb2.st_mtime > gsb1.st_mtime )
590 g_debug ( "No need to run carto" );
594 // XML file doesn't exist
599 // No XML specified thus need to generate
605 // Don't load the XML config if carto load fails
606 if ( !carto_load ( vml, vvp ) )
609 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
611 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
612 _("Mapnik error loading configuration file:\n%s"),
619 ui_add_recent_file ( vml->filename_xml );
623 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
625 // Free returned string after use
626 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
628 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
631 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
633 if ( vml->use_file_cache ) {
634 if ( vml->file_cache_dir ) {
635 GError *error = NULL;
636 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
638 gchar *dir = g_path_get_dirname ( filename );
639 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
640 if ( g_mkdir_with_parents ( dir , 0777 ) != 0 )
641 g_warning ("%s: Failed to mkdir %s", __FUNCTION__, dir );
644 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
645 g_warning ("%s: %s", __FUNCTION__, error->message );
646 g_error_free (error);
659 const gchar* request;
665 * Common render function which can run in separate thread
667 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
669 gint64 tt1 = g_get_real_time ();
670 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
671 gint64 tt2 = g_get_real_time ();
672 gdouble tt = (gdouble)(tt2-tt1)/1000000;
673 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
675 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
676 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
678 possibly_save_pixbuf ( vml, pixbuf, ulm );
680 // NB Mapnik can apply alpha, but use our own function for now
681 if ( vml->alpha < 255 )
682 pixbuf = ui_pixbuf_scale_alpha ( pixbuf, vml->alpha );
683 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 );
684 g_object_unref(pixbuf);
687 static void render_info_free ( RenderInfo *data )
691 g_free ( data->ulmc );
692 // NB No need to free the request/key - as this is freed by the hash table destructor
696 static void background ( RenderInfo *data, gpointer threaddata )
698 int res = a_background_thread_progress ( threaddata, 0 );
700 render ( data->vml, data->ul, data->br, data->ulmc );
703 g_mutex_lock(tp_mutex);
704 g_hash_table_remove (requests, data->request);
705 g_mutex_unlock(tp_mutex);
708 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
711 static void render_cancel_cleanup (RenderInfo *data)
716 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
721 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
724 guint nn = name ? g_str_hash ( name ) : 0;
725 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
727 g_mutex_lock(tp_mutex);
729 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
731 g_mutex_unlock (tp_mutex);
735 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
737 ri->ul = g_malloc ( sizeof(VikCoord) );
738 ri->br = g_malloc ( sizeof(VikCoord) );
739 ri->ulmc = g_malloc ( sizeof(MapCoord) );
740 memcpy(ri->ul, ul, sizeof(VikCoord));
741 memcpy(ri->br, br, sizeof(VikCoord));
742 memcpy(ri->ulmc, mul, sizeof(MapCoord));
743 ri->request = request;
745 g_hash_table_insert ( requests, request, NULL );
747 g_mutex_unlock (tp_mutex);
749 gchar *basename = g_path_get_basename (name);
750 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
752 a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
753 VIK_GTK_WINDOW_FROM_LAYER(vml),
755 (vik_thr_func) background,
757 (vik_thr_free_func) render_info_free,
758 (vik_thr_free_func) render_cancel_cleanup,
760 g_free ( description );
766 * If function returns GdkPixbuf properly, reference counter to this
767 * buffer has to be decreased, when buffer is no longer needed.
769 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
772 GdkPixbuf *pixbuf = NULL;
773 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
776 if ( g_stat ( filename, &gsb ) == 0 ) {
778 GError *error = NULL;
779 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
781 g_warning ("%s: %s", __FUNCTION__, error->message );
782 g_error_free ( error );
785 if ( vml->alpha < 255 )
786 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
787 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 );
789 // If file is too old mark for rerendering
790 if ( planet_import_time < gsb.st_mtime ) {
800 * Caller has to decrease reference counter of returned
801 * GdkPixbuf, when buffer is no longer needed.
803 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
805 VikCoord ul; VikCoord br;
806 GdkPixbuf *pixbuf = NULL;
808 map_utils_iTMS_to_vikcoord (ulm, &ul);
809 map_utils_iTMS_to_vikcoord (brm, &br);
811 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 );
814 gboolean rerender = FALSE;
815 if ( vml->use_file_cache && vml->file_cache_dir )
816 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
817 if ( ! pixbuf || rerender ) {
819 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
821 // Run in the foreground
822 render ( vml, &ul, &br, ulm );
823 vik_layer_emit_update ( VIK_LAYER(vml) );
834 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
839 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
840 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
841 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
846 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
848 vik_viewport_add_copyright ( vvp, copyright );
853 ul.mode = VIK_COORD_LATLON;
854 br.mode = VIK_COORD_LATLON;
855 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
856 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
858 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
859 gdouble yzoom = vik_viewport_get_ympp ( vvp );
863 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
864 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
865 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
870 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
871 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
873 // Split rendering into a grid for the current viewport
874 // thus each individual 'tile' can then be stored in the map cache
875 for (gint x = xmin; x <= xmax; x++ ) {
876 for (gint y = ymin; y <= ymax; y++ ) {
882 pixbuf = get_pixbuf ( vml, &ulm, &brm );
885 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
886 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
887 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
888 g_object_unref(pixbuf);
893 // Done after so drawn on top
894 // Just a handy guide to tile blocks.
895 if ( vik_debug && vik_verbose ) {
896 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
897 gint width = vik_viewport_get_width(vvp);
898 gint height = vik_viewport_get_height(vvp);
900 ulm.x = xmin; ulm.y = ymin;
901 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
902 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
903 xx = xx - (vml->tile_size_x/2);
904 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
905 for (gint x = xmin; x <= xmax; x++ ) {
906 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
907 xx += vml->tile_size_x;
909 for (gint y = ymin; y <= ymax; y++ ) {
910 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
911 yy += vml->tile_size_x; // Yes use X ATM
920 static void mapnik_layer_free ( VikMapnikLayer *vml )
922 mapnik_interface_free ( vml->mi );
923 if ( vml->filename_css )
924 g_free ( vml->filename_css );
925 if ( vml->filename_xml )
926 g_free ( vml->filename_xml );
929 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
931 return mapnik_layer_new ( vp );
940 typedef gpointer menu_array_values[MA_LAST];
945 static void mapnik_layer_flush_memory ( menu_array_values values )
947 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
953 static void mapnik_layer_reload ( menu_array_values values )
955 VikMapnikLayer *vml = values[MA_VML];
956 VikViewport *vvp = values[MA_VVP];
957 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
958 mapnik_layer_draw ( vml, vvp );
964 * Most carto projects will consist of many files
965 * ATM don't have a way of detecting when any of the included files have changed
966 * Thus allow a manual method to force re-running carto
968 static void mapnik_layer_carto ( menu_array_values values )
970 VikMapnikLayer *vml = values[MA_VML];
971 VikViewport *vvp = values[MA_VVP];
973 // Don't load the XML config if carto load fails
974 if ( !carto_load ( vml, vvp ) )
977 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
979 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
980 _("Mapnik error loading configuration file:\n%s"),
985 mapnik_layer_draw ( vml, vvp );
989 * Show Mapnik configuration parameters
991 static void mapnik_layer_information ( menu_array_values values )
993 VikMapnikLayer *vml = values[MA_VML];
996 GArray *array = mapnik_interface_get_parameters( vml->mi );
998 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
999 // Free the copied strings
1000 for ( int i = 0; i < array->len; i++ )
1001 g_free ( g_array_index(array,gchar*,i) );
1003 g_array_free ( array, FALSE );
1009 static void mapnik_layer_about ( menu_array_values values )
1011 VikMapnikLayer *vml = values[MA_VML];
1012 gchar *msg = mapnik_interface_about();
1013 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
1020 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
1022 static menu_array_values values;
1023 values[MA_VML] = vml;
1024 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1026 GtkWidget *item = gtk_menu_item_new();
1027 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1028 gtk_widget_show ( item );
1030 // Typical users shouldn't need to use this functionality - so debug only ATM
1032 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1033 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1034 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1035 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1036 gtk_widget_show ( item );
1039 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1040 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1041 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1042 gtk_widget_show ( item );
1044 if ( g_strcmp0 ("", vml->filename_css) ) {
1045 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1046 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1047 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1048 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1049 gtk_widget_show ( item );
1052 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1053 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1054 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1055 gtk_widget_show ( item );
1057 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1058 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1059 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1060 gtk_widget_show ( item );
1064 * Rerender a specific tile
1066 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1069 // Requested position to map coord
1070 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1071 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1072 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1073 // Bottom right bound is simply +1 in TMS coords
1077 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1078 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1084 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1087 // Requested position to map coord
1088 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1090 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 );
1092 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1093 gchar *filemsg = NULL;
1094 gchar *timemsg = NULL;
1096 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1097 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1098 // Get some timestamp information of the tile
1099 struct stat stat_buf;
1100 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1102 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1103 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1106 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1110 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1111 timemsg = g_strdup("");
1114 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1115 g_array_append_val ( array, filemsg );
1116 g_array_append_val ( array, timemsg );
1118 gchar *rendmsg = NULL;
1120 if ( extra.duration > 0.0 ) {
1121 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1122 g_array_append_val ( array, rendmsg );
1125 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1126 g_array_free ( array, FALSE );
1131 g_free ( filename );
1134 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1138 if ( event->button == 3 ) {
1139 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1140 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1142 if ( ! vml->right_click_menu ) {
1144 vml->right_click_menu = gtk_menu_new ();
1146 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1147 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1148 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1149 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1151 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1152 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1153 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1154 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1157 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1158 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );