]>
Commit | Line | Data |
---|---|---|
5fa4fe86 RN |
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> | |
616812b7 RN |
27 | #include <mapnik/projection.hpp> |
28 | #if MAPNIK_VERSION < 300000 | |
5fa4fe86 | 29 | #include <mapnik/graphics.hpp> |
616812b7 RN |
30 | #else |
31 | #include <mapnik/value.hpp> | |
32 | #endif | |
5fa4fe86 RN |
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> | |
c064c64c | 43 | #include <glib/gi18n.h> |
5fa4fe86 RN |
44 | |
45 | #include "mapnik_interface.h" | |
46 | #include "globals.h" | |
54a451e9 | 47 | #include "settings.h" |
5fa4fe86 RN |
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> | |
616812b7 RN |
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 | |
5fa4fe86 RN |
62 | #endif |
63 | ||
0658c2f1 RN |
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; | |
465559f1 | 90 | gchar *copyright; // Cached Mapnik parameter to save looking it up each time |
0658c2f1 RN |
91 | }; |
92 | ||
93 | G_DEFINE_TYPE (MapnikInterface, mapnik_interface, G_TYPE_OBJECT) | |
5fa4fe86 RN |
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 | ||
0658c2f1 RN |
98 | MapnikInterface* mapnik_interface_new () |
99 | { | |
100 | MapnikInterface* mi = MAPNIK_INTERFACE ( g_object_new ( MAPNIK_INTERFACE_TYPE, NULL ) ); | |
101 | mi->myMap = new mapnik::Map; | |
465559f1 | 102 | mi->copyright = NULL; |
0658c2f1 RN |
103 | return mi; |
104 | } | |
105 | ||
106 | void mapnik_interface_free (MapnikInterface* mi) | |
107 | { | |
465559f1 RN |
108 | if ( mi ) { |
109 | g_free ( mi->copyright ); | |
0658c2f1 | 110 | delete mi->myMap; |
465559f1 | 111 | } |
0658c2f1 RN |
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) | |
5fa4fe86 RN |
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); | |
5fa4fe86 RN |
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__); | |
5fa4fe86 RN |
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 | ||
465559f1 RN |
138 | /** |
139 | * caching this answer instead of looking it up each time | |
140 | */ | |
141 | static void set_copyright ( MapnikInterface* mi ) | |
142 | { | |
616812b7 RN |
143 | g_free ( mi->copyright ); |
144 | mi->copyright = NULL; | |
465559f1 RN |
145 | |
146 | mapnik::parameters pmts = mi->myMap->get_extra_parameters(); | |
616812b7 | 147 | #if MAPNIK_VERSION < 300000 |
465559f1 RN |
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() ); | |
616812b7 | 154 | break; |
465559f1 RN |
155 | } |
156 | } | |
616812b7 RN |
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 | |
465559f1 RN |
165 | } |
166 | ||
54a451e9 RN |
167 | #define VIK_SETTINGS_MAPNIK_BUFFER_SIZE "mapnik_buffer_size" |
168 | ||
5fa4fe86 RN |
169 | /** |
170 | * mapnik_interface_load_map_file: | |
171 | * | |
76bb97a4 RN |
172 | * Returns NULL on success otherwise a string about what went wrong. |
173 | * This string should be freed once it has been used. | |
5fa4fe86 | 174 | */ |
76bb97a4 RN |
175 | gchar* mapnik_interface_load_map_file ( MapnikInterface* mi, |
176 | const gchar *filename, | |
177 | guint width, | |
178 | guint height ) | |
5fa4fe86 | 179 | { |
76bb97a4 RN |
180 | gchar *msg = NULL; |
181 | if ( !mi ) return g_strdup ("Internal Error"); | |
5fa4fe86 | 182 | try { |
0658c2f1 RN |
183 | mi->myMap->remove_all(); // Support reloading |
184 | mapnik::load_map(*mi->myMap, filename); | |
5fa4fe86 | 185 | |
0658c2f1 RN |
186 | mi->myMap->resize(width,height); |
187 | mi->myMap->set_srs ( mapnik::MAPNIK_GMERC_PROJ ); // ONLY WEB MERCATOR output supported ATM | |
5fa4fe86 RN |
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 | |
0658c2f1 | 192 | if (mi->myMap->buffer_size() == 0) { |
54a451e9 RN |
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); | |
5fa4fe86 RN |
199 | } |
200 | ||
465559f1 RN |
201 | set_copyright ( mi ); |
202 | ||
304c1942 | 203 | g_debug ("%s layers: %d", __FUNCTION__, (guint)mi->myMap->layer_count() ); |
5fa4fe86 | 204 | } catch (std::exception const& ex) { |
76bb97a4 | 205 | msg = g_strdup ( ex.what() ); |
5fa4fe86 | 206 | } catch (...) { |
76bb97a4 | 207 | msg = g_strdup ("unknown error"); |
5fa4fe86 | 208 | } |
76bb97a4 | 209 | return msg; |
5fa4fe86 RN |
210 | } |
211 | ||
5fa4fe86 RN |
212 | |
213 | /** | |
214 | * mapnik_interface_render: | |
215 | * | |
216 | * Returns a #GdkPixbuf of the specified area. #GdkPixbuf may be NULL | |
217 | */ | |
0658c2f1 | 218 | GdkPixbuf* mapnik_interface_render ( MapnikInterface* mi, double lat_tl, double lon_tl, double lat_br, double lon_br ) |
5fa4fe86 | 219 | { |
0658c2f1 RN |
220 | if ( !mi ) return NULL; |
221 | ||
892a9205 RN |
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 | ||
5fa4fe86 RN |
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 { | |
892a9205 RN |
238 | unsigned width = myMap.width(); |
239 | unsigned height = myMap.height(); | |
5fa4fe86 RN |
240 | mapnik::image_32 image(width,height); |
241 | mapnik::box2d<double> bbox(p0x, p0y, p1x, p1y); | |
892a9205 | 242 | myMap.zoom_to_box(bbox); |
5fa4fe86 | 243 | // FUTURE: option to use cairo / grid renderers? |
892a9205 | 244 | mapnik::agg_renderer<mapnik::image_32> render(myMap,image); |
5fa4fe86 RN |
245 | render.apply(); |
246 | ||
247 | if ( image.painted() ) { | |
53991e79 | 248 | unsigned char *ImageRawDataPtr = (unsigned char *) g_malloc(width * 4 * height); |
5fa4fe86 RN |
249 | if (!ImageRawDataPtr) |
250 | return NULL; | |
53991e79 MH |
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); | |
5fa4fe86 RN |
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 | } | |
c064c64c | 265 | |
465559f1 RN |
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 | ||
581feeec RN |
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(); | |
616812b7 RN |
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++) { | |
581feeec | 290 | std::stringstream ss; |
616812b7 RN |
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 | |
581feeec RN |
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 | ||
c064c64c RN |
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; | |
ce7af119 | 319 | for (uint nn = 0; nn < plugins.size(); nn++ ) |
c064c64c RN |
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(), | |
304c1942 | 328 | (guint)mapnik::freetype_engine::face_names().size() ); |
c064c64c RN |
329 | return msg; |
330 | } |