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 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
235 // Current Debian location
236 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
238 data.s = g_strdup ( "" );
243 static VikLayerParamData fonts_default ( void )
245 // Possibly should be string list to allow loading from multiple directories
246 VikLayerParamData data;
248 data.s = g_strdup ( "C:\\Windows\\Fonts" );
249 #elif defined __APPLE__
250 data.s = g_strdup ( "/Library/Fonts" );
252 data.s = g_strdup ( "/usr/share/fonts" );
257 static VikLayerParam prefs[] = {
258 // Changing these values only applies before first mapnik layer is 'created'
259 { 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 },
260 { 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 },
261 { 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 },
262 { 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 },
263 // Changeable any time
264 { 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 },
267 static time_t planet_import_time;
269 static GMutex *tp_mutex;
270 static GHashTable *requests = NULL;
273 * vik_mapnik_layer_init:
275 * Mostly to initialize preferences
277 void vik_mapnik_layer_init (void)
279 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
282 VikLayerParamData tmp = plugins_default();
283 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
285 tmp = fonts_default();
286 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
289 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
291 tmp.u = 168; // One week
292 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
295 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
297 tp_mutex = vik_mutex_new();
299 // Just storing keys only
300 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
302 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
303 GDateTime *now = g_date_time_new_now_local ();
304 GDateTime *then = g_date_time_add_hours (now, -hours);
305 planet_import_time = g_date_time_to_unix (then);
306 g_date_time_unref ( now );
307 g_date_time_unref ( then );
310 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
311 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
312 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
313 // Only update if newer
314 if ( planet_import_time > gsb.st_mtime )
315 planet_import_time = gsb.st_mtime;
317 g_free ( import_time_file );
320 void vik_mapnik_layer_uninit ()
322 vik_mutex_free (tp_mutex);
325 // NB Only performed once per program run
326 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
328 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
329 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
330 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
333 GType vik_mapnik_layer_get_type ()
335 static GType vml_type = 0;
338 static const GTypeInfo vml_info = {
339 sizeof (VikMapnikLayerClass),
340 NULL, /* base_init */
341 NULL, /* base_finalize */
342 (GClassInitFunc) mapnik_layer_class_init, /* class init */
343 NULL, /* class_finalize */
344 NULL, /* class_data */
345 sizeof (VikMapnikLayer),
347 NULL /* instance init */
349 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
355 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
357 return vml->filename_xml;
360 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
362 if ( vml->filename_xml )
363 g_free (vml->filename_xml);
364 // Mapnik doesn't seem to cope with relative filenames
365 if ( g_strcmp0 (name, "" ) )
366 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
368 vml->filename_xml = g_strdup (name);
371 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
373 if ( vml->filename_css )
374 g_free (vml->filename_css);
375 vml->filename_css = g_strdup (name);
378 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
380 if ( vml->file_cache_dir )
381 g_free (vml->file_cache_dir);
382 vml->file_cache_dir = g_strdup (name);
385 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
387 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
390 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
392 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
393 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
397 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
400 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
401 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
402 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
403 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
404 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
410 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
412 VikLayerParamData data;
414 case PARAM_CONFIG_CSS: {
415 data.s = vml->filename_css;
416 gboolean set = FALSE;
417 if ( is_file_operation ) {
418 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
419 gchar *cwd = g_get_current_dir();
421 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
422 if ( !data.s ) data.s = "";
428 data.s = vml->filename_css ? vml->filename_css : "";
431 case PARAM_CONFIG_XML: {
432 data.s = vml->filename_xml;
433 gboolean set = FALSE;
434 if ( is_file_operation ) {
435 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
436 gchar *cwd = g_get_current_dir();
438 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
439 if ( !data.s ) data.s = "";
445 data.s = vml->filename_xml ? vml->filename_xml : "";
448 case PARAM_ALPHA: data.u = vml->alpha; break;
449 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
450 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
459 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
461 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
462 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
463 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
464 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
466 vml->mi = mapnik_interface_new();
472 * ATM don't have any version issues AFAIK
473 * Tested with carto 0.14.0
475 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
477 gchar *mystdout = NULL;
478 gchar *mystderr = NULL;
479 GError *error = NULL;
481 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
482 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
484 gboolean answer = TRUE;
485 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
487 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
488 // cf code in babel.c to handle stdout
490 // NB Running carto may take several seconds
491 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
492 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
494 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
495 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
496 vik_window_set_busy_cursor ( vw );
501 // You won't get a sensible timing measurement if running too old a GLIB
502 #if GLIB_CHECK_VERSION (2, 28, 0)
503 tt1 = g_get_real_time ();
506 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
507 #if GLIB_CHECK_VERSION (2, 28, 0)
508 tt2 = g_get_real_time ();
511 if ( strlen(mystderr) > 1 ) {
512 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
516 // NB This will overwrite the specified XML file
517 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
518 // XML Not specified so try to create based on CSS file name
519 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
521 g_critical ("%s: %s", __FUNCTION__, error->message );
522 if ( vml->filename_xml )
523 g_free (vml->filename_xml);
524 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
526 g_warning ("%s: %s", __FUNCTION__, error->message );
527 // Prevent overwriting self
528 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
529 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
532 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
533 g_warning ("%s: %s", __FUNCTION__, error->message );
534 g_error_free (error);
541 g_warning ("%s: %s", __FUNCTION__, error->message );
542 g_error_free (error);
547 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
548 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
550 vik_window_clear_busy_cursor ( vw );
558 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
560 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
562 // Determine if carto needs to be run
563 gboolean do_carto = FALSE;
564 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
565 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
566 // Compare timestamps
568 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
570 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
571 // Is CSS file newer than the XML file
572 if ( gsb2.st_mtime > gsb1.st_mtime )
575 g_debug ( "No need to run carto" );
579 // XML file doesn't exist
584 // No XML specified thus need to generate
590 // Don't load the XML config if carto load fails
591 if ( !carto_load ( vml, vvp ) )
594 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
596 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
597 _("Mapnik error loading configuration file:\n%s"),
604 ui_add_recent_file ( vml->filename_xml );
608 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
610 // Free returned string after use
611 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
613 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
616 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
618 if ( vml->use_file_cache ) {
619 if ( vml->file_cache_dir ) {
620 GError *error = NULL;
621 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
623 gchar *dir = g_path_get_dirname ( filename );
624 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
625 g_mkdir_with_parents ( dir , 0777 );
628 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
629 g_warning ("%s: %s", __FUNCTION__, error->message );
630 g_error_free (error);
643 const gchar* request;
649 * Common render function which can run in separate thread
651 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
653 gint64 tt1 = g_get_real_time ();
654 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
655 gint64 tt2 = g_get_real_time ();
656 gdouble tt = (gdouble)(tt2-tt1)/1000000;
657 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
659 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
660 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
662 possibly_save_pixbuf ( vml, pixbuf, ulm );
664 // NB Mapnik can apply alpha, but use our own function for now
665 if ( vml->alpha < 255 )
666 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
667 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 );
670 static void render_info_free ( RenderInfo *data )
674 g_free ( data->ulmc );
675 // NB No need to free the request/key - as this is freed by the hash table destructor
679 static void background ( RenderInfo *data, gpointer threaddata )
681 int res = a_background_thread_progress ( threaddata, 0 );
683 render ( data->vml, data->ul, data->br, data->ulmc );
686 g_mutex_lock(tp_mutex);
687 g_hash_table_remove (requests, data->request);
688 g_mutex_unlock(tp_mutex);
691 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
694 static void render_cancel_cleanup (RenderInfo *data)
699 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
704 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
707 guint nn = name ? g_str_hash ( name ) : 0;
708 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
710 g_mutex_lock(tp_mutex);
712 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
714 g_mutex_unlock (tp_mutex);
718 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
720 ri->ul = g_malloc ( sizeof(VikCoord) );
721 ri->br = g_malloc ( sizeof(VikCoord) );
722 ri->ulmc = g_malloc ( sizeof(MapCoord) );
723 memcpy(ri->ul, ul, sizeof(VikCoord));
724 memcpy(ri->br, br, sizeof(VikCoord));
725 memcpy(ri->ulmc, mul, sizeof(MapCoord));
726 ri->request = request;
728 g_hash_table_insert ( requests, request, NULL );
730 g_mutex_unlock (tp_mutex);
732 gchar *basename = g_path_get_basename (name);
733 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
735 a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
736 VIK_GTK_WINDOW_FROM_LAYER(vml),
738 (vik_thr_func) background,
740 (vik_thr_free_func) render_info_free,
741 (vik_thr_free_func) render_cancel_cleanup,
743 g_free ( description );
749 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
752 GdkPixbuf *pixbuf = NULL;
753 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
756 if ( g_stat ( filename, &gsb ) == 0 ) {
758 GError *error = NULL;
759 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
761 g_warning ("%s: %s", __FUNCTION__, error->message );
762 g_error_free ( error );
765 if ( vml->alpha < 255 )
766 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
767 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 );
769 // If file is too old mark for rerendering
770 if ( planet_import_time < gsb.st_mtime ) {
782 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
784 VikCoord ul; VikCoord br;
785 GdkPixbuf *pixbuf = NULL;
787 map_utils_iTMS_to_vikcoord (ulm, &ul);
788 map_utils_iTMS_to_vikcoord (brm, &br);
790 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 );
793 gboolean rerender = FALSE;
794 if ( vml->use_file_cache && vml->file_cache_dir )
795 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
796 if ( ! pixbuf || rerender ) {
798 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
800 // Run in the foreground
801 render ( vml, &ul, &br, ulm );
802 vik_layer_emit_update ( VIK_LAYER(vml) );
813 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
818 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
819 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
820 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
825 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
827 vik_viewport_add_copyright ( vvp, copyright );
832 ul.mode = VIK_COORD_LATLON;
833 br.mode = VIK_COORD_LATLON;
834 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
835 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
837 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
838 gdouble yzoom = vik_viewport_get_ympp ( vvp );
842 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
843 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
844 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
849 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
850 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
852 // Split rendering into a grid for the current viewport
853 // thus each individual 'tile' can then be stored in the map cache
854 for (gint x = xmin; x <= xmax; x++ ) {
855 for (gint y = ymin; y <= ymax; y++ ) {
861 pixbuf = get_pixbuf ( vml, &ulm, &brm );
864 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
865 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
866 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
871 // Done after so drawn on top
872 // Just a handy guide to tile blocks.
873 if ( vik_debug && vik_verbose ) {
874 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
875 gint width = vik_viewport_get_width(vvp);
876 gint height = vik_viewport_get_height(vvp);
878 ulm.x = xmin; ulm.y = ymin;
879 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
880 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
881 xx = xx - (vml->tile_size_x/2);
882 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
883 for (gint x = xmin; x <= xmax; x++ ) {
884 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
885 xx += vml->tile_size_x;
887 for (gint y = ymin; y <= ymax; y++ ) {
888 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
889 yy += vml->tile_size_x; // Yes use X ATM
898 static void mapnik_layer_free ( VikMapnikLayer *vml )
900 mapnik_interface_free ( vml->mi );
901 if ( vml->filename_css )
902 g_free ( vml->filename_css );
903 if ( vml->filename_xml )
904 g_free ( vml->filename_xml );
907 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
909 return mapnik_layer_new ( vp );
918 typedef gpointer menu_array_values[MA_LAST];
923 static void mapnik_layer_flush_memory ( menu_array_values values )
925 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
931 static void mapnik_layer_reload ( menu_array_values values )
933 VikMapnikLayer *vml = values[MA_VML];
934 VikViewport *vvp = values[MA_VVP];
935 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
936 mapnik_layer_draw ( vml, vvp );
942 * Most carto projects will consist of many files
943 * ATM don't have a way of detecting when any of the included files have changed
944 * Thus allow a manual method to force re-running carto
946 static void mapnik_layer_carto ( menu_array_values values )
948 VikMapnikLayer *vml = values[MA_VML];
949 VikViewport *vvp = values[MA_VVP];
951 // Don't load the XML config if carto load fails
952 if ( !carto_load ( vml, vvp ) )
955 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
957 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
958 _("Mapnik error loading configuration file:\n%s"),
963 mapnik_layer_draw ( vml, vvp );
967 * Show Mapnik configuration parameters
969 static void mapnik_layer_information ( menu_array_values values )
971 VikMapnikLayer *vml = values[MA_VML];
974 GArray *array = mapnik_interface_get_parameters( vml->mi );
976 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
977 // Free the copied strings
978 for ( int i = 0; i < array->len; i++ )
979 g_free ( g_array_index(array,gchar*,i) );
981 g_array_free ( array, FALSE );
987 static void mapnik_layer_about ( menu_array_values values )
989 VikMapnikLayer *vml = values[MA_VML];
990 gchar *msg = mapnik_interface_about();
991 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
998 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
1000 static menu_array_values values;
1001 values[MA_VML] = vml;
1002 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1004 GtkWidget *item = gtk_menu_item_new();
1005 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1006 gtk_widget_show ( item );
1008 // Typical users shouldn't need to use this functionality - so debug only ATM
1010 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1011 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1012 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1013 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1014 gtk_widget_show ( item );
1017 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1018 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1019 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1020 gtk_widget_show ( item );
1022 if ( g_strcmp0 ("", vml->filename_css) ) {
1023 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1024 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1025 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1026 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1027 gtk_widget_show ( item );
1030 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1031 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1032 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1033 gtk_widget_show ( item );
1035 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1036 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1037 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1038 gtk_widget_show ( item );
1042 * Rerender a specific tile
1044 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1047 // Requested position to map coord
1048 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1049 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1050 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1051 // Bottom right bound is simply +1 in TMS coords
1055 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1056 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1062 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1065 // Requested position to map coord
1066 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1068 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 );
1070 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1071 gchar *filemsg = NULL;
1072 gchar *timemsg = NULL;
1074 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1075 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1076 // Get some timestamp information of the tile
1077 struct stat stat_buf;
1078 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1080 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1081 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1084 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1088 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1089 timemsg = g_strdup("");
1092 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1093 g_array_append_val ( array, filemsg );
1094 g_array_append_val ( array, timemsg );
1096 gchar *rendmsg = NULL;
1098 if ( extra.duration > 0.0 ) {
1099 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1100 g_array_append_val ( array, rendmsg );
1103 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1104 g_array_free ( array, FALSE );
1109 g_free ( filename );
1112 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1116 if ( event->button == 3 ) {
1117 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1118 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1120 if ( ! vml->right_click_menu ) {
1122 vml->right_click_menu = gtk_menu_new ();
1124 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1125 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1126 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1127 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1129 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1130 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1131 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1132 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1135 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1136 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );