]> git.street.me.uk Git - andy/viking.git/blame_incremental - src/vikmapniklayer.c
Fix uniquify tracks to use the appropriate sort order.
[andy/viking.git] / src / vikmapniklayer.c
... / ...
CommitLineData
1/*
2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3 *
4 * Copyright (c) 2015, Rob Norris <rw_norris@hotmail.com>
5 *
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.
10 *
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.
15 *
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
19 *
20 */
21
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
25
26#include "viking.h"
27#include "vikutils.h"
28#include <glib.h>
29#include <glib/gstdio.h>
30#include <glib/gi18n.h>
31#include <string.h>
32#include <math.h>
33#include <stdlib.h>
34#include <ctype.h>
35
36#include "map_ids.h"
37#include "maputils.h"
38#include "mapcoord.h"
39#include "mapcache.h"
40#include "dir.h"
41#include "util.h"
42#include "ui_util.h"
43#include "preferences.h"
44#include "icons/icons.h"
45#include "mapnik_interface.h"
46#include "background.h"
47
48#include "vikmapslayer.h"
49
50#if !GLIB_CHECK_VERSION(2,26,0)
51typedef struct stat GStatBuf;
52#endif
53
54struct _VikMapnikLayerClass
55{
56 VikLayerClass object_class;
57};
58
59static VikLayerParamData file_default ( void )
60{
61 VikLayerParamData data;
62 data.s = "";
63 return data;
64}
65
66static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
67static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
68
69static VikLayerParamData cache_dir_default ( void )
70{
71 VikLayerParamData data;
72 data.s = g_strconcat ( maps_layer_default_dir(), "MapnikRendering", NULL );
73 return data;
74}
75
76static VikLayerParamScale scales[] = {
77 { 0, 255, 5, 0 }, // Alpha
78 { 64, 1024, 8, 0 }, // Tile size
79 { 0, 1024, 12, 0 }, // Rerender timeout hours
80};
81
82VikLayerParam 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 },
93};
94
95enum {
96 PARAM_CONFIG_CSS = 0,
97 PARAM_CONFIG_XML,
98 PARAM_ALPHA,
99 PARAM_USE_FILE_CACHE,
100 PARAM_FILE_CACHE_DIR,
101 NUM_PARAMS };
102
103static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
104static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
105static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
106static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
107static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
108static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
109static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
110static void mapnik_layer_free ( VikMapnikLayer *vml );
111static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
112static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
113
114static gpointer mapnik_feature_create ( VikWindow *vw, VikViewport *vvp)
115{
116 return vvp;
117}
118
119static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp );
120
121// See comment in viktrwlayer.c for advice on values used
122// FUTURE:
123static VikToolInterface mapnik_tools[] = {
124 // Layer Info
125 // Zoom All?
126 { { "MapnikFeatures", GTK_STOCK_INFO, N_("_Mapnik Features"), NULL, N_("Mapnik Features"), 0 },
127 (VikToolConstructorFunc) mapnik_feature_create,
128 NULL,
129 NULL,
130 NULL,
131 NULL,
132 NULL,
133 (VikToolMouseFunc) mapnik_feature_release,
134 NULL,
135 FALSE,
136 GDK_LEFT_PTR, NULL, NULL },
137};
138
139static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
140
141VikLayerInterface vik_mapnik_layer_interface = {
142 "Mapnik Rendering",
143 N_("Mapnik Rendering"),
144 NULL,
145 &vikmapniklayer_pixbuf, // icon
146
147 mapnik_tools,
148 sizeof(mapnik_tools) / sizeof(VikToolInterface),
149
150 mapnik_layer_params,
151 NUM_PARAMS,
152 NULL,
153 0,
154
155 VIK_MENU_ITEM_ALL,
156
157 (VikLayerFuncCreate) mapnik_layer_create,
158 (VikLayerFuncRealize) NULL,
159 (VikLayerFuncPostRead) mapnik_layer_post_read,
160 (VikLayerFuncFree) mapnik_layer_free,
161
162 (VikLayerFuncProperties) NULL,
163 (VikLayerFuncDraw) mapnik_layer_draw,
164 (VikLayerFuncChangeCoordMode) NULL,
165
166 (VikLayerFuncSetMenuItemsSelection) NULL,
167 (VikLayerFuncGetMenuItemsSelection) NULL,
168
169 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
170 (VikLayerFuncSublayerAddMenuItems) NULL,
171
172 (VikLayerFuncSublayerRenameRequest) NULL,
173 (VikLayerFuncSublayerToggleVisible) NULL,
174 (VikLayerFuncSublayerTooltip) NULL,
175 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
176 (VikLayerFuncLayerSelected) NULL,
177
178 (VikLayerFuncMarshall) mapnik_layer_marshall,
179 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
180
181 (VikLayerFuncSetParam) mapnik_layer_set_param,
182 (VikLayerFuncGetParam) mapnik_layer_get_param,
183 (VikLayerFuncChangeParam) NULL,
184
185 (VikLayerFuncReadFileData) NULL,
186 (VikLayerFuncWriteFileData) NULL,
187
188 (VikLayerFuncDeleteItem) NULL,
189 (VikLayerFuncCutItem) NULL,
190 (VikLayerFuncCopyItem) NULL,
191 (VikLayerFuncPasteItem) NULL,
192 (VikLayerFuncFreeCopiedItem) NULL,
193 (VikLayerFuncDragDropRequest) NULL,
194
195 (VikLayerFuncSelectClick) NULL,
196 (VikLayerFuncSelectMove) NULL,
197 (VikLayerFuncSelectRelease) NULL,
198 (VikLayerFuncSelectedViewportMenu) NULL,
199};
200
201struct _VikMapnikLayer {
202 VikLayer vl;
203 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
204 gchar *filename_xml;
205 guint8 alpha;
206
207 guint tile_size_x; // Y is the same as X ATM
208 gboolean loaded;
209 MapnikInterface* mi;
210 guint rerender_timeout;
211
212 gboolean use_file_cache;
213 gchar *file_cache_dir;
214
215 VikCoord rerender_ul;
216 VikCoord rerender_br;
217 gdouble rerender_zoom;
218 GtkWidget *right_click_menu;
219};
220
221#define MAPNIK_PREFS_GROUP_KEY "mapnik"
222#define MAPNIK_PREFS_NAMESPACE "mapnik."
223
224static VikLayerParamData plugins_default ( void )
225{
226 VikLayerParamData data;
227#ifdef WINDOWS
228 data.s = g_strdup ( "input" );
229#else
230 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
231 data.s = g_strdup ( "/usr/lib/mapnik/input" );
232 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
233 // Current Debian location
234 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
235 else
236 data.s = g_strdup ( "" );
237#endif
238 return data;
239}
240
241static VikLayerParamData fonts_default ( void )
242{
243 // Possibly should be string list to allow loading from multiple directories
244 VikLayerParamData data;
245#ifdef WINDOWS
246 data.s = g_strdup ( "C:\\Windows\\Fonts" );
247#elif defined __APPLE__
248 data.s = g_strdup ( "/Library/Fonts" );
249#else
250 data.s = g_strdup ( "/usr/share/fonts" );
251#endif
252 return data;
253}
254
255static VikLayerParam prefs[] = {
256 // Changing these values only applies before first mapnik layer is 'created'
257 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"plugins_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Plugins Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), plugins_default, NULL, NULL },
258 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"fonts_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Fonts Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), fonts_default, NULL, NULL },
259 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Recurse Fonts Directory:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), vik_lpd_true_default, NULL, NULL },
260 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"rerender_after", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Rerender Timeout (hours):"), VIK_LAYER_WIDGET_SPINBUTTON, &scales[2], NULL, N_("You need to restart Viking for a change to this value to be used"), NULL, NULL, NULL },
261 // Changeable any time
262 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"carto", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CartoCSS:"), VIK_LAYER_WIDGET_FILEENTRY, NULL, NULL, N_("The program to convert CartoCSS files into Mapnik XML"), NULL, NULL, NULL },
263};
264
265static time_t planet_import_time;
266
267static GMutex *tp_mutex;
268static GHashTable *requests = NULL;
269
270/**
271 * vik_mapnik_layer_init:
272 *
273 * Mostly to initialize preferences
274 */
275void vik_mapnik_layer_init (void)
276{
277 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
278
279 guint i = 0;
280 VikLayerParamData tmp = plugins_default();
281 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
282
283 tmp = fonts_default();
284 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
285
286 tmp.b = TRUE;
287 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
288
289 tmp.u = 168; // One week
290 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
291
292 tmp.s = "carto";
293 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
294
295 tp_mutex = vik_mutex_new();
296
297 // Just storing keys only
298 requests = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
299
300 guint hours = a_preferences_get (MAPNIK_PREFS_NAMESPACE"rerender_after")->u;
301 GDateTime *now = g_date_time_new_now_local ();
302 GDateTime *then = g_date_time_add_hours (now, -hours);
303 planet_import_time = g_date_time_to_unix (then);
304 g_date_time_unref ( now );
305 g_date_time_unref ( then );
306
307 GStatBuf gsb;
308 // Similar to mod_tile method to mark DB has been imported/significantly changed to cause a rerendering of all tiles
309 gchar *import_time_file = g_strconcat ( a_get_viking_dir(), G_DIR_SEPARATOR_S, "planet-import-complete", NULL );
310 if ( g_stat ( import_time_file, &gsb ) == 0 ) {
311 // Only update if newer
312 if ( planet_import_time > gsb.st_mtime )
313 planet_import_time = gsb.st_mtime;
314 }
315 g_free ( import_time_file );
316}
317
318void vik_mapnik_layer_uninit ()
319{
320 vik_mutex_free (tp_mutex);
321}
322
323// NB Only performed once per program run
324static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
325{
326 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
327 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
328 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
329}
330
331GType vik_mapnik_layer_get_type ()
332{
333 static GType vml_type = 0;
334
335 if (!vml_type) {
336 static const GTypeInfo vml_info = {
337 sizeof (VikMapnikLayerClass),
338 NULL, /* base_init */
339 NULL, /* base_finalize */
340 (GClassInitFunc) mapnik_layer_class_init, /* class init */
341 NULL, /* class_finalize */
342 NULL, /* class_data */
343 sizeof (VikMapnikLayer),
344 0,
345 NULL /* instance init */
346 };
347 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
348 }
349
350 return vml_type;
351}
352
353static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
354{
355 return vml->filename_xml;
356}
357
358static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
359{
360 if ( vml->filename_xml )
361 g_free (vml->filename_xml);
362 // Mapnik doesn't seem to cope with relative filenames
363 if ( g_strcmp0 (name, "" ) )
364 vml->filename_xml = vu_get_canonical_filename ( VIK_LAYER(vml), name);
365 else
366 vml->filename_xml = g_strdup (name);
367}
368
369static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
370{
371 if ( vml->filename_css )
372 g_free (vml->filename_css);
373 vml->filename_css = g_strdup (name);
374}
375
376static void mapnik_layer_set_cache_dir ( VikMapnikLayer *vml, const gchar *name )
377{
378 if ( vml->file_cache_dir )
379 g_free (vml->file_cache_dir);
380 vml->file_cache_dir = g_strdup (name);
381}
382
383static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
384{
385 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
386}
387
388static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
389{
390 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
391 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
392 return rv;
393}
394
395static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
396{
397 switch ( id ) {
398 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
399 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
400 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
401 case PARAM_USE_FILE_CACHE: vml->use_file_cache = data.b; break;
402 case PARAM_FILE_CACHE_DIR: mapnik_layer_set_cache_dir (vml, data.s); break;
403 default: break;
404 }
405 return TRUE;
406}
407
408static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
409{
410 VikLayerParamData data;
411 switch ( id ) {
412 case PARAM_CONFIG_CSS: {
413 data.s = vml->filename_css;
414 gboolean set = FALSE;
415 if ( is_file_operation ) {
416 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
417 gchar *cwd = g_get_current_dir();
418 if ( cwd ) {
419 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
420 if ( !data.s ) data.s = "";
421 set = TRUE;
422 }
423 }
424 }
425 if ( !set )
426 data.s = vml->filename_css ? vml->filename_css : "";
427 break;
428 }
429 case PARAM_CONFIG_XML: {
430 data.s = vml->filename_xml;
431 gboolean set = FALSE;
432 if ( is_file_operation ) {
433 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
434 gchar *cwd = g_get_current_dir();
435 if ( cwd ) {
436 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
437 if ( !data.s ) data.s = "";
438 set = TRUE;
439 }
440 }
441 }
442 if ( !set )
443 data.s = vml->filename_xml ? vml->filename_xml : "";
444 break;
445 }
446 case PARAM_ALPHA: data.u = vml->alpha; break;
447 case PARAM_USE_FILE_CACHE: data.b = vml->use_file_cache; break;
448 case PARAM_FILE_CACHE_DIR: data.s = vml->file_cache_dir; break;
449 default: break;
450 }
451 return data;
452}
453
454/**
455 *
456 */
457static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
458{
459 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
460 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
461 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
462 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
463 vml->loaded = FALSE;
464 vml->mi = mapnik_interface_new();
465 return vml;
466}
467
468/**
469 * Run carto command
470 * ATM don't have any version issues AFAIK
471 * Tested with carto 0.14.0
472 */
473gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
474{
475 gchar *mystdout = NULL;
476 gchar *mystderr = NULL;
477 GError *error = NULL;
478
479 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
480 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
481
482 gboolean answer = TRUE;
483 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
484 //GPid pid;
485 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
486 // cf code in babel.c to handle stdout
487
488 // NB Running carto may take several seconds
489 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
490 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
491 if ( vw ) {
492 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
493 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
494 vik_window_set_busy_cursor ( vw );
495 }
496
497 gint64 tt1 = 0;
498 gint64 tt2 = 0;
499 // You won't get a sensible timing measurement if running too old a GLIB
500#if GLIB_CHECK_VERSION (2, 28, 0)
501 tt1 = g_get_real_time ();
502#endif
503
504 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
505#if GLIB_CHECK_VERSION (2, 28, 0)
506 tt2 = g_get_real_time ();
507#endif
508 if ( mystderr )
509 if ( strlen(mystderr) > 1 ) {
510 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
511 answer = FALSE;
512 }
513 if ( mystdout ) {
514 // NB This will overwrite the specified XML file
515 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
516 // XML Not specified so try to create based on CSS file name
517 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
518 if ( error )
519 g_critical ("%s: %s", __FUNCTION__, error->message );
520 if ( vml->filename_xml )
521 g_free (vml->filename_xml);
522 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
523 if ( error )
524 g_warning ("%s: %s", __FUNCTION__, error->message );
525 // Prevent overwriting self
526 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
527 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
528 }
529 }
530 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
531 g_warning ("%s: %s", __FUNCTION__, error->message );
532 g_error_free (error);
533 }
534 }
535 g_free ( mystdout );
536 g_free ( mystderr );
537 }
538 else {
539 g_warning ("%s: %s", __FUNCTION__, error->message );
540 g_error_free (error);
541 }
542 g_free ( command );
543
544 if ( vw ) {
545 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
546 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
547 g_free ( msg );
548 vik_window_clear_busy_cursor ( vw );
549 }
550 return answer;
551}
552
553/**
554 *
555 */
556static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
557{
558 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
559
560 // Determine if carto needs to be run
561 gboolean do_carto = FALSE;
562 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
563 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
564 // Compare timestamps
565 GStatBuf gsb1;
566 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
567 GStatBuf gsb2;
568 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
569 // Is CSS file newer than the XML file
570 if ( gsb2.st_mtime > gsb1.st_mtime )
571 do_carto = TRUE;
572 else
573 g_debug ( "No need to run carto" );
574 }
575 }
576 else {
577 // XML file doesn't exist
578 do_carto = TRUE;
579 }
580 }
581 else {
582 // No XML specified thus need to generate
583 do_carto = TRUE;
584 }
585 }
586
587 if ( do_carto )
588 // Don't load the XML config if carto load fails
589 if ( !carto_load ( vml, vvp ) )
590 return;
591
592 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
593 if ( ans ) {
594 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
595 _("Mapnik error loading configuration file:\n%s"),
596 ans );
597 g_free ( ans );
598 }
599 else {
600 vml->loaded = TRUE;
601 if ( !from_file )
602 ui_add_recent_file ( vml->filename_xml );
603 }
604}
605
606#define MAPNIK_LAYER_FILE_CACHE_LAYOUT "%s"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d"G_DIR_SEPARATOR_S"%d.png"
607
608// Free returned string after use
609static gchar *get_filename ( gchar *dir, guint x, guint y, guint z)
610{
611 return g_strdup_printf ( MAPNIK_LAYER_FILE_CACHE_LAYOUT, dir, (17-z), x, y );
612}
613
614static void possibly_save_pixbuf ( VikMapnikLayer *vml, GdkPixbuf *pixbuf, MapCoord *ulm )
615{
616 if ( vml->use_file_cache ) {
617 if ( vml->file_cache_dir ) {
618 GError *error = NULL;
619 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
620
621 gchar *dir = g_path_get_dirname ( filename );
622 if ( !g_file_test ( filename, G_FILE_TEST_EXISTS ) )
623 g_mkdir_with_parents ( dir , 0777 );
624 g_free ( dir );
625
626 if ( !gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL ) ) {
627 g_warning ("%s: %s", __FUNCTION__, error->message );
628 g_error_free (error);
629 }
630 g_free (filename);
631 }
632 }
633}
634
635typedef struct
636{
637 VikMapnikLayer *vml;
638 VikCoord *ul;
639 VikCoord *br;
640 MapCoord *ulmc;
641 const gchar* request;
642} RenderInfo;
643
644/**
645 * render:
646 *
647 * Common render function which can run in separate thread
648 */
649static void render ( VikMapnikLayer *vml, VikCoord *ul, VikCoord *br, MapCoord *ulm )
650{
651 gint64 tt1 = g_get_real_time ();
652 GdkPixbuf *pixbuf = mapnik_interface_render ( vml->mi, ul->north_south, ul->east_west, br->north_south, br->east_west );
653 gint64 tt2 = g_get_real_time ();
654 gdouble tt = (gdouble)(tt2-tt1)/1000000;
655 g_debug ( "Mapnik rendering completed in %.3f seconds", tt );
656 if ( !pixbuf ) {
657 // A pixbuf to stick into cache incase of an unrenderable area - otherwise will get continually re-requested
658 pixbuf = gdk_pixbuf_scale_simple ( gdk_pixbuf_from_pixdata(&vikmapniklayer_pixbuf, FALSE, NULL), vml->tile_size_x, vml->tile_size_x, GDK_INTERP_BILINEAR );
659 }
660 possibly_save_pixbuf ( vml, pixbuf, ulm );
661
662 // NB Mapnik can apply alpha, but use our own function for now
663 if ( vml->alpha < 255 )
664 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
665 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 );
666}
667
668static void render_info_free ( RenderInfo *data )
669{
670 g_free ( data->ul );
671 g_free ( data->br );
672 g_free ( data->ulmc );
673 // NB No need to free the request/key - as this is freed by the hash table destructor
674 g_free ( data );
675}
676
677static void background ( RenderInfo *data, gpointer threaddata )
678{
679 int res = a_background_thread_progress ( threaddata, 0 );
680 if (res == 0) {
681 render ( data->vml, data->ul, data->br, data->ulmc );
682 }
683
684 g_mutex_lock(tp_mutex);
685 g_hash_table_remove (requests, data->request);
686 g_mutex_unlock(tp_mutex);
687
688 if (res == 0)
689 vik_layer_emit_update ( VIK_LAYER(data->vml) ); // NB update display from background
690}
691
692static void render_cancel_cleanup (RenderInfo *data)
693{
694 // Anything?
695}
696
697#define REQUEST_HASHKEY_FORMAT "%d-%d-%d-%d-%d"
698
699/**
700 * Thread
701 */
702void thread_add (VikMapnikLayer *vml, MapCoord *mul, VikCoord *ul, VikCoord *br, gint x, gint y, gint z, gint zoom, const gchar* name )
703{
704 // Create request
705 guint nn = name ? g_str_hash ( name ) : 0;
706 gchar *request = g_strdup_printf ( REQUEST_HASHKEY_FORMAT, x, y, z, zoom, nn );
707
708 g_mutex_lock(tp_mutex);
709
710 if ( g_hash_table_lookup_extended (requests, request, NULL, NULL ) ) {
711 g_free ( request );
712 g_mutex_unlock (tp_mutex);
713 return;
714 }
715
716 RenderInfo *ri = g_malloc ( sizeof(RenderInfo) );
717 ri->vml = vml;
718 ri->ul = g_malloc ( sizeof(VikCoord) );
719 ri->br = g_malloc ( sizeof(VikCoord) );
720 ri->ulmc = g_malloc ( sizeof(MapCoord) );
721 memcpy(ri->ul, ul, sizeof(VikCoord));
722 memcpy(ri->br, br, sizeof(VikCoord));
723 memcpy(ri->ulmc, mul, sizeof(MapCoord));
724 ri->request = request;
725
726 g_hash_table_insert ( requests, request, NULL );
727
728 g_mutex_unlock (tp_mutex);
729
730 gchar *basename = g_path_get_basename (name);
731 gchar *description = g_strdup_printf ( _("Mapnik Render %d:%d:%d %s"), zoom, x, y, basename );
732 g_free ( basename );
733 a_background_thread ( BACKGROUND_POOL_LOCAL_MAPNIK,
734 VIK_GTK_WINDOW_FROM_LAYER(vml),
735 description,
736 (vik_thr_func) background,
737 ri,
738 (vik_thr_free_func) render_info_free,
739 (vik_thr_free_func) render_cancel_cleanup,
740 1 );
741 g_free ( description );
742}
743
744/**
745 * load_pixbuf:
746 */
747static GdkPixbuf *load_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm, gboolean *rerender )
748{
749 *rerender = FALSE;
750 GdkPixbuf *pixbuf = NULL;
751 gchar *filename = get_filename ( vml->file_cache_dir, ulm->x, ulm->y, ulm->scale );
752
753 GStatBuf gsb;
754 if ( g_stat ( filename, &gsb ) == 0 ) {
755 // Get from disk
756 GError *error = NULL;
757 pixbuf = gdk_pixbuf_new_from_file ( filename, &error );
758 if ( error ) {
759 g_warning ("%s: %s", __FUNCTION__, error->message );
760 g_error_free ( error );
761 }
762 else {
763 if ( vml->alpha < 255 )
764 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
765 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 );
766 }
767 // If file is too old mark for rerendering
768 if ( planet_import_time < gsb.st_mtime ) {
769 *rerender = TRUE;
770 }
771 }
772 g_free ( filename );
773
774 return pixbuf;
775}
776
777/**
778 *
779 */
780static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
781{
782 VikCoord ul; VikCoord br;
783 GdkPixbuf *pixbuf = NULL;
784
785 map_utils_iTMS_to_vikcoord (ulm, &ul);
786 map_utils_iTMS_to_vikcoord (brm, &br);
787
788 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 );
789
790 if ( ! pixbuf ) {
791 gboolean rerender = FALSE;
792 if ( vml->use_file_cache && vml->file_cache_dir )
793 pixbuf = load_pixbuf ( vml, ulm, brm, &rerender );
794 if ( ! pixbuf || rerender ) {
795 if ( TRUE )
796 thread_add (vml, ulm, &ul, &br, ulm->x, ulm->y, ulm->z, ulm->scale, vml->filename_xml );
797 else {
798 // Run in the foreground
799 render ( vml, &ul, &br, ulm );
800 vik_layer_emit_update ( VIK_LAYER(vml) );
801 }
802 }
803 }
804
805 return pixbuf;
806}
807
808/**
809 *
810 */
811static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
812{
813 if ( !vml->loaded )
814 return;
815
816 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
817 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
818 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
819 return;
820 }
821
822 if ( vml->mi ) {
823 gchar *copyright = mapnik_interface_get_copyright ( vml->mi );
824 if ( copyright ) {
825 vik_viewport_add_copyright ( vvp, copyright );
826 }
827 }
828
829 VikCoord ul, br;
830 ul.mode = VIK_COORD_LATLON;
831 br.mode = VIK_COORD_LATLON;
832 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
833 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
834
835 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
836 gdouble yzoom = vik_viewport_get_ympp ( vvp );
837
838 MapCoord ulm, brm;
839
840 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
841 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
842 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
843 GdkPixbuf *pixbuf;
844 VikCoord coord;
845 gint xx, yy;
846
847 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
848 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
849
850 // Split rendering into a grid for the current viewport
851 // thus each individual 'tile' can then be stored in the map cache
852 for (gint x = xmin; x <= xmax; x++ ) {
853 for (gint y = ymin; y <= ymax; y++ ) {
854 ulm.x = x;
855 ulm.y = y;
856 brm.x = x+1;
857 brm.y = y+1;
858
859 pixbuf = get_pixbuf ( vml, &ulm, &brm );
860
861 if ( pixbuf ) {
862 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
863 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
864 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
865 }
866 }
867 }
868
869 // Done after so drawn on top
870 // Just a handy guide to tile blocks.
871 if ( vik_debug && vik_verbose ) {
872 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
873 gint width = vik_viewport_get_width(vvp);
874 gint height = vik_viewport_get_height(vvp);
875 gint xx, yy;
876 ulm.x = xmin; ulm.y = ymin;
877 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
878 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
879 xx = xx - (vml->tile_size_x/2);
880 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
881 for (gint x = xmin; x <= xmax; x++ ) {
882 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
883 xx += vml->tile_size_x;
884 }
885 for (gint y = ymin; y <= ymax; y++ ) {
886 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
887 yy += vml->tile_size_x; // Yes use X ATM
888 }
889 }
890 }
891}
892
893/**
894 *
895 */
896static void mapnik_layer_free ( VikMapnikLayer *vml )
897{
898 mapnik_interface_free ( vml->mi );
899 if ( vml->filename_css )
900 g_free ( vml->filename_css );
901 if ( vml->filename_xml )
902 g_free ( vml->filename_xml );
903}
904
905static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
906{
907 return mapnik_layer_new ( vp );
908}
909
910typedef enum {
911 MA_VML = 0,
912 MA_VVP,
913 MA_LAST
914} menu_array_index;
915
916typedef gpointer menu_array_values[MA_LAST];
917
918/**
919 *
920 */
921static void mapnik_layer_flush_memory ( menu_array_values values )
922{
923 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
924}
925
926/**
927 *
928 */
929static void mapnik_layer_reload ( menu_array_values values )
930{
931 VikMapnikLayer *vml = values[MA_VML];
932 VikViewport *vvp = values[MA_VVP];
933 mapnik_layer_post_read (VIK_LAYER(vml), vvp, FALSE);
934 mapnik_layer_draw ( vml, vvp );
935}
936
937/**
938 * Force carto run
939 *
940 * Most carto projects will consist of many files
941 * ATM don't have a way of detecting when any of the included files have changed
942 * Thus allow a manual method to force re-running carto
943 */
944static void mapnik_layer_carto ( menu_array_values values )
945{
946 VikMapnikLayer *vml = values[MA_VML];
947 VikViewport *vvp = values[MA_VVP];
948
949 // Don't load the XML config if carto load fails
950 if ( !carto_load ( vml, vvp ) )
951 return;
952
953 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
954 if ( ans ) {
955 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
956 _("Mapnik error loading configuration file:\n%s"),
957 ans );
958 g_free ( ans );
959 }
960 else
961 mapnik_layer_draw ( vml, vvp );
962}
963
964/**
965 * Show Mapnik configuration parameters
966 */
967static void mapnik_layer_information ( menu_array_values values )
968{
969 VikMapnikLayer *vml = values[MA_VML];
970 if ( !vml->mi )
971 return;
972 GArray *array = mapnik_interface_get_parameters( vml->mi );
973 if ( array->len ) {
974 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Mapnik Information"), array, 1 );
975 // Free the copied strings
976 for ( int i = 0; i < array->len; i++ )
977 g_free ( g_array_index(array,gchar*,i) );
978 }
979 g_array_free ( array, FALSE );
980}
981
982/**
983 *
984 */
985static void mapnik_layer_about ( menu_array_values values )
986{
987 VikMapnikLayer *vml = values[MA_VML];
988 gchar *msg = mapnik_interface_about();
989 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
990 g_free ( msg );
991}
992
993/**
994 *
995 */
996static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
997{
998 static menu_array_values values;
999 values[MA_VML] = vml;
1000 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
1001
1002 GtkWidget *item = gtk_menu_item_new();
1003 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
1004 gtk_widget_show ( item );
1005
1006 // Typical users shouldn't need to use this functionality - so debug only ATM
1007 if ( vik_debug ) {
1008 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
1009 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
1010 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
1011 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1012 gtk_widget_show ( item );
1013 }
1014
1015 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_REFRESH, NULL );
1016 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_reload), values );
1017 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1018 gtk_widget_show ( item );
1019
1020 if ( g_strcmp0 ("", vml->filename_css) ) {
1021 item = gtk_image_menu_item_new_with_mnemonic ( _("_Run Carto Command") );
1022 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_EXECUTE, GTK_ICON_SIZE_MENU) );
1023 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_carto), values );
1024 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1025 gtk_widget_show ( item );
1026 }
1027
1028 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_INFO, NULL );
1029 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_information), values );
1030 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1031 gtk_widget_show ( item );
1032
1033 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
1034 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
1035 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1036 gtk_widget_show ( item );
1037}
1038
1039/**
1040 * Rerender a specific tile
1041 */
1042static void mapnik_layer_rerender ( VikMapnikLayer *vml )
1043{
1044 MapCoord ulm;
1045 // Requested position to map coord
1046 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1047 // Reconvert back - thus getting the coordinate at the tile *ul corner*
1048 map_utils_iTMS_to_vikcoord (&ulm, &vml->rerender_ul );
1049 // Bottom right bound is simply +1 in TMS coords
1050 MapCoord brm = ulm;
1051 brm.x = brm.x+1;
1052 brm.y = brm.y+1;
1053 map_utils_iTMS_to_vikcoord (&brm, &vml->rerender_br );
1054 thread_add (vml, &ulm, &vml->rerender_ul, &vml->rerender_br, ulm.x, ulm.y, ulm.z, ulm.scale, vml->filename_xml );
1055}
1056
1057/**
1058 * Info
1059 */
1060static void mapnik_layer_tile_info ( VikMapnikLayer *vml )
1061{
1062 MapCoord ulm;
1063 // Requested position to map coord
1064 map_utils_vikcoord_to_iTMS ( &vml->rerender_ul, vml->rerender_zoom, vml->rerender_zoom, &ulm );
1065
1066 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 );
1067
1068 gchar *filename = get_filename ( vml->file_cache_dir, ulm.x, ulm.y, ulm.scale );
1069 gchar *filemsg = NULL;
1070 gchar *timemsg = NULL;
1071
1072 if ( g_file_test ( filename, G_FILE_TEST_EXISTS ) ) {
1073 filemsg = g_strconcat ( "Tile File: ", filename, NULL );
1074 // Get some timestamp information of the tile
1075 struct stat stat_buf;
1076 if ( g_stat ( filename, &stat_buf ) == 0 ) {
1077 gchar time_buf[64];
1078 strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) );
1079 timemsg = g_strdup_printf ( _("Tile File Timestamp: %s"), time_buf );
1080 }
1081 else {
1082 timemsg = g_strdup ( _("Tile File Timestamp: Not Available") );
1083 }
1084 }
1085 else {
1086 filemsg = g_strdup_printf ( "Tile File: %s [Not Available]", filename );
1087 timemsg = g_strdup("");
1088 }
1089
1090 GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
1091 g_array_append_val ( array, filemsg );
1092 g_array_append_val ( array, timemsg );
1093
1094 gchar *rendmsg = NULL;
1095 // Show the info
1096 if ( extra.duration > 0.0 ) {
1097 rendmsg = g_strdup_printf ( _("Rendering time %.2f seconds"), extra.duration );
1098 g_array_append_val ( array, rendmsg );
1099 }
1100
1101 a_dialog_list ( VIK_GTK_WINDOW_FROM_LAYER(vml), _("Tile Information"), array, 5 );
1102 g_array_free ( array, FALSE );
1103
1104 g_free ( rendmsg );
1105 g_free ( timemsg );
1106 g_free ( filemsg );
1107 g_free ( filename );
1108}
1109
1110static gboolean mapnik_feature_release ( VikMapnikLayer *vml, GdkEventButton *event, VikViewport *vvp )
1111{
1112 if ( !vml )
1113 return FALSE;
1114 if ( event->button == 3 ) {
1115 vik_viewport_screen_to_coord ( vvp, MAX(0, event->x), MAX(0, event->y), &vml->rerender_ul );
1116 vml->rerender_zoom = vik_viewport_get_zoom ( vvp );
1117
1118 if ( ! vml->right_click_menu ) {
1119 GtkWidget *item;
1120 vml->right_click_menu = gtk_menu_new ();
1121
1122 item = gtk_image_menu_item_new_with_mnemonic ( _("_Rerender Tile") );
1123 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
1124 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_rerender), vml );
1125 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1126
1127 item = gtk_image_menu_item_new_with_mnemonic ( _("_Info") );
1128 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_MENU) );
1129 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_tile_info), vml );
1130 gtk_menu_shell_append ( GTK_MENU_SHELL(vml->right_click_menu), item );
1131 }
1132
1133 gtk_menu_popup ( GTK_MENU(vml->right_click_menu), NULL, NULL, NULL, NULL, event->button, event->time );
1134 gtk_widget_show_all ( GTK_WIDGET(vml->right_click_menu) );
1135 }
1136
1137 return FALSE;
1138}