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