]> git.street.me.uk Git - andy/viking.git/blame - src/kmz.c
Remove definition of a non existant function
[andy/viking.git] / src / kmz.c
CommitLineData
49d43d14
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#include "kmz.h"
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27#ifdef HAVE_ZIP_H
28#include <zip.h>
29#endif
ab922442
RN
30#include "viking.h"
31#include <string.h>
49d43d14
RN
32#include <stdio.h>
33#include <glib/gstdio.h>
ab922442
RN
34#ifdef HAVE_EXPAT_H
35#include <expat.h>
36#endif
37#include <math.h>
49d43d14
RN
38
39#ifdef HAVE_ZIP_H
40/**
41 * Simple KML 'file' with a Ground Overlay
42 *
43 * See https://developers.google.com/kml/documentation/kmlreference
44 *
45 * AFAIK the projection is always in Web Mercator
46 * Probably for normal use case of not too large an area coverage (on a Garmin device) the projection is near enough...
47 */
48// Hopefully image_filename will not break the XML file tag structure
49static gchar* doc_kml_str ( const gchar *name, const gchar *image_filename, gdouble north, gdouble south, gdouble east, gdouble west )
50{
51 gchar *tmp_n = a_coords_dtostr ( north );
52 gchar *tmp_s = a_coords_dtostr ( south );
53 gchar *tmp_e = a_coords_dtostr ( east );
54 gchar *tmp_w = a_coords_dtostr ( west );
55
56 gchar *doc_kml = g_strdup_printf (
57 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
58 "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\" xmlns:kml=\"http://www.opengis.net/kml/2.2\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n"
59 "<GroundOverlay>\n"
60 " <name>%s</name>\n"
61 " <Icon>\n"
62 " <href>%s</href>\n"
63 " </Icon>\n"
64 " <LatLonBox>\n"
65 " <north>%s</north>\n"
66 " <south>%s</south>\n"
67 " <east>%s</east>\n"
68 " <west>%s</west>\n"
69 " <rotation>0</rotation>\n" // Rotation always zero
70 " </LatLonBox>\n"
71 "</GroundOverlay>\n"
72 "</kml>\n",
73 name, image_filename, tmp_n, tmp_s, tmp_e, tmp_w );
74
75 g_free ( tmp_n );
76 g_free ( tmp_s );
77 g_free ( tmp_e );
78 g_free ( tmp_w );
79
80 return doc_kml;
81}
82#endif
83
84/**
85 * kmz_save_file:
86 *
87 * @pixbuf: The image to save
88 * @filename: Save the KMZ as this filename
89 * @north: Top latitude in degrees
90 * @east: Right most longitude in degrees
91 * @south: Bottom latitude in degrees
92 * @west: Left most longitude in degrees
93 *
94 * Returns:
95 * -1 if KMZ not supported (this shouldn't happen)
96 * 0 on success
97 * >0 some kind of error
98 *
99 * Mostly intended for use with as a Custom Map on a Garmin
100 *
101 * See http://garminbasecamp.wikispaces.com/Custom+Maps
102 *
103 * The KMZ is a zipped file containing a KML file with the associated image
104 */
105int kmz_save_file ( GdkPixbuf *pixbuf, const gchar* filename, gdouble north, gdouble east, gdouble south, gdouble west )
106{
107#ifdef HAVE_ZIP_H
108// Older libzip compatibility:
109#ifndef zip_source_t
110typedef struct zip_source zip_source_t;
111#endif
112
113 int ans = ZIP_ER_OK;
114 gchar *image_filename = "image.jpg";
115
116 // Generate KMZ file (a zip file)
117 struct zip* archive = zip_open ( filename, ZIP_CREATE | ZIP_TRUNCATE, &ans );
118 if ( !archive ) {
119 g_warning ( "Unable to create archive: '%s' Error code %d", filename, ans );
120 goto finish;
121 }
122
123 // Generate KML file
124 gchar *dk = doc_kml_str ( a_file_basename(filename), image_filename, north, south, east, west );
125 int dkl = strlen ( dk );
126
127 // KML must be named doc.kml in the kmz file
128 zip_source_t *src_kml = zip_source_buffer ( archive, dk, dkl, 0 );
129 zip_file_add ( archive, "doc.kml", src_kml, ZIP_FL_OVERWRITE );
130
131 GError *error = NULL;
132 gchar *buffer;
133 gsize blen;
134 gdk_pixbuf_save_to_buffer ( pixbuf, &buffer, &blen, "jpeg", &error, "x-dpi", "72", "y-dpi", "72", NULL );
135 if ( error ) {
136 g_warning ( "Save to buffer error: %s", error->message );
137 g_error_free (error);
138 zip_discard ( archive );
139 ans = 130;
140 goto kml_cleanup;
141 }
142
143 zip_source_t *src_img = zip_source_buffer ( archive, buffer, (int)blen, 0 );
144 zip_file_add ( archive, image_filename, src_img, ZIP_FL_OVERWRITE );
145 // NB Only store as limited use trying to (further) compress a JPG
146 zip_set_file_compression ( archive, 1, ZIP_CM_STORE, 0 );
147
148 ans = zip_close ( archive );
149
150 g_free ( buffer );
151
152 kml_cleanup:
153 g_free ( dk );
154 finish:
155 return ans;
156#else
157 return -1;
158#endif
159}
160
ab922442
RN
161typedef enum {
162 tt_unknown = 0,
163 tt_kml,
164 tt_kml_go,
165 tt_kml_go_name,
166 tt_kml_go_image,
167 tt_kml_go_latlonbox,
168 tt_kml_go_latlonbox_n,
169 tt_kml_go_latlonbox_e,
170 tt_kml_go_latlonbox_s,
171 tt_kml_go_latlonbox_w,
172} xtag_type;
173
174typedef struct {
175 xtag_type tag_type; /* enum from above for this tag */
176 const char *tag_name; /* xpath-ish tag name */
177} xtag_mapping;
178
179typedef struct {
180 GString *xpath;
181 GString *c_cdata;
182 xtag_type current_tag;
183 gchar *name;
184 gchar *image; // AKA icon
185 gdouble north;
186 gdouble east;
187 gdouble south;
188 gdouble west;
189} xml_data;
190
191#ifdef HAVE_ZIP_H
192#ifdef HAVE_EXPAT_H
193// NB No support for orientation ATM
194static xtag_mapping xtag_path_map[] = {
195 { tt_kml, "/kml" },
196 { tt_kml_go, "/kml/GroundOverlay" },
197 { tt_kml_go_name, "/kml/GroundOverlay/name" },
198 { tt_kml_go_image, "/kml/GroundOverlay/Icon/href" },
199 { tt_kml_go_latlonbox, "/kml/GroundOverlay/LatLonBox" },
200 { tt_kml_go_latlonbox_n, "/kml/GroundOverlay/LatLonBox/north" },
201 { tt_kml_go_latlonbox_e, "/kml/GroundOverlay/LatLonBox/east" },
202 { tt_kml_go_latlonbox_s, "/kml/GroundOverlay/LatLonBox/south" },
203 { tt_kml_go_latlonbox_w, "/kml/GroundOverlay/LatLonBox/west" },
204};
205
206// NB Don't be pedantic about matching case of strings for tags
207static xtag_type get_tag ( const char *t )
208{
209 xtag_mapping *tm;
210 for (tm = xtag_path_map; tm->tag_type != 0; tm++)
211 if (0 == g_ascii_strcasecmp(tm->tag_name, t))
212 return tm->tag_type;
213 return tt_unknown;
214}
215
216static void kml_start ( xml_data *xd, const char *el, const char **attr )
217{
218 g_string_append_c ( xd->xpath, '/' );
219 g_string_append ( xd->xpath, el );
220
221 xd->current_tag = get_tag ( xd->xpath->str );
222 switch ( xd->current_tag ) {
223 case tt_kml_go_name:
224 case tt_kml_go_image:
225 case tt_kml_go_latlonbox_n:
226 case tt_kml_go_latlonbox_s:
227 case tt_kml_go_latlonbox_e:
228 case tt_kml_go_latlonbox_w:
229 g_string_erase ( xd->c_cdata, 0, -1 );
230 break;
231 default: break; // ignore cdata from other things
232 }
233}
234
235static void kml_end ( xml_data *xd, const char *el )
236{
237 g_string_truncate ( xd->xpath, xd->xpath->len - strlen(el) - 1 );
238
239 switch ( xd->current_tag ) {
240 case tt_kml_go_name:
241 xd->name = g_strdup ( xd->c_cdata->str );
242 g_string_erase ( xd->c_cdata, 0, -1 );
243 break;
244 case tt_kml_go_image:
245 xd->image = g_strdup ( xd->c_cdata->str );
246 g_string_erase ( xd->c_cdata, 0, -1 );
247 break;
248 case tt_kml_go_latlonbox_n:
249 xd->north = g_ascii_strtod ( xd->c_cdata->str, NULL );
250 g_string_erase ( xd->c_cdata, 0, -1 );
251 break;
252 case tt_kml_go_latlonbox_s:
253 xd->south = g_ascii_strtod ( xd->c_cdata->str, NULL );
254 g_string_erase ( xd->c_cdata, 0, -1 );
255 break;
256 case tt_kml_go_latlonbox_e:
257 xd->east = g_ascii_strtod ( xd->c_cdata->str, NULL );
258 g_string_erase ( xd->c_cdata, 0, -1 );
259 break;
260 case tt_kml_go_latlonbox_w:
261 xd->west = g_ascii_strtod ( xd->c_cdata->str, NULL );
262 g_string_erase ( xd->c_cdata, 0, -1 );
263 break;
264 default:
265 break;
266 }
267
268 xd->current_tag = get_tag ( xd->xpath->str );
269}
270
271static void kml_cdata ( xml_data *xd, const XML_Char *s, int len )
272{
273 switch ( xd->current_tag ) {
274 case tt_kml_go_name:
275 case tt_kml_go_image:
276 case tt_kml_go_latlonbox_n:
277 case tt_kml_go_latlonbox_s:
278 case tt_kml_go_latlonbox_e:
279 case tt_kml_go_latlonbox_w:
280 g_string_append_len ( xd->c_cdata, s, len );
281 break;
282 default: break; // ignore cdata from other things
283 }
284}
285#endif
286
287/**
288 *
289 */
290static gboolean parse_kml ( const char* buffer, int len, gchar **name, gchar **image, gdouble *north, gdouble *south, gdouble *east, gdouble *west )
291{
292#ifdef HAVE_EXPAT_H
293 XML_Parser parser = XML_ParserCreate(NULL);
294 enum XML_Status status = XML_STATUS_ERROR;
295
296 xml_data *xd = g_malloc ( sizeof (xml_data) );
297 // Set default (invalid) values;
298 xd->xpath = g_string_new ( "" );
299 xd->c_cdata = g_string_new ( "" );
300 xd->current_tag = tt_unknown;
301 xd->north = NAN;
302 xd->south = NAN;
303 xd->east = NAN;
304 xd->west = NAN;
305 xd->name = NULL;
306 xd->image = NULL;
307
308 XML_SetElementHandler(parser, (XML_StartElementHandler) kml_start, (XML_EndElementHandler) kml_end);
309 XML_SetUserData(parser, xd);
310 XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler) kml_cdata);
311
312 status = XML_Parse(parser, buffer, len, TRUE);
313
314 XML_ParserFree (parser);
315
316 *north = xd->north;
317 *south = xd->south;
318 *east = xd->east;
319 *west = xd->west;
320 *name = xd->name; // NB don't free xd->name
321 *image = xd->image; // NB don't free xd->image
322
323 g_string_free ( xd->xpath, TRUE );
324 g_string_free ( xd->c_cdata, TRUE );
325 g_free ( xd );
326
327 return status != XML_STATUS_ERROR;
328#else
329 return FALSE;
330#endif
331}
332#endif
333
334/**
335 * kmz_open_file:
336 * @filename: The KMZ file to open
337 * @vvp: The #VikViewport
338 * @vlp: The #VikLayersPanel that the converted KMZ will be stored in
339 *
340 * Returns:
341 * -1 if KMZ not supported (this shouldn't happen)
342 * 0 on success
343 * >0 <128 ZIP error code
344 * 128 - No doc.kml file in KMZ
345 * 129 - Couldn't understand the doc.kml file
346 * 130 - Couldn't get bounds from KML (error not detected ATM)
347 * 131 - No image file in KML
348 * 132 - Couldn't get image from KML
349 * 133 - Image file problem
350 */
351int kmz_open_file ( const gchar* filename, VikViewport *vvp, VikLayersPanel *vlp )
352{
353 // Unzip
354#ifdef HAVE_ZIP_H
355// Older libzip compatibility:
356#ifndef zip_t
357typedef struct zip zip_t;
358typedef struct zip_file zip_file_t;
359#endif
360#ifndef ZIP_RDONLY
361#define ZIP_RDONLY 0
362#endif
363
364 int ans = ZIP_ER_OK;
365 zip_t *archive = zip_open ( filename, ZIP_RDONLY, &ans );
366 if ( !archive ) {
367 g_warning ( "Unable to open archive: '%s' Error code %d", filename, ans );
368 goto cleanup;
369 }
370
371 zip_int64_t zindex = zip_name_locate ( archive, "doc.kml", ZIP_FL_NOCASE | ZIP_FL_ENC_GUESS );
372 if ( zindex == -1 ) {
373 g_warning ( "Unable to find doc.kml" );
374 goto kmz_cleanup;
375 }
376
377 struct zip_stat zs;
378 if ( zip_stat_index( archive, zindex, 0, &zs ) == 0) {
379 zip_file_t *zf = zip_fopen_index ( archive, zindex, 0 );
380 char *buffer = g_malloc(zs.size);
381 int len = zip_fread ( zf, buffer, zs.size );
382 if ( len != zs.size ) {
383 ans = 128;
384 g_free ( buffer );
385 g_warning ( "Unable to read doc.kml from zip file" );
386 goto kmz_cleanup;
387 }
388
389 gdouble north, south, east, west;
390 gchar *name = NULL;
391 gchar *image = NULL;
392 gboolean parsed = parse_kml ( buffer, len, &name, &image, &north, &south, &east, &west );
393 g_free ( buffer );
394
395 GdkPixbuf *pixbuf = NULL;
396
397 if ( parsed ) {
398 // Read zip for image...
399 if ( image ) {
400 if ( zip_stat ( archive, image, ZIP_FL_NOCASE | ZIP_FL_ENC_GUESS, &zs ) == 0) {
401 zip_file_t *zfi = zip_fopen_index ( archive, zs.index, 0 );
402 // Don't know a way to create a pixbuf using streams.
403 // Thus write out to file
404 // Could read in chunks rather than one big buffer, but don't expect images to be that big
405 char *ibuffer = g_malloc(zs.size);
406 int ilen = zip_fread ( zfi, ibuffer, zs.size );
407 if ( ilen != zs.size ) {
408 ans = 131;
409 g_warning ( "Unable to read %s from zip file", image );
410 }
411 else {
412 gchar *image_file = util_write_tmp_file_from_bytes ( ibuffer, ilen );
413 GError *error = NULL;
414 pixbuf = gdk_pixbuf_new_from_file ( image_file, &error );
415 if ( error ) {
416 g_warning ("%s: %s", __FUNCTION__, error->message );
417 g_error_free ( error );
418 ans = 133;
419 }
420 else {
421 util_remove ( image_file );
422 }
423 g_free ( image_file );
424 }
425 g_free ( ibuffer );
426 }
427 g_free ( image );
428 }
429 else {
430 ans = 132;
431 }
432 }
433 else {
434 ans = 129;
435 }
436
437 if ( pixbuf ) {
438 // Some simple detection of broken position values ??
439 //if ( xd->north > 90.0 || xd->north < -90.0 ||
440 // xd->south > 90.0 || xd->south < -90.0 || )
441 VikCoord vc_tl, vc_br;
442 struct LatLon ll_tl, ll_br;
443 ll_tl.lat = north;
444 ll_tl.lon = west;
445 ll_br.lat = south;
446 ll_br.lon = east;
447 vik_coord_load_from_latlon ( &vc_tl, vik_viewport_get_coord_mode(vvp), &ll_tl );
448 vik_coord_load_from_latlon ( &vc_br, vik_viewport_get_coord_mode(vvp), &ll_br );
449
450 VikGeorefLayer *vgl = vik_georef_layer_create ( vvp, vlp, name, pixbuf, &vc_tl, &vc_br );
451 if ( vgl ) {
452 VikAggregateLayer *top = vik_layers_panel_get_top_layer ( vlp );
453 vik_aggregate_layer_add_layer ( top, VIK_LAYER(vgl), FALSE );
454 }
455 }
456 }
457
458kmz_cleanup:
459 zip_discard ( archive ); // Close and ensure unchanged
460 cleanup:
461 return ans;
462#else
463 return -1;
464#endif
465}