]> git.street.me.uk Git - andy/viking.git/blob - src/vikmapniklayer.c
A Mapnik Rendering layer
[andy/viking.git] / src / vikmapniklayer.c
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
46 struct _VikMapnikLayerClass
47 {
48         VikLayerClass object_class;
49 };
50
51 static VikLayerParamData file_default ( void )
52 {
53         VikLayerParamData data;
54         data.s = "";
55         return data;
56 }
57
58 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
59 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
60
61 static VikLayerParamScale scales[] = {
62         { 0, 255, 5, 0 }, // Alpha
63         { 64, 1024, 8, 0 }, // Tile size
64 };
65
66 VikLayerParam 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
73 enum {
74         PARAM_CONFIG_XML = 0,
75         PARAM_ALPHA,
76         NUM_PARAMS };
77
78 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
79 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
80 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
81 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
82 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
83 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
84 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
85 static void mapnik_layer_free ( VikMapnikLayer *vml );
86 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
87 static 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:
91 static VikToolInterface mapnik_tools[] = {
92         // Layer Info
93         // Zoom All?
94 };
95
96 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
97
98 VikLayerInterface 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
158 struct _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
170 static 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
187 static 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
201 static 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
209 static 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
216 void 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
231 GType 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
253 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
254 {
255         return vml->filename_xml;
256 }
257
258 static 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
265 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
266 {
267         vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
268 }
269
270 static 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
277 static 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
287 static 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  */
318 static 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  */
331 static 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  */
353 static 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  */
379 static 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  */
459 static void mapnik_layer_free ( VikMapnikLayer *vml )
460 {
461         if ( vml->filename_xml )
462                 g_free ( vml->filename_xml );
463 }
464
465 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
466 {
467         return mapnik_layer_new ( vp );
468 }
469
470 typedef enum {
471         MA_VML = 0,
472         MA_VVP,
473         MA_LAST
474 } menu_array_index;
475
476 typedef gpointer menu_array_values[MA_LAST];
477
478 /**
479  *
480  */
481 static void mapnik_layer_flush_memory ( menu_array_values values )
482 {
483         a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
484 }
485
486 /**
487  *
488  */
489 static 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 }