#ifdef HAVE_ZIP_H
#include <zip.h>
#endif
-#include "coords.h"
-#include "fileutils.h"
+#include "viking.h"
+#include <string.h>
#include <stdio.h>
#include <glib/gstdio.h>
+#ifdef HAVE_EXPAT_H
+#include <expat.h>
+#endif
+#include <math.h>
#ifdef HAVE_ZIP_H
/**
#endif
}
+typedef enum {
+ tt_unknown = 0,
+ tt_kml,
+ tt_kml_go,
+ tt_kml_go_name,
+ tt_kml_go_image,
+ tt_kml_go_latlonbox,
+ tt_kml_go_latlonbox_n,
+ tt_kml_go_latlonbox_e,
+ tt_kml_go_latlonbox_s,
+ tt_kml_go_latlonbox_w,
+} xtag_type;
+
+typedef struct {
+ xtag_type tag_type; /* enum from above for this tag */
+ const char *tag_name; /* xpath-ish tag name */
+} xtag_mapping;
+
+typedef struct {
+ GString *xpath;
+ GString *c_cdata;
+ xtag_type current_tag;
+ gchar *name;
+ gchar *image; // AKA icon
+ gdouble north;
+ gdouble east;
+ gdouble south;
+ gdouble west;
+} xml_data;
+
+#ifdef HAVE_ZIP_H
+#ifdef HAVE_EXPAT_H
+// NB No support for orientation ATM
+static xtag_mapping xtag_path_map[] = {
+ { tt_kml, "/kml" },
+ { tt_kml_go, "/kml/GroundOverlay" },
+ { tt_kml_go_name, "/kml/GroundOverlay/name" },
+ { tt_kml_go_image, "/kml/GroundOverlay/Icon/href" },
+ { tt_kml_go_latlonbox, "/kml/GroundOverlay/LatLonBox" },
+ { tt_kml_go_latlonbox_n, "/kml/GroundOverlay/LatLonBox/north" },
+ { tt_kml_go_latlonbox_e, "/kml/GroundOverlay/LatLonBox/east" },
+ { tt_kml_go_latlonbox_s, "/kml/GroundOverlay/LatLonBox/south" },
+ { tt_kml_go_latlonbox_w, "/kml/GroundOverlay/LatLonBox/west" },
+};
+
+// NB Don't be pedantic about matching case of strings for tags
+static xtag_type get_tag ( const char *t )
+{
+ xtag_mapping *tm;
+ for (tm = xtag_path_map; tm->tag_type != 0; tm++)
+ if (0 == g_ascii_strcasecmp(tm->tag_name, t))
+ return tm->tag_type;
+ return tt_unknown;
+}
+
+static void kml_start ( xml_data *xd, const char *el, const char **attr )
+{
+ g_string_append_c ( xd->xpath, '/' );
+ g_string_append ( xd->xpath, el );
+
+ xd->current_tag = get_tag ( xd->xpath->str );
+ switch ( xd->current_tag ) {
+ case tt_kml_go_name:
+ case tt_kml_go_image:
+ case tt_kml_go_latlonbox_n:
+ case tt_kml_go_latlonbox_s:
+ case tt_kml_go_latlonbox_e:
+ case tt_kml_go_latlonbox_w:
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ default: break; // ignore cdata from other things
+ }
+}
+
+static void kml_end ( xml_data *xd, const char *el )
+{
+ g_string_truncate ( xd->xpath, xd->xpath->len - strlen(el) - 1 );
+
+ switch ( xd->current_tag ) {
+ case tt_kml_go_name:
+ xd->name = g_strdup ( xd->c_cdata->str );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ case tt_kml_go_image:
+ xd->image = g_strdup ( xd->c_cdata->str );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ case tt_kml_go_latlonbox_n:
+ xd->north = g_ascii_strtod ( xd->c_cdata->str, NULL );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ case tt_kml_go_latlonbox_s:
+ xd->south = g_ascii_strtod ( xd->c_cdata->str, NULL );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ case tt_kml_go_latlonbox_e:
+ xd->east = g_ascii_strtod ( xd->c_cdata->str, NULL );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ case tt_kml_go_latlonbox_w:
+ xd->west = g_ascii_strtod ( xd->c_cdata->str, NULL );
+ g_string_erase ( xd->c_cdata, 0, -1 );
+ break;
+ default:
+ break;
+ }
+
+ xd->current_tag = get_tag ( xd->xpath->str );
+}
+
+static void kml_cdata ( xml_data *xd, const XML_Char *s, int len )
+{
+ switch ( xd->current_tag ) {
+ case tt_kml_go_name:
+ case tt_kml_go_image:
+ case tt_kml_go_latlonbox_n:
+ case tt_kml_go_latlonbox_s:
+ case tt_kml_go_latlonbox_e:
+ case tt_kml_go_latlonbox_w:
+ g_string_append_len ( xd->c_cdata, s, len );
+ break;
+ default: break; // ignore cdata from other things
+ }
+}
+#endif
+
+/**
+ *
+ */
+static gboolean parse_kml ( const char* buffer, int len, gchar **name, gchar **image, gdouble *north, gdouble *south, gdouble *east, gdouble *west )
+{
+#ifdef HAVE_EXPAT_H
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status status = XML_STATUS_ERROR;
+
+ xml_data *xd = g_malloc ( sizeof (xml_data) );
+ // Set default (invalid) values;
+ xd->xpath = g_string_new ( "" );
+ xd->c_cdata = g_string_new ( "" );
+ xd->current_tag = tt_unknown;
+ xd->north = NAN;
+ xd->south = NAN;
+ xd->east = NAN;
+ xd->west = NAN;
+ xd->name = NULL;
+ xd->image = NULL;
+
+ XML_SetElementHandler(parser, (XML_StartElementHandler) kml_start, (XML_EndElementHandler) kml_end);
+ XML_SetUserData(parser, xd);
+ XML_SetCharacterDataHandler(parser, (XML_CharacterDataHandler) kml_cdata);
+
+ status = XML_Parse(parser, buffer, len, TRUE);
+
+ XML_ParserFree (parser);
+
+ *north = xd->north;
+ *south = xd->south;
+ *east = xd->east;
+ *west = xd->west;
+ *name = xd->name; // NB don't free xd->name
+ *image = xd->image; // NB don't free xd->image
+
+ g_string_free ( xd->xpath, TRUE );
+ g_string_free ( xd->c_cdata, TRUE );
+ g_free ( xd );
+
+ return status != XML_STATUS_ERROR;
+#else
+ return FALSE;
+#endif
+}
+#endif
+
+/**
+ * kmz_open_file:
+ * @filename: The KMZ file to open
+ * @vvp: The #VikViewport
+ * @vlp: The #VikLayersPanel that the converted KMZ will be stored in
+ *
+ * Returns:
+ * -1 if KMZ not supported (this shouldn't happen)
+ * 0 on success
+ * >0 <128 ZIP error code
+ * 128 - No doc.kml file in KMZ
+ * 129 - Couldn't understand the doc.kml file
+ * 130 - Couldn't get bounds from KML (error not detected ATM)
+ * 131 - No image file in KML
+ * 132 - Couldn't get image from KML
+ * 133 - Image file problem
+ */
+int kmz_open_file ( const gchar* filename, VikViewport *vvp, VikLayersPanel *vlp )
+{
+ // Unzip
+#ifdef HAVE_ZIP_H
+// Older libzip compatibility:
+#ifndef zip_t
+typedef struct zip zip_t;
+typedef struct zip_file zip_file_t;
+#endif
+#ifndef ZIP_RDONLY
+#define ZIP_RDONLY 0
+#endif
+
+ int ans = ZIP_ER_OK;
+ zip_t *archive = zip_open ( filename, ZIP_RDONLY, &ans );
+ if ( !archive ) {
+ g_warning ( "Unable to open archive: '%s' Error code %d", filename, ans );
+ goto cleanup;
+ }
+
+ zip_int64_t zindex = zip_name_locate ( archive, "doc.kml", ZIP_FL_NOCASE | ZIP_FL_ENC_GUESS );
+ if ( zindex == -1 ) {
+ g_warning ( "Unable to find doc.kml" );
+ goto kmz_cleanup;
+ }
+
+ struct zip_stat zs;
+ if ( zip_stat_index( archive, zindex, 0, &zs ) == 0) {
+ zip_file_t *zf = zip_fopen_index ( archive, zindex, 0 );
+ char *buffer = g_malloc(zs.size);
+ int len = zip_fread ( zf, buffer, zs.size );
+ if ( len != zs.size ) {
+ ans = 128;
+ g_free ( buffer );
+ g_warning ( "Unable to read doc.kml from zip file" );
+ goto kmz_cleanup;
+ }
+
+ gdouble north, south, east, west;
+ gchar *name = NULL;
+ gchar *image = NULL;
+ gboolean parsed = parse_kml ( buffer, len, &name, &image, &north, &south, &east, &west );
+ g_free ( buffer );
+
+ GdkPixbuf *pixbuf = NULL;
+
+ if ( parsed ) {
+ // Read zip for image...
+ if ( image ) {
+ if ( zip_stat ( archive, image, ZIP_FL_NOCASE | ZIP_FL_ENC_GUESS, &zs ) == 0) {
+ zip_file_t *zfi = zip_fopen_index ( archive, zs.index, 0 );
+ // Don't know a way to create a pixbuf using streams.
+ // Thus write out to file
+ // Could read in chunks rather than one big buffer, but don't expect images to be that big
+ char *ibuffer = g_malloc(zs.size);
+ int ilen = zip_fread ( zfi, ibuffer, zs.size );
+ if ( ilen != zs.size ) {
+ ans = 131;
+ g_warning ( "Unable to read %s from zip file", image );
+ }
+ else {
+ gchar *image_file = util_write_tmp_file_from_bytes ( ibuffer, ilen );
+ GError *error = NULL;
+ pixbuf = gdk_pixbuf_new_from_file ( image_file, &error );
+ if ( error ) {
+ g_warning ("%s: %s", __FUNCTION__, error->message );
+ g_error_free ( error );
+ ans = 133;
+ }
+ else {
+ util_remove ( image_file );
+ }
+ g_free ( image_file );
+ }
+ g_free ( ibuffer );
+ }
+ g_free ( image );
+ }
+ else {
+ ans = 132;
+ }
+ }
+ else {
+ ans = 129;
+ }
+
+ if ( pixbuf ) {
+ // Some simple detection of broken position values ??
+ //if ( xd->north > 90.0 || xd->north < -90.0 ||
+ // xd->south > 90.0 || xd->south < -90.0 || )
+ VikCoord vc_tl, vc_br;
+ struct LatLon ll_tl, ll_br;
+ ll_tl.lat = north;
+ ll_tl.lon = west;
+ ll_br.lat = south;
+ ll_br.lon = east;
+ vik_coord_load_from_latlon ( &vc_tl, vik_viewport_get_coord_mode(vvp), &ll_tl );
+ vik_coord_load_from_latlon ( &vc_br, vik_viewport_get_coord_mode(vvp), &ll_br );
+
+ VikGeorefLayer *vgl = vik_georef_layer_create ( vvp, vlp, name, pixbuf, &vc_tl, &vc_br );
+ if ( vgl ) {
+ VikAggregateLayer *top = vik_layers_panel_get_top_layer ( vlp );
+ vik_aggregate_layer_add_layer ( top, VIK_LAYER(vgl), FALSE );
+ }
+ }
+ }
+
+kmz_cleanup:
+ zip_discard ( archive ); // Close and ensure unchanged
+ cleanup:
+ return ans;
+#else
+ return -1;
+#endif
+}
draw_to_image_file ( vw, VW_GEN_DIRECTORY_OF_IMAGES );
}
+/**
+ *
+ */
+static void import_kmz_file_cb ( GtkAction *a, VikWindow *vw )
+{
+ GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Open File"),
+ GTK_WINDOW(vw),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ GtkFileFilter *filter;
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name ( filter, _("KMZ") );
+ gtk_file_filter_add_mime_type ( filter, "vnd.google-earth.kmz");
+ gtk_file_filter_add_pattern ( filter, "*.kmz" );
+ gtk_file_chooser_add_filter ( GTK_FILE_CHOOSER(dialog), filter );
+ gtk_file_chooser_set_filter ( GTK_FILE_CHOOSER(dialog), filter );
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name( filter, _("All") );
+ gtk_file_filter_add_pattern ( filter, "*" );
+ gtk_file_chooser_add_filter ( GTK_FILE_CHOOSER(dialog), filter );
+ // Default to any file - same as before open filters were added
+ gtk_file_chooser_set_filter ( GTK_FILE_CHOOSER(dialog), filter );
+
+ if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
+ gchar *fn = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER(dialog) );
+ // TODO convert ans value into readable explaination of failure...
+ int ans = kmz_open_file ( fn, vw->viking_vvp, vw->viking_vlp );
+ if ( ans )
+ a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Unable to import %s."), fn );
+
+ draw_update ( vw );
+ }
+ gtk_widget_destroy ( dialog );
+}
+
static void print_cb ( GtkAction *a, VikWindow *vw )
{
a_print(vw, vw->viking_vvp);
{ "SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), NULL, N_("Save the file under different name"), (GCallback)save_file_as },
{ "FileProperties", NULL, N_("Properties..."), NULL, N_("File Properties"), (GCallback)file_properties_cb },
#ifdef HAVE_ZIP_H
+ { "ImportKMZ", GTK_STOCK_CONVERT, N_("Import KMZ _Map File..."), NULL, N_("Import a KMZ file"), (GCallback)import_kmz_file_cb },
{ "GenKMZ", GTK_STOCK_DND, N_("Generate _KMZ Map File..."), NULL, N_("Generate a KMZ file with an overlay of the current view"), (GCallback)draw_to_kmz_file_cb },
#endif
{ "GenImg", GTK_STOCK_FILE, N_("_Generate Image File..."), NULL, N_("Save a snapshot of the workspace into a file"), (GCallback)draw_to_image_file_cb },