2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (C) 2015, Rob Norris <rw_norris@hotmail.com>
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.
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.
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
33 #include <glib/gstdio.h>
41 * Simple KML 'file' with a Ground Overlay
43 * See https://developers.google.com/kml/documentation/kmlreference
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...
48 // Hopefully image_filename will not break the XML file tag structure
49 static gchar* doc_kml_str ( const gchar *name, const gchar *image_filename, gdouble north, gdouble south, gdouble east, gdouble west )
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 );
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"
65 " <north>%s</north>\n"
66 " <south>%s</south>\n"
69 " <rotation>0</rotation>\n" // Rotation always zero
73 name, image_filename, tmp_n, tmp_s, tmp_e, tmp_w );
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
95 * -1 if KMZ not supported (this shouldn't happen)
97 * >0 some kind of error
99 * Mostly intended for use with as a Custom Map on a Garmin
101 * See http://garminbasecamp.wikispaces.com/Custom+Maps
103 * The KMZ is a zipped file containing a KML file with the associated image
105 int kmz_save_file ( GdkPixbuf *pixbuf, const gchar* filename, gdouble north, gdouble east, gdouble south, gdouble west )
108 // Older libzip compatibility:
110 typedef struct zip_source zip_source_t;
114 gchar *image_filename = "image.jpg";
116 // Generate KMZ file (a zip file)
117 struct zip* archive = zip_open ( filename, ZIP_CREATE | ZIP_TRUNCATE, &ans );
119 g_warning ( "Unable to create archive: '%s' Error code %d", filename, ans );
124 gchar *dk = doc_kml_str ( a_file_basename(filename), image_filename, north, south, east, west );
125 int dkl = strlen ( dk );
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 );
131 GError *error = NULL;
134 gdk_pixbuf_save_to_buffer ( pixbuf, &buffer, &blen, "jpeg", &error, "x-dpi", "72", "y-dpi", "72", NULL );
136 g_warning ( "Save to buffer error: %s", error->message );
137 g_error_free (error);
138 zip_discard ( archive );
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 );
148 ans = zip_close ( archive );
168 tt_kml_go_latlonbox_n,
169 tt_kml_go_latlonbox_e,
170 tt_kml_go_latlonbox_s,
171 tt_kml_go_latlonbox_w,
175 xtag_type tag_type; /* enum from above for this tag */
176 const char *tag_name; /* xpath-ish tag name */
182 xtag_type current_tag;
184 gchar *image; // AKA icon
193 // NB No support for orientation ATM
194 static xtag_mapping xtag_path_map[] = {
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" },
206 // NB Don't be pedantic about matching case of strings for tags
207 static xtag_type get_tag ( const char *t )
210 for (tm = xtag_path_map; tm->tag_type != 0; tm++)
211 if (0 == g_ascii_strcasecmp(tm->tag_name, t))
216 static void kml_start ( xml_data *xd, const char *el, const char **attr )
218 g_string_append_c ( xd->xpath, '/' );
219 g_string_append ( xd->xpath, el );
221 xd->current_tag = get_tag ( xd->xpath->str );
222 switch ( xd->current_tag ) {
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 );
231 default: break; // ignore cdata from other things
235 static void kml_end ( xml_data *xd, const char *el )
237 g_string_truncate ( xd->xpath, xd->xpath->len - strlen(el) - 1 );
239 switch ( xd->current_tag ) {
241 xd->name = g_strdup ( xd->c_cdata->str );
242 g_string_erase ( xd->c_cdata, 0, -1 );
244 case tt_kml_go_image:
245 xd->image = g_strdup ( xd->c_cdata->str );
246 g_string_erase ( xd->c_cdata, 0, -1 );
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 );
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 );
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 );
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 );
268 xd->current_tag = get_tag ( xd->xpath->str );
271 static void kml_cdata ( xml_data *xd, const XML_Char *s, int len )
273 switch ( xd->current_tag ) {
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 );
282 default: break; // ignore cdata from other things
290 static gboolean parse_kml ( const char* buffer, int len, gchar **name, gchar **image, gdouble *north, gdouble *south, gdouble *east, gdouble *west )
293 XML_Parser parser = XML_ParserCreate(NULL);
294 enum XML_Status status = XML_STATUS_ERROR;
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;
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);
312 status = XML_Parse(parser, buffer, len, TRUE);
314 XML_ParserFree (parser);
320 *name = xd->name; // NB don't free xd->name
321 *image = xd->image; // NB don't free xd->image
323 g_string_free ( xd->xpath, TRUE );
324 g_string_free ( xd->c_cdata, TRUE );
327 return status != XML_STATUS_ERROR;
336 * @filename: The KMZ file to open
337 * @vvp: The #VikViewport
338 * @vlp: The #VikLayersPanel that the converted KMZ will be stored in
341 * -1 if KMZ not supported (this shouldn't happen)
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
351 int kmz_open_file ( const gchar* filename, VikViewport *vvp, VikLayersPanel *vlp )
355 // Older libzip compatibility:
357 typedef struct zip zip_t;
358 typedef struct zip_file zip_file_t;
365 zip_t *archive = zip_open ( filename, ZIP_RDONLY, &ans );
367 g_warning ( "Unable to open archive: '%s' Error code %d", filename, ans );
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" );
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 ) {
385 g_warning ( "Unable to read doc.kml from zip file" );
389 gdouble north, south, east, west;
392 gboolean parsed = parse_kml ( buffer, len, &name, &image, &north, &south, &east, &west );
395 GdkPixbuf *pixbuf = NULL;
398 // Read zip for 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 ) {
409 g_warning ( "Unable to read %s from zip file", image );
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 );
416 g_warning ("%s: %s", __FUNCTION__, error->message );
417 g_error_free ( error );
421 util_remove ( image_file );
423 g_free ( image_file );
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;
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 );
450 VikGeorefLayer *vgl = vik_georef_layer_create ( vvp, vlp, name, pixbuf, &vc_tl, &vc_br );
452 VikAggregateLayer *top = vik_layers_panel_get_top_layer ( vlp );
453 vik_aggregate_layer_add_layer ( top, VIK_LAYER(vgl), FALSE );
459 zip_discard ( archive ); // Close and ensure unchanged