]> git.street.me.uk Git - andy/viking.git/blame - src/vikmapniklayer.c
A Mapnik Rendering layer
[andy/viking.git] / src / vikmapniklayer.c
CommitLineData
5fa4fe86
RN
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 "util.h"
41#include "ui_util.h"
42#include "preferences.h"
43#include "icons/icons.h"
44#include "mapnik_interface.h"
45
46struct _VikMapnikLayerClass
47{
48 VikLayerClass object_class;
49};
50
51static VikLayerParamData file_default ( void )
52{
53 VikLayerParamData data;
54 data.s = "";
55 return data;
56}
57
58static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
59static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
60
61static VikLayerParamScale scales[] = {
62 { 0, 255, 5, 0 }, // Alpha
63 { 64, 1024, 8, 0 }, // Tile size
64};
65
66VikLayerParam mapnik_layer_params[] = {
67 { 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,
68 N_("Mapnik XML configuration file"), file_default, NULL, NULL },
69 { VIK_LAYER_MAPNIK, "alpha", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Alpha:"), VIK_LAYER_WIDGET_HSCALE, &scales[0], NULL,
70 NULL, alpha_default, NULL, NULL },
71};
72
73enum {
74 PARAM_CONFIG_XML = 0,
75 PARAM_ALPHA,
76 NUM_PARAMS };
77
78static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
79static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
80static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
81static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
82static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
83static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
84static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
85static void mapnik_layer_free ( VikMapnikLayer *vml );
86static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
87static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
88
89// See comment in viktrwlayer.c for advice on values used
90// FUTURE:
91static VikToolInterface mapnik_tools[] = {
92 // Layer Info
93 // Zoom All?
94};
95
96static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
97
98VikLayerInterface vik_mapnik_layer_interface = {
99 "Mapnik Rendering",
100 N_("Mapnik Rendering"),
101 NULL,
102 &vikmapniklayer_pixbuf, // icon
103
104 mapnik_tools,
105 sizeof(mapnik_tools) / sizeof(VikToolInterface),
106
107 mapnik_layer_params,
108 NUM_PARAMS,
109 NULL,
110 0,
111
112 VIK_MENU_ITEM_ALL,
113
114 (VikLayerFuncCreate) mapnik_layer_create,
115 (VikLayerFuncRealize) NULL,
116 (VikLayerFuncPostRead) mapnik_layer_post_read,
117 (VikLayerFuncFree) mapnik_layer_free,
118
119 (VikLayerFuncProperties) NULL,
120 (VikLayerFuncDraw) mapnik_layer_draw,
121 (VikLayerFuncChangeCoordMode) NULL,
122
123 (VikLayerFuncSetMenuItemsSelection) NULL,
124 (VikLayerFuncGetMenuItemsSelection) NULL,
125
126 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
127 (VikLayerFuncSublayerAddMenuItems) NULL,
128
129 (VikLayerFuncSublayerRenameRequest) NULL,
130 (VikLayerFuncSublayerToggleVisible) NULL,
131 (VikLayerFuncSublayerTooltip) NULL,
132 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
133 (VikLayerFuncLayerSelected) NULL,
134
135 (VikLayerFuncMarshall) mapnik_layer_marshall,
136 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
137
138 (VikLayerFuncSetParam) mapnik_layer_set_param,
139 (VikLayerFuncGetParam) mapnik_layer_get_param,
140 (VikLayerFuncChangeParam) NULL,
141
142 (VikLayerFuncReadFileData) NULL,
143 (VikLayerFuncWriteFileData) NULL,
144
145 (VikLayerFuncDeleteItem) NULL,
146 (VikLayerFuncCutItem) NULL,
147 (VikLayerFuncCopyItem) NULL,
148 (VikLayerFuncPasteItem) NULL,
149 (VikLayerFuncFreeCopiedItem) NULL,
150 (VikLayerFuncDragDropRequest) NULL,
151
152 (VikLayerFuncSelectClick) NULL,
153 (VikLayerFuncSelectMove) NULL,
154 (VikLayerFuncSelectRelease) NULL,
155 (VikLayerFuncSelectedViewportMenu) NULL,
156};
157
158struct _VikMapnikLayer {
159 VikLayer vl;
160 gchar *filename_xml;
161 guint8 alpha;
162
163 guint tile_size_x; // Y is the same as X ATM
164 gboolean loaded;
165};
166
167#define MAPNIK_PREFS_GROUP_KEY "mapnik"
168#define MAPNIK_PREFS_NAMESPACE "mapnik."
169
170static VikLayerParamData plugins_default ( void )
171{
172 VikLayerParamData data;
173#ifdef WINDOWS
174 data.s = g_strdup ( "input" );
175#else
176 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
177 data.s = g_strdup ( "/usr/lib/mapnik/input" );
178 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
179 // Current Debian location
180 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
181 else
182 data.s = g_strdup ( "" );
183#endif
184 return data;
185}
186
187static VikLayerParamData fonts_default ( void )
188{
189 // Possibly should be string list to allow loading from multiple directories
190 VikLayerParamData data;
191#ifdef WINDOWS
192 data.s = g_strdup ( "C:\\Windows\\Fonts" );
193#elif defined __APPLE__
194 data.s = g_strdup ( "/Library/Fonts" );
195#else
196 data.s = g_strdup ( "/usr/share/fonts" );
197#endif
198 return data;
199}
200
201static VikLayerParam prefs[] = {
202 // Changing these values only applies before first mapnik layer is 'created'
203 { 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 },
204 { 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 },
205 { 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 },
206};
207
208// NB Only performed once per program run
209static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
210{
211 mapnik_interface_init ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
212 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
213 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
214}
215
216void vik_mapnik_layer_init (void)
217{
218 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
219
220 guint i = 0;
221 VikLayerParamData tmp = plugins_default();
222 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
223
224 tmp = fonts_default();
225 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
226
227 tmp.b = TRUE;
228 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
229}
230
231GType vik_mapnik_layer_get_type ()
232{
233 static GType vml_type = 0;
234
235 if (!vml_type) {
236 static const GTypeInfo vml_info = {
237 sizeof (VikMapnikLayerClass),
238 NULL, /* base_init */
239 NULL, /* base_finalize */
240 (GClassInitFunc) mapnik_layer_class_init, /* class init */
241 NULL, /* class_finalize */
242 NULL, /* class_data */
243 sizeof (VikMapnikLayer),
244 0,
245 NULL /* instance init */
246 };
247 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
248 }
249
250 return vml_type;
251}
252
253static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
254{
255 return vml->filename_xml;
256}
257
258static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
259{
260 if ( vml->filename_xml )
261 g_free (vml->filename_xml);
262 vml->filename_xml = g_strdup (name);
263}
264
265static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
266{
267 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
268}
269
270static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
271{
272 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
273 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
274 return rv;
275}
276
277static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
278{
279 switch ( id ) {
280 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
281 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
282 default: break;
283 }
284 return TRUE;
285}
286
287static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
288{
289 VikLayerParamData data;
290 switch ( id ) {
291 case PARAM_CONFIG_XML: {
292 data.s = vml->filename_xml;
293 gboolean set = FALSE;
294 if ( is_file_operation ) {
295 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
296 gchar *cwd = g_get_current_dir();
297 if ( cwd ) {
298 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
299 if ( !data.s ) data.s = "";
300 set = TRUE;
301 }
302 }
303 }
304 if ( !set )
305 data.s = vml->filename_xml ? vml->filename_xml : "";
306 break;
307 }
308 case PARAM_ALPHA: data.u = vml->alpha; break;
309 //case PARAM_TILE_X: data.u = vml->tile_size_x; break;
310 default: break;
311 }
312 return data;
313}
314
315/**
316 *
317 */
318static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
319{
320 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
321 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
322 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
323 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
324 vml->loaded = FALSE;
325 return vml;
326}
327
328/**
329 *
330 */
331static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
332{
333 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
334
335 if ( mapnik_interface_load_map_file ( vml->filename_xml, vml->tile_size_x, vml->tile_size_x ) ) {
336 if ( !from_file )
337 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
338 _("Mapnik error loading configuration file: %s"),
339 vml->filename_xml );
340 else
341 g_warning ( _("Mapnik error loading configuration file: %s"), vml->filename_xml );
342 }
343 else {
344 vml->loaded = TRUE;
345 if ( !from_file )
346 ui_add_recent_file ( vml->filename_xml );
347 }
348}
349
350/**
351 *
352 */
353static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
354{
355 VikCoord ul; VikCoord br;
356 GdkPixbuf *pixbuf;
357
358 map_utils_iTMS_to_vikcoord (ulm, &ul);
359 map_utils_iTMS_to_vikcoord (brm, &br);
360
361 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 );
362
363 if ( ! pixbuf ) {
364 pixbuf = mapnik_interface_render ( vml->mi, ul.north_south, ul.east_west, br.north_south, br.east_west );
365 if ( pixbuf ) {
366 // NB Mapnik can apply alpha, but use our own function for now
367 if ( vml->alpha < 255 )
368 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
369 a_mapcache_add ( pixbuf, ulm->x, ulm->y, ulm->z, MAPNIK_LAYER_MAP_TYPE, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
370 }
371 }
372
373 return pixbuf;
374}
375
376/**
377 *
378 */
379static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
380{
381 if ( !vml->loaded )
382 return;
383
384 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
385 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
386 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
387 return;
388 }
389
390 VikCoord ul, br;
391 ul.mode = VIK_COORD_LATLON;
392 br.mode = VIK_COORD_LATLON;
393 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
394 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
395
396 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
397 gdouble yzoom = vik_viewport_get_ympp ( vvp );
398
399 MapCoord ulm, brm;
400
401 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
402 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
403 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
404 GdkPixbuf *pixbuf;
405 VikCoord coord;
406 gint xx, yy;
407
408 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
409 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
410
411 // Split rendering into a grid for the current viewport
412 // thus each individual 'tile' can then be stored in the map cache
413 // TODO: Also potentially allows using multi threads for each individual tile
414 // this is more important for complicated stylesheets/datasources as each rendering may take some time
415 for (gint x = xmin; x <= xmax; x++ ) {
416 for (gint y = ymin; y <= ymax; y++ ) {
417 ulm.x = x;
418 ulm.y = y;
419 brm.x = x+1;
420 brm.y = y+1;
421
422 pixbuf = get_pixbuf ( vml, &ulm, &brm );
423
424 if ( pixbuf ) {
425 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
426 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
427 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
428 }
429 }
430 }
431
432 // Done after so drawn on top
433 // Just a handy guide to tile blocks.
434 if ( vik_debug && vik_verbose ) {
435 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
436 gint width = vik_viewport_get_width(vvp);
437 gint height = vik_viewport_get_height(vvp);
438 gint xx, yy;
439 ulm.x = xmin; ulm.y = ymin;
440 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
441 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
442 xx = xx - (vml->tile_size_x/2);
443 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
444 for (gint x = xmin; x <= xmax; x++ ) {
445 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
446 xx += vml->tile_size_x;
447 }
448 for (gint y = ymin; y <= ymax; y++ ) {
449 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
450 yy += vml->tile_size_x; // Yes use X ATM
451 }
452 }
453 }
454}
455
456/**
457 *
458 */
459static void mapnik_layer_free ( VikMapnikLayer *vml )
460{
461 if ( vml->filename_xml )
462 g_free ( vml->filename_xml );
463}
464
465static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
466{
467 return mapnik_layer_new ( vp );
468}
469
470typedef enum {
471 MA_VML = 0,
472 MA_VVP,
473 MA_LAST
474} menu_array_index;
475
476typedef gpointer menu_array_values[MA_LAST];
477
478/**
479 *
480 */
481static void mapnik_layer_flush_memory ( menu_array_values values )
482{
483 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
484}
485
486/**
487 *
488 */
489static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
490{
491 static menu_array_values values;
492 values[MA_VML] = vml;
493 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
494
495 GtkWidget *item = gtk_menu_item_new();
496 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
497 gtk_widget_show ( item );
498
499 // Typical users shouldn't need to use this functionality - so debug only ATM
500 if ( vik_debug ) {
501 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
502 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
503 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
504 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
505 gtk_widget_show ( item );
506 }
507}