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 * Just 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);
299 * vik_mapnik_layer_post_init:
301 * Initialize data structures - now that reading preferences is OK to perform
303 void vik_mapnik_layer_post_init (void)
305 tp_mutex = vik_mutex_new();
307 // Just storing keys only
308 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
310 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
311 GDateTime *now = g_date_time_new_now_local ();
312 GDateTime *then = g_date_time_add_hours (now, -hours);
313 planet_import_time = g_date_time_to_unix (then);
314 g_date_time_unref ( now );
315 g_date_time_unref ( then );
318 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
319 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
320 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
321 // Only update if newer
322 if ( planet_import_time > gsb.st_mtime )
323 planet_import_time = gsb.st_mtime;
325 g_free ( import_time_file );
328 void vik_mapnik_layer_uninit ()
330 vik_mutex_free (tp_mutex);
333 // NB Only performed once per program run
334 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
336 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
337 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
338 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
341 GType vik_mapnik_layer_get_type ()
343 static GType vml_type = 0;
346 static const GTypeInfo vml_info = {
347 sizeof (VikMapnikLayerClass),
348 NULL, /* base_init */
349 NULL, /* base_finalize */
350 (GClassInitFunc) mapnik_layer_class_init, /* class init */
351 NULL, /* class_finalize */
352 NULL, /* class_data */
353 sizeof (VikMapnikLayer),
355 NULL /* instance init */
357 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
363 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
365 return vml->filename_xml;
368 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
370 if ( vml->filename_xml )
371 g_free (vml->filename_xml);
372 // Mapnik doesn't seem to cope with relative filenames
373 if ( g_strcmp0 (name, "" ) )
374 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
376 vml->filename_xml = g_strdup (name);
379 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
381 if ( vml->filename_css )
382 g_free (vml->filename_css);
383 vml->filename_css = g_strdup (name);
386 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
388 if ( vml->file_cache_dir )
389 g_free (vml->file_cache_dir);
390 vml->file_cache_dir = g_strdup (name);
393 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
395 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
398 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
400 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
401 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
405 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
408 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
409 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
410 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
411 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
412 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
418 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
420 VikLayerParamData data;
422 case PARAM_CONFIG_CSS: {
423 data.s = vml->filename_css;
424 gboolean set = FALSE;
425 if ( is_file_operation ) {
426 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
427 gchar *cwd = g_get_current_dir();
429 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
430 if ( !data.s ) data.s = "";
436 data.s = vml->filename_css ? vml->filename_css : "";
439 case PARAM_CONFIG_XML: {
440 data.s = vml->filename_xml;
441 gboolean set = FALSE;
442 if ( is_file_operation ) {
443 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
444 gchar *cwd = g_get_current_dir();
446 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
447 if ( !data.s ) data.s = "";
453 data.s = vml->filename_xml ? vml->filename_xml : "";
456 case PARAM_ALPHA: data.u = vml->alpha; break;
457 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
458 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
467 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
469 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
470 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
471 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
472 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
474 vml->mi = mapnik_interface_new();
480 * ATM don't have any version issues AFAIK
481 * Tested with carto 0.14.0
483 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
485 gchar *mystdout = NULL;
486 gchar *mystderr = NULL;
487 GError *error = NULL;
489 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
490 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
492 gboolean answer = TRUE;
493 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
495 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
496 // cf code in babel.c to handle stdout
498 // NB Running carto may take several seconds
499 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
500 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
502 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
503 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
504 vik_window_set_busy_cursor ( vw );
509 // You won't get a sensible timing measurement if running too old a GLIB
510 #if GLIB_CHECK_VERSION (2, 28, 0)
511 tt1 = g_get_real_time ();
514 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
515 #if GLIB_CHECK_VERSION (2, 28, 0)
516 tt2 = g_get_real_time ();
519 if ( strlen(mystderr) > 1 ) {
520 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
524 // NB This will overwrite the specified XML file
525 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
526 // XML Not specified so try to create based on CSS file name
527 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
529 g_critical ("%s: %s", __FUNCTION__, error->message );
530 if ( vml->filename_xml )
531 g_free (vml->filename_xml);
532 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
534 g_warning ("%s: %s", __FUNCTION__, error->message );
535 // Prevent overwriting self
536 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
537 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
540 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
541 g_warning ("%s: %s", __FUNCTION__, error->message );
542 g_error_free (error);
549 g_warning ("%s: %s", __FUNCTION__, error->message );
550 g_error_free (error);
555 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
556 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
558 vik_window_clear_busy_cursor ( vw );
566 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
568 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
570 // Determine if carto needs to be run
571 gboolean do_carto = FALSE;
572 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
573 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
574 // Compare timestamps
576 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
578 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
579 // Is CSS file newer than the XML file
580 if ( gsb2.st_mtime > gsb1.st_mtime )
583 g_debug ( "No need to run carto" );
587 // XML file doesn't exist
592 // No XML specified thus need to generate
598 // Don't load the XML config if carto load fails
599 if ( !carto_load ( vml, vvp ) )
602 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
604 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
605 _("Mapnik error loading configuration file:\n%s"),
612 ui_add_recent_file ( vml->filename_xml );
616 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
618 // Free returned string after use
619 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
621 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
624 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
626 if ( vml->use_file_cache ) {
627 if ( vml->file_cache_dir ) {
628 GError *error = NULL;
629 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
631 gchar *dir = g_path_get_dirname ( filename );
632 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
633 g_mkdir_with_parents ( dir , 0777 );
636 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
637 g_warning ("%s: %s", __FUNCTION__, error->message );
638 g_error_free (error);
651 const gchar* request;
657 * Common render function which can run in separate thread
659 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
661 gint64 tt1 = g_get_real_time ();
662 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
663 gint64 tt2 = g_get_real_time ();
664 gdouble tt = (gdouble)(tt2-tt1)/1000000;
665 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
667 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
668 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
670 possibly_save_pixbuf ( vml, pixbuf, ulm );
672 // NB Mapnik can apply alpha, but use our own function for now
673 if ( vml->alpha < 255 )
674 pixbuf = ui_pixbuf_scale_alpha ( pixbuf, vml->alpha );
675 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 );
678 static void render_info_free ( RenderInfo *data )
682 g_free ( data->ulmc );
683 // NB No need to free the request/key - as this is freed by the hash table destructor
687 static void background ( RenderInfo *data, gpointer threaddata )
689 int res = a_background_thread_progress ( threaddata, 0 );
691 render ( data->vml, data->ul, data->br, data->ulmc );
694 g_mutex_lock(tp_mutex);
695 g_hash_table_remove (requests, data->request);
696 g_mutex_unlock(tp_mutex);
699 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
702 static void render_cancel_cleanup (RenderInfo *data)
707 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
712 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
715 guint nn = name ? g_str_hash ( name ) : 0;
716 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
718 g_mutex_lock(tp_mutex);
720 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
722 g_mutex_unlock (tp_mutex);
726 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
728 ri->ul = g_malloc ( sizeof(VikCoord) );
729 ri->br = g_malloc ( sizeof(VikCoord) );
730 ri->ulmc = g_malloc ( sizeof(MapCoord) );
731 memcpy(ri->ul, ul, sizeof(VikCoord));
732 memcpy(ri->br, br, sizeof(VikCoord));
733 memcpy(ri->ulmc, mul, sizeof(MapCoord));
734 ri->request = request;
736 g_hash_table_insert ( requests, request, NULL );
738 g_mutex_unlock (tp_mutex);
740 gchar *basename = g_path_get_basename (name);
741 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
743 a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
744 VIK_GTK_WINDOW_FROM_LAYER(vml),
746 (vik_thr_func) background,
748 (vik_thr_free_func) render_info_free,
749 (vik_thr_free_func) render_cancel_cleanup,
751 g_free ( description );
757 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
760 GdkPixbuf *pixbuf = NULL;
761 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
764 if ( g_stat ( filename, &gsb ) == 0 ) {
766 GError *error = NULL;
767 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
769 g_warning ("%s: %s", __FUNCTION__, error->message );
770 g_error_free ( error );
773 if ( vml->alpha < 255 )
774 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
775 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 );
777 // If file is too old mark for rerendering
778 if ( planet_import_time < gsb.st_mtime ) {
790 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
792 VikCoord ul; VikCoord br;
793 GdkPixbuf *pixbuf = NULL;
795 map_utils_iTMS_to_vikcoord (ulm, &ul);
796 map_utils_iTMS_to_vikcoord (brm, &br);
798 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 );
801 gboolean rerender = FALSE;
802 if ( vml->use_file_cache && vml->file_cache_dir )
803 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
804 if ( ! pixbuf || rerender ) {
806 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
808 // Run in the foreground
809 render ( vml, &ul, &br, ulm );
810 vik_layer_emit_update ( VIK_LAYER(vml) );
821 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
826 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
827 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
828 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
833 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
835 vik_viewport_add_copyright ( vvp, copyright );
840 ul.mode = VIK_COORD_LATLON;
841 br.mode = VIK_COORD_LATLON;
842 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
843 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
845 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
846 gdouble yzoom = vik_viewport_get_ympp ( vvp );
850 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
851 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
852 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
857 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
858 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
860 // Split rendering into a grid for the current viewport
861 // thus each individual 'tile' can then be stored in the map cache
862 for (gint x = xmin; x <= xmax; x++ ) {
863 for (gint y = ymin; y <= ymax; y++ ) {
869 pixbuf = get_pixbuf ( vml, &ulm, &brm );
872 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
873 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
874 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
879 // Done after so drawn on top
880 // Just a handy guide to tile blocks.
881 if ( vik_debug && vik_verbose ) {
882 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
883 gint width = vik_viewport_get_width(vvp);
884 gint height = vik_viewport_get_height(vvp);
886 ulm.x = xmin; ulm.y = ymin;
887 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
888 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
889 xx = xx - (vml->tile_size_x/2);
890 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
891 for (gint x = xmin; x <= xmax; x++ ) {
892 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
893 xx += vml->tile_size_x;
895 for (gint y = ymin; y <= ymax; y++ ) {
896 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
897 yy += vml->tile_size_x; // Yes use X ATM
906 static void mapnik_layer_free ( VikMapnikLayer *vml )
908 mapnik_interface_free ( vml->mi );
909 if ( vml->filename_css )
910 g_free ( vml->filename_css );
911 if ( vml->filename_xml )
912 g_free ( vml->filename_xml );
915 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
917 return mapnik_layer_new ( vp );
926 typedef gpointer menu_array_values[MA_LAST];
931 static void mapnik_layer_flush_memory ( menu_array_values values )
933 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
939 static void mapnik_layer_reload ( menu_array_values values )
941 VikMapnikLayer *vml = values[MA_VML];
942 VikViewport *vvp = values[MA_VVP];
943 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
944 mapnik_layer_draw ( vml, vvp );
950 * Most carto projects will consist of many files
951 * ATM don't have a way of detecting when any of the included files have changed
952 * Thus allow a manual method to force re-running carto
954 static void mapnik_layer_carto ( menu_array_values values )
956 VikMapnikLayer *vml = values[MA_VML];
957 VikViewport *vvp = values[MA_VVP];
959 // Don't load the XML config if carto load fails
960 if ( !carto_load ( vml, vvp ) )
963 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
965 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
966 _("Mapnik error loading configuration file:\n%s"),
971 mapnik_layer_draw ( vml, vvp );
975 * Show Mapnik configuration parameters
977 static void mapnik_layer_information ( menu_array_values values )
979 VikMapnikLayer *vml = values[MA_VML];
982 GArray *array = mapnik_interface_get_parameters( vml->mi );
984 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
985 // Free the copied strings
986 for ( int i = 0; i < array->len; i++ )
987 g_free ( g_array_index(array,gchar*,i) );
989 g_array_free ( array, FALSE );
995 static void mapnik_layer_about ( menu_array_values values )
997 VikMapnikLayer *vml = values[MA_VML];
998 gchar *msg = mapnik_interface_about();
999 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
1006 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
1008 static menu_array_values values;
1009 values[MA_VML] = vml;
1010 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1012 GtkWidget *item = gtk_menu_item_new();
1013 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1014 gtk_widget_show ( item );
1016 // Typical users shouldn't need to use this functionality - so debug only ATM
1018 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1019 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1020 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1021 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1022 gtk_widget_show ( item );
1025 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1026 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1027 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1028 gtk_widget_show ( item );
1030 if ( g_strcmp0 ("", vml->filename_css) ) {
1031 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1032 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1033 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1034 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1035 gtk_widget_show ( item );
1038 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1039 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1040 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1041 gtk_widget_show ( item );
1043 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1044 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1045 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1046 gtk_widget_show ( item );
1050 * Rerender a specific tile
1052 static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1055 // Requested position to map coord
1056 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1057 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1058 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1059 // Bottom right bound is simply +1 in TMS coords
1063 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1064 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1070 static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1073 // Requested position to map coord
1074 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1076 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 );
1078 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1079 gchar *filemsg = NULL;
1080 gchar *timemsg = NULL;
1082 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1083 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1084 // Get some timestamp information of the tile
1085 struct stat stat_buf;
1086 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1088 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1089 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1092 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1096 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1097 timemsg = g_strdup("");
1100 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1101 g_array_append_val ( array, filemsg );
1102 g_array_append_val ( array, timemsg );
1104 gchar *rendmsg = NULL;
1106 if ( extra.duration > 0.0 ) {
1107 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1108 g_array_append_val ( array, rendmsg );
1111 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1112 g_array_free ( array, FALSE );
1117 g_free ( filename );
1120 static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1124 if ( event->button == 3 ) {
1125 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1126 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1128 if ( ! vml->right_click_menu ) {
1130 vml->right_click_menu = gtk_menu_new ();
1132 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1133 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1134 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1135 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1137 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1138 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1139 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1140 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1143 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1144 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );