]> git.street.me.uk Git - andy/viking.git/blob - src/mapnik_interface.cpp
Merge branch 'MapnikRenderingLayer'
[andy/viking.git] / src / mapnik_interface.cpp
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2015, Rob Norris <rw_norris@hotmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 #include <mapnik/version.hpp>
23 #include <mapnik/map.hpp>
24 #include <mapnik/datasource_cache.hpp>
25 #include <mapnik/agg_renderer.hpp>
26 #include <mapnik/load_map.hpp>
27 #include <mapnik/graphics.hpp>
28 #include <mapnik/image_util.hpp>
29
30 #include <exception>
31 #include <iostream>
32 #include <memory>
33 #include <string>
34 #include <stdlib.h>
35
36 #include <glib.h>
37 #include <glib/gstdio.h>
38 #include <glib/gi18n.h>
39
40 #include "mapnik_interface.h"
41 #include "globals.h"
42 #include "settings.h"
43
44 #if MAPNIK_VERSION < 200000
45 #include <mapnik/envelope.hpp>
46 #define image_32 Image32
47 #define image_data_32 ImageData32
48 #define box2d Envelope
49 #define zoom_to_box zoomToBox
50 #else
51 #include <mapnik/box2d.hpp>
52 #endif
53
54 #define MAPNIK_INTERFACE_TYPE            (mapnik_interface_get_type ())
55 #define MAPNIK_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAPNIK_INTERFACE_TYPE, MapnikInterface))
56 #define MAPNIK_INTERFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), MAPNIK_INTERFACE_TYPE, MapnikInterfaceClass))
57 #define IS_MAPNIK_INTERFACE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAPNIK_INTERFACE_TYPE))
58 #define IS_MAPNIK_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MAPNIK_INTERFACE_TYPE))
59
60 typedef struct _MapnikInterfaceClass MapnikInterfaceClass;
61 typedef struct _MapnikInterface MapnikInterface;
62
63 GType mapnik_interface_get_type ();
64 struct _MapnikInterfaceClass
65 {
66         GObjectClass object_class;
67 };
68
69 static void mapnik_interface_class_init ( MapnikInterfaceClass *mic )
70 {
71 }
72
73 static void mapnik_interface_init ( MapnikInterface *mi )
74 {
75 }
76
77 struct _MapnikInterface {
78         GObject obj;
79         mapnik::Map *myMap;
80         gchar *copyright; // Cached Mapnik parameter to save looking it up each time
81 };
82
83 G_DEFINE_TYPE (MapnikInterface, mapnik_interface, G_TYPE_OBJECT)
84
85 // Can't change prj after init - but ATM only support drawing in Spherical Mercator
86 static mapnik::projection prj( mapnik::MAPNIK_GMERC_PROJ );
87
88 MapnikInterface* mapnik_interface_new ()
89 {
90         MapnikInterface* mi = MAPNIK_INTERFACE ( g_object_new ( MAPNIK_INTERFACE_TYPE, NULL ) );
91         mi->myMap = new mapnik::Map;
92         mi->copyright = NULL;
93         return mi;
94 }
95
96 void mapnik_interface_free (MapnikInterface* mi)
97 {
98         if ( mi ) {
99                 g_free ( mi->copyright );
100                 delete mi->myMap;
101         }
102         g_object_unref ( G_OBJECT(mi) );
103 }
104
105 /**
106  * mapnik_interface_initialize:
107  */
108 void mapnik_interface_initialize (const char *plugins_dir, const char* font_dir, int font_dir_recurse)
109 {
110         g_debug ("using mapnik version %s", MAPNIK_VERSION_STRING );
111         try {
112                 if ( plugins_dir )
113 #if MAPNIK_VERSION >= 200200
114                         mapnik::datasource_cache::instance().register_datasources(plugins_dir);
115 #else
116                         mapnik::datasource_cache::instance()->register_datasources(plugins_dir);
117 #endif
118                 if ( font_dir )
119                         if ( ! mapnik::freetype_engine::register_fonts(font_dir, font_dir_recurse ? true : false) )
120                                 g_warning ("%s: No fonts found", __FUNCTION__);
121         } catch (std::exception const& ex) {
122                 g_warning ("An error occurred while initialising mapnik: %s", ex.what());
123         } catch (...) {
124                 g_warning ("An unknown error occurred while initialising mapnik");
125         }
126 }
127
128 /**
129  *  caching this answer instead of looking it up each time
130  */
131 static void set_copyright ( MapnikInterface* mi )
132 {
133         if ( mi->copyright )
134                 g_free ( mi->copyright );
135
136         mapnik::parameters pmts = mi->myMap->get_extra_parameters();
137         for (mapnik::parameters::const_iterator ii = pmts.begin(); ii != pmts.end(); ii++) {
138                 if ( ii->first == "attribution" || ii->first == "copyright" ) {
139                         std::stringstream ss;
140                         ss << ii->second;
141                         // Copy it
142                         mi->copyright = g_strdup ( (gchar*)ss.str().c_str() );
143                 }
144         }
145 }
146
147 #define VIK_SETTINGS_MAPNIK_BUFFER_SIZE "mapnik_buffer_size"
148
149 /**
150  * mapnik_interface_load_map_file:
151  *
152  * Returns NULL on success otherwise a string about what went wrong.
153  *  This string should be freed once it has been used.
154  */
155 gchar* mapnik_interface_load_map_file ( MapnikInterface* mi,
156                                         const gchar *filename,
157                                         guint width,
158                                         guint height )
159 {
160         gchar *msg = NULL;
161         if ( !mi ) return g_strdup ("Internal Error");
162         try {
163                 mi->myMap->remove_all(); // Support reloading
164                 mapnik::load_map(*mi->myMap, filename);
165
166                 mi->myMap->resize(width,height);
167                 mi->myMap->set_srs ( mapnik::MAPNIK_GMERC_PROJ ); // ONLY WEB MERCATOR output supported ATM
168
169                 // IIRC This size is the number of pixels outside the tile to be considered so stuff is shown (i.e. particularly labels)
170                 // Only set buffer size if the buffer size isn't explicitly set in the mapnik stylesheet.
171                 // Alternatively render a bigger 'virtual' tile and then only use the appropriate subset
172                 if (mi->myMap->buffer_size() == 0) {
173                         gint buffer_size = (width+height/4); // e.g. 128 for a 256x256 image.
174                         gint tmp;
175                         if ( a_settings_get_integer ( VIK_SETTINGS_MAPNIK_BUFFER_SIZE, &tmp ) )
176                                 buffer_size = tmp;
177
178                         mi->myMap->set_buffer_size(buffer_size);
179                 }
180
181                 set_copyright ( mi );
182
183                 g_debug ("%s layers: %d", __FUNCTION__, mi->myMap->layer_count() );
184         } catch (std::exception const& ex) {
185                 msg = g_strdup ( ex.what() );
186         } catch (...) {
187                 msg = g_strdup ("unknown error");
188         }
189         return msg;
190 }
191
192 // These two functions copied from gpsdrive 2.11
193 /**
194  * convert the color channel
195  */
196 inline unsigned char
197 convert_color_channel (unsigned char Source, unsigned char Alpha)
198 {
199         return Alpha ? ((Source << 8) - Source) / Alpha : 0;
200 }
201
202 /**
203  * converting argb32 to gdkpixbuf
204  */
205 static void
206 convert_argb32_to_gdkpixbuf_data (unsigned char const *Source, unsigned int width, unsigned int height, unsigned char *Dest)
207 {
208         unsigned char const *SourcePixel = Source;
209         unsigned char *DestPixel = Dest;
210         for (int y = 0; y < height; y++) {
211                 for (int x = 0; x < width; x++) {
212                         DestPixel[0] = convert_color_channel(SourcePixel[0], SourcePixel[3]);
213                         DestPixel[1] = convert_color_channel(SourcePixel[1], SourcePixel[3]);
214                         DestPixel[2] = convert_color_channel(SourcePixel[2], SourcePixel[3]);
215                         DestPixel += 3;
216                         SourcePixel += 4;
217                 }
218         }
219 }
220
221 /**
222  * mapnik_interface_render:
223  *
224  * Returns a #GdkPixbuf of the specified area. #GdkPixbuf may be NULL
225  */
226 GdkPixbuf* mapnik_interface_render ( MapnikInterface* mi, double lat_tl, double lon_tl, double lat_br, double lon_br )
227 {
228         if ( !mi ) return NULL;
229
230         // Copy main object to local map variable
231         //  This enables rendering to work when this function is called from different threads
232         mapnik::Map myMap(*mi->myMap);
233
234         // Note prj & bbox want stuff in lon,lat order!
235         double p0x = lon_tl;
236         double p0y = lat_tl;
237         double p1x = lon_br;
238         double p1y = lat_br;
239
240         // Convert into projection coordinates for bbox
241         prj.forward(p0x, p0y);
242         prj.forward(p1x, p1y);
243
244         GdkPixbuf *pixbuf = NULL;
245         try {
246                 unsigned width  = myMap.width();
247                 unsigned height = myMap.height();
248                 mapnik::image_32 image(width,height);
249                 mapnik::box2d<double> bbox(p0x, p0y, p1x, p1y);
250                 myMap.zoom_to_box(bbox);
251                 // FUTURE: option to use cairo / grid renderers?
252                 mapnik::agg_renderer<mapnik::image_32> render(myMap,image);
253                 render.apply();
254
255                 if ( image.painted() ) {
256                         unsigned char *ImageRawDataPtr = (unsigned char *) g_malloc(width * 3 * height);
257                         if (!ImageRawDataPtr)
258                                 return NULL;
259                         convert_argb32_to_gdkpixbuf_data(image.raw_data(), width, height, ImageRawDataPtr);
260                         pixbuf = gdk_pixbuf_new_from_data(ImageRawDataPtr, GDK_COLORSPACE_RGB, FALSE, 8, width, height, width * 3, NULL, NULL);
261                 }
262                 else
263                         g_warning ("%s not rendered", __FUNCTION__ );
264         }
265         catch (const std::exception & ex) {
266                 g_warning ("An error occurred while rendering: %s", ex.what());
267         } catch (...) {
268                 g_warning ("An unknown error occurred while rendering");
269         }
270
271         return pixbuf;
272 }
273
274 /**
275  * Copyright/Attribution information about the Map - string maybe NULL
276  *
277  * Free returned string  after use
278  */
279 gchar* mapnik_interface_get_copyright ( MapnikInterface* mi )
280 {
281         if ( !mi ) return NULL;
282         return mi->copyright;
283 }
284
285 /**
286  * 'Parameter' information about the Map configuration
287  *
288  * Free every string element and the returned GArray itself after use
289  */
290 GArray* mapnik_interface_get_parameters ( MapnikInterface* mi )
291 {
292         GArray *array = g_array_new (FALSE, TRUE, sizeof(gchar*));
293
294         mapnik::parameters pmts = mi->myMap->get_extra_parameters();
295         for (mapnik::parameters::const_iterator ii = pmts.begin(); ii != pmts.end(); ii++) {
296                 // Dodgy hacking to avoid using boost or mapnik::utils visitor stuff (not available in mapnik 2.2)
297                 // Simply want the strings of each parameter so we can display something...
298                 std::stringstream ss;
299                 ss << ii->first << ": " << ii->second;
300                 // Copy - otherwise ss goes output scope and junk memory would be referenced.
301                 gchar *str2 = g_strdup ( (gchar*)ss.str().c_str() );
302                 g_array_append_val ( array, str2 );
303         }
304
305         return array;
306 }
307
308 /**
309  * General information about Mapnik
310  *
311  * Free the returned string after use
312  */
313 gchar * mapnik_interface_about ( void )
314 {
315         // Normally about 10 plugins so list them all
316 #if MAPNIK_VERSION >= 200200
317         std::vector<std::string> plugins = mapnik::datasource_cache::instance().plugin_names();
318 #else
319         std::vector<std::string> plugins = mapnik::datasource_cache::instance()->plugin_names();
320 #endif
321         std::string str;
322         for (int nn = 0; nn < plugins.size(); nn++ )
323                 str += plugins[nn] + ',';
324         str += '\n';
325         // NB Can have a couple hundred fonts loaded when using system directories
326         //  So ATM don't list them all - otherwise need better GUI feedback display.
327         gchar *msg = g_strdup_printf ( _("%s %s\nPlugins=%sFonts loaded=%d"),
328                                        _("Mapnik"),
329                                        MAPNIK_VERSION_STRING,
330                                        str.c_str(),
331                                        mapnik::freetype_engine::face_names().size() );
332         return msg;
333 }