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 // See comment in viktrwlayer.c for advice on values used
116 static VikToolInterface mapnik_tools[] = {
121 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
123 VikLayerInterface vik_mapnik_layer_interface = {
125 N_("Mapnik Rendering"),
127 &vikmapniklayer_pixbuf, // icon
130 sizeof(mapnik_tools) / sizeof(VikToolInterface),
139 (VikLayerFuncCreate) mapnik_layer_create,
140 (VikLayerFuncRealize) NULL,
141 (VikLayerFuncPostRead) mapnik_layer_post_read,
142 (VikLayerFuncFree) mapnik_layer_free,
144 (VikLayerFuncProperties) NULL,
145 (VikLayerFuncDraw) mapnik_layer_draw,
146 (VikLayerFuncChangeCoordMode) NULL,
148 (VikLayerFuncSetMenuItemsSelection) NULL,
149 (VikLayerFuncGetMenuItemsSelection) NULL,
151 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
152 (VikLayerFuncSublayerAddMenuItems) NULL,
154 (VikLayerFuncSublayerRenameRequest) NULL,
155 (VikLayerFuncSublayerToggleVisible) NULL,
156 (VikLayerFuncSublayerTooltip) NULL,
157 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
158 (VikLayerFuncLayerSelected) NULL,
160 (VikLayerFuncMarshall) mapnik_layer_marshall,
161 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
163 (VikLayerFuncSetParam) mapnik_layer_set_param,
164 (VikLayerFuncGetParam) mapnik_layer_get_param,
165 (VikLayerFuncChangeParam) NULL,
167 (VikLayerFuncReadFileData) NULL,
168 (VikLayerFuncWriteFileData) NULL,
170 (VikLayerFuncDeleteItem) NULL,
171 (VikLayerFuncCutItem) NULL,
172 (VikLayerFuncCopyItem) NULL,
173 (VikLayerFuncPasteItem) NULL,
174 (VikLayerFuncFreeCopiedItem) NULL,
175 (VikLayerFuncDragDropRequest) NULL,
177 (VikLayerFuncSelectClick) NULL,
178 (VikLayerFuncSelectMove) NULL,
179 (VikLayerFuncSelectRelease) NULL,
180 (VikLayerFuncSelectedViewportMenu) NULL,
183 struct _VikMapnikLayer {
185 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
189 guint tile_size_x; // Y is the same as X ATM
192 guint rerender_timeout;
194 gboolean use_file_cache;
195 gchar *file_cache_dir;
198 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
199 #define MAPNIK_PREFS_NAMESPACE "mapnik."
201 static VikLayerParamData plugins_default ( void )
203 VikLayerParamData data;
205 data.s = g_strdup ( "input" );
207 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
208 data.s = g_strdup ( "/usr/lib/mapnik/input" );
209 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
210 // Current Debian location
211 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
213 data.s = g_strdup ( "" );
218 static VikLayerParamData fonts_default ( void )
220 // Possibly should be string list to allow loading from multiple directories
221 VikLayerParamData data;
223 data.s = g_strdup ( "C:\\Windows\\Fonts" );
224 #elif defined __APPLE__
225 data.s = g_strdup ( "/Library/Fonts" );
227 data.s = g_strdup ( "/usr/share/fonts" );
232 static VikLayerParam prefs[] = {
233 // Changing these values only applies before first mapnik layer is 'created'
234 { 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 },
235 { 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 },
236 { 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 },
237 { 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 },
238 // Changeable any time
239 { 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 },
242 static time_t planet_import_time;
244 static GMutex *tp_mutex;
245 static GHashTable *requests = NULL;
248 * vik_mapnik_layer_init:
250 * Mostly to initialize preferences
252 void vik_mapnik_layer_init (void)
254 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
257 VikLayerParamData tmp = plugins_default();
258 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
260 tmp = fonts_default();
261 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
264 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
266 tmp.u = 168; // One week
267 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
270 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
272 tp_mutex = vik_mutex_new();
274 // Just storing keys only
275 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
277 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
278 GDateTime *now = g_date_time_new_now_local ();
279 GDateTime *then = g_date_time_add_hours (now, -hours);
280 planet_import_time = g_date_time_to_unix (then);
281 g_date_time_unref ( now );
282 g_date_time_unref ( then );
285 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
286 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
287 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
288 // Only update if newer
289 if ( planet_import_time > gsb.st_mtime )
290 planet_import_time = gsb.st_mtime;
292 g_free ( import_time_file );
295 void vik_mapnik_layer_uninit ()
297 vik_mutex_free (tp_mutex);
300 // NB Only performed once per program run
301 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
303 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
304 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
305 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
308 GType vik_mapnik_layer_get_type ()
310 static GType vml_type = 0;
313 static const GTypeInfo vml_info = {
314 sizeof (VikMapnikLayerClass),
315 NULL, /* base_init */
316 NULL, /* base_finalize */
317 (GClassInitFunc) mapnik_layer_class_init, /* class init */
318 NULL, /* class_finalize */
319 NULL, /* class_data */
320 sizeof (VikMapnikLayer),
322 NULL /* instance init */
324 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
330 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
332 return vml->filename_xml;
335 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
337 if ( vml->filename_xml )
338 g_free (vml->filename_xml);
339 vml->filename_xml = g_strdup (name);
342 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
344 if ( vml->filename_css )
345 g_free (vml->filename_css);
346 vml->filename_css = g_strdup (name);
349 static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
351 if ( vml->file_cache_dir )
352 g_free (vml->file_cache_dir);
353 vml->file_cache_dir = g_strdup (name);
356 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
358 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
361 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
363 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
364 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
368 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
371 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
372 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
373 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
374 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
375 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
381 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
383 VikLayerParamData data;
385 case PARAM_CONFIG_CSS: {
386 data.s = vml->filename_css;
387 gboolean set = FALSE;
388 if ( is_file_operation ) {
389 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
390 gchar *cwd = g_get_current_dir();
392 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
393 if ( !data.s ) data.s = "";
399 data.s = vml->filename_css ? vml->filename_css : "";
402 case PARAM_CONFIG_XML: {
403 data.s = vml->filename_xml;
404 gboolean set = FALSE;
405 if ( is_file_operation ) {
406 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
407 gchar *cwd = g_get_current_dir();
409 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
410 if ( !data.s ) data.s = "";
416 data.s = vml->filename_xml ? vml->filename_xml : "";
419 case PARAM_ALPHA: data.u = vml->alpha; break;
420 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
421 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
430 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
432 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
433 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
434 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
435 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
437 vml->mi = mapnik_interface_new();
443 * ATM don't have any version issues AFAIK
444 * Tested with carto 0.14.0
446 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
448 gchar *mystdout = NULL;
449 gchar *mystderr = NULL;
450 GError *error = NULL;
452 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
453 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
455 gboolean answer = TRUE;
456 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
458 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
459 // cf code in babel.c to handle stdout
461 // NB Running carto may take several seconds
462 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
463 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
465 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
466 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
467 vik_window_set_busy_cursor ( vw );
472 // You won't get a sensible timing measurement if running too old a GLIB
473 #if GLIB_CHECK_VERSION (2, 28, 0)
474 tt1 = g_get_real_time ();
477 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
478 #if GLIB_CHECK_VERSION (2, 28, 0)
479 tt2 = g_get_real_time ();
482 if ( strlen(mystderr) > 1 ) {
483 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
487 // NB This will overwrite the specified XML file
488 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
489 // XML Not specified so try to create based on CSS file name
490 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
492 g_critical ("%s: %s", __FUNCTION__, error->message );
493 if ( vml->filename_xml )
494 g_free (vml->filename_xml);
495 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
497 g_warning ("%s: %s", __FUNCTION__, error->message );
498 // Prevent overwriting self
499 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
500 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
503 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
504 g_warning ("%s: %s", __FUNCTION__, error->message );
505 g_error_free (error);
512 g_warning ("%s: %s", __FUNCTION__, error->message );
513 g_error_free (error);
518 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
519 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
521 vik_window_clear_busy_cursor ( vw );
529 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
531 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
533 // Determine if carto needs to be run
534 gboolean do_carto = FALSE;
535 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
536 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
537 // Compare timestamps
539 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
541 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
542 // Is CSS file newer than the XML file
543 if ( gsb2.st_mtime > gsb1.st_mtime )
546 g_debug ( "No need to run carto" );
550 // XML file doesn't exist
555 // No XML specified thus need to generate
561 // Don't load the XML config if carto load fails
562 if ( !carto_load ( vml, vvp ) )
565 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
567 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
568 _("Mapnik error loading configuration file:\n%s"),
575 ui_add_recent_file ( vml->filename_xml );
579 #define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
581 // Free returned string after use
582 static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
584 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
587 static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
589 if ( vml->use_file_cache ) {
590 if ( vml->file_cache_dir ) {
591 GError *error = NULL;
592 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
594 gchar *dir = g_path_get_dirname ( filename );
595 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
596 g_mkdir_with_parents ( dir , 0777 );
599 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
600 g_warning ("%s: %s", __FUNCTION__, error->message );
601 g_error_free (error);
614 const gchar* request;
620 * Common render function which can run in separate thread
622 static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
624 gint64 tt1 = g_get_real_time ();
625 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
626 gint64 tt2 = g_get_real_time ();
627 g_debug ( "Mapnik rendering completed in %.3f seconds", (gdouble)(tt2-tt1)/1000000 );
629 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
630 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
632 possibly_save_pixbuf ( vml, pixbuf, ulm );
634 // NB Mapnik can apply alpha, but use our own function for now
635 if ( vml->alpha < 255 )
636 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
637 a_mapcache_add ( pixbuf, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
640 static void render_info_free ( RenderInfo *data )
644 g_free ( data->ulmc );
645 // NB No need to free the request/key - as this is freed by the hash table destructor
649 static void background ( RenderInfo *data, gpointer threaddata )
651 int res = a_background_thread_progress ( threaddata, 0 );
653 render ( data->vml, data->ul, data->br, data->ulmc );
656 g_mutex_lock(tp_mutex);
657 g_hash_table_remove (requests, data->request);
658 g_mutex_unlock(tp_mutex);
661 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
664 static void render_cancel_cleanup (RenderInfo *data)
669 #define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
674 void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
677 guint nn = name ? g_str_hash ( name ) : 0;
678 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
680 g_mutex_lock(tp_mutex);
682 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
684 g_mutex_unlock (tp_mutex);
688 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
690 ri->ul = g_malloc ( sizeof(VikCoord) );
691 ri->br = g_malloc ( sizeof(VikCoord) );
692 ri->ulmc = g_malloc ( sizeof(MapCoord) );
693 memcpy(ri->ul, ul, sizeof(VikCoord));
694 memcpy(ri->br, br, sizeof(VikCoord));
695 memcpy(ri->ulmc, mul, sizeof(MapCoord));
696 ri->request = request;
698 g_hash_table_insert ( requests, request, NULL );
700 g_mutex_unlock (tp_mutex);
702 gchar *basename = g_path_get_basename (name);
703 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
705 a_background_thread ( BACKGROUND_POOL_LOCAL,
706 VIK_GTK_WINDOW_FROM_LAYER(vml),
708 (vik_thr_func) background,
710 (vik_thr_free_func) render_info_free,
711 (vik_thr_free_func) render_cancel_cleanup,
713 g_free ( description );
719 static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
722 GdkPixbuf *pixbuf = NULL;
723 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
726 if ( g_stat ( filename, &gsb ) == 0 ) {
728 GError *error = NULL;
729 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
731 g_warning ("%s: %s", __FUNCTION__, error->message );
732 g_error_free ( error );
735 if ( vml->alpha < 255 )
736 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
737 a_mapcache_add ( pixbuf, ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
739 // If file is too old mark for rerendering
740 if ( planet_import_time < gsb.st_mtime ) {
752 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
754 VikCoord ul; VikCoord br;
755 GdkPixbuf *pixbuf = NULL;
757 map_utils_iTMS_to_vikcoord (ulm, &ul);
758 map_utils_iTMS_to_vikcoord (brm, &br);
760 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 );
763 gboolean rerender = FALSE;
764 if ( vml->use_file_cache && vml->file_cache_dir )
765 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
766 if ( ! pixbuf || rerender ) {
768 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
770 // Run in the foreground
771 render ( vml, &ul, &br, ulm );
772 vik_layer_emit_update ( VIK_LAYER(vml) );
783 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
788 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
789 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
790 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
795 ul.mode = VIK_COORD_LATLON;
796 br.mode = VIK_COORD_LATLON;
797 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
798 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
800 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
801 gdouble yzoom = vik_viewport_get_ympp ( vvp );
805 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
806 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
807 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
812 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
813 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
815 // Split rendering into a grid for the current viewport
816 // thus each individual 'tile' can then be stored in the map cache
817 for (gint x = xmin; x <= xmax; x++ ) {
818 for (gint y = ymin; y <= ymax; y++ ) {
824 pixbuf = get_pixbuf ( vml, &ulm, &brm );
827 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
828 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
829 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
834 // Done after so drawn on top
835 // Just a handy guide to tile blocks.
836 if ( vik_debug && vik_verbose ) {
837 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
838 gint width = vik_viewport_get_width(vvp);
839 gint height = vik_viewport_get_height(vvp);
841 ulm.x = xmin; ulm.y = ymin;
842 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
843 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
844 xx = xx - (vml->tile_size_x/2);
845 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
846 for (gint x = xmin; x <= xmax; x++ ) {
847 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
848 xx += vml->tile_size_x;
850 for (gint y = ymin; y <= ymax; y++ ) {
851 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
852 yy += vml->tile_size_x; // Yes use X ATM
861 static void mapnik_layer_free ( VikMapnikLayer *vml )
863 mapnik_interface_free ( vml->mi );
864 if ( vml->filename_css )
865 g_free ( vml->filename_css );
866 if ( vml->filename_xml )
867 g_free ( vml->filename_xml );
870 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
872 return mapnik_layer_new ( vp );
881 typedef gpointer menu_array_values[MA_LAST];
886 static void mapnik_layer_flush_memory ( menu_array_values values )
888 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
894 static void mapnik_layer_reload ( menu_array_values values )
896 VikMapnikLayer *vml = values[MA_VML];
897 VikViewport *vvp = values[MA_VVP];
898 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
899 mapnik_layer_draw ( vml, vvp );
905 static void mapnik_layer_about ( menu_array_values values )
907 VikMapnikLayer *vml = values[MA_VML];
908 gchar *msg = mapnik_interface_about();
909 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
916 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
918 static menu_array_values values;
919 values[MA_VML] = vml;
920 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
922 GtkWidget *item = gtk_menu_item_new();
923 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
924 gtk_widget_show ( item );
926 // Typical users shouldn't need to use this functionality - so debug only ATM
928 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
929 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
930 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
931 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
932 gtk_widget_show ( item );
935 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
936 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
937 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
938 gtk_widget_show ( item );
940 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
941 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
942 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
943 gtk_widget_show ( item );