Unable to create KMZ file using java.util.zip

2019-04-13 15:15发布

问题:

So, I have made some sample KML/KMZ files in the past, largely by hand, and have discovered a few inconsistencies with Google Earth as a result of this work. For the most part, I thought I had a good handle on what I needed to do for specific versions of GE. Recently, I attempted to add some automation to my KML/KMZ sample files to customize them for certain clients based on a given CSV of points and other metadata.

I want to reach out to those with experience creating KMZ files from Java with custom icons or content. Here's the wrinkle I'm stuck on:

I created, by hand, a bunch of styles and custom icons to better display client data. Recently, I created a Java app that allows me to save a great deal of time by automating the KML/KMZ production. These styles were copied directly from the examples into my Java code (with escapes in their rightful places). The Java code initially built the KML, then I packaged the KML with the icons folder from my examples, zipped and saved as KMZ. All worked fine.

I then added a code block largely based on this example and added all my icons as resources to the jar. I then built the file as a .zip and verified that the KML and Icon files (in appropriate folders) were in the zip. They were. All was happy with the world.

I then changed the output filename to .kmz instead of .zip and tried to run the output in Google. None of my custom icons are loaded. The KML works fine, points and polygons are there with the proper style colors, but there are boxes with X's in the middle as if it can't access the icons.

If I rename the output KMZ to zip, unpack and run the KML inside it, everything works as expected. If I rezip and rename to KMZ, it's broken again.

Here's the real fun though. If I take the KML out of the KMZ, repack with the icons folder from the resources from my workspace, save as KMZ and load into google earth, it works OK.

I feel like that is telling me that my java.util.zip code is somehow corrupting the images to a point where GE doesn't know what to do with them. But I'm completely confused as to why they work fine when unpackaged, but then again broke when repackaged from the same location.

Anyone have any ideas? Apologies in advance for not posting code. I will post what I can if we can narrow down the problem space a little.

Here's as much code as I can transcribe at the moment:

//Create new file output based on file-name of previously made KML file (fileOut)
//nameToken exists to pop KML extension off the back end of fileOut.getName()

File fileOut2 = new File(fileOut.getParent(), nameToken2[0] + ".kmz");
FileOutputStream foutstream = new FileOutputStream(fileOut2);

ZipOutputStream zout = new ZipOutputStream(foutstream);
byte[] buffer = new byte[1024];

String[] resourceFiles = {null,"/icons/b-lv.png",...}; //many files listed here
for(int i = 0; i < resourceFiles.length; i++){
    //Previously wrote kml file, time to read it in and add to zip
    if (i == 0){
        FileInputStream fin = new FileInoutStream(fileOut);
        zout.putNextEntry(new ZipEntry(fileOut.getName());
        int length;
        while ((length = fin.read(buffer)) > 0){
            zout.write(buffer,0,length);
        }
        zout.closeEntry();
        fin.close();
    }
    //Read in resource icon files and add to zip
    else{
        InputStream inStream = this.getClass().getResourceAsStream(resourceFiles[i]);
        zout.putNextEntry(new ZipEntry(resourceFiles[i]));
        int length;
        while((length = inStream.read(buffer)) > 0){
            zout.write(buffer,0,length);
        }
        zout.closeEntry();
        inStream.close();
    }
}

zout.flush();
zout.close();
foutstream.close();
fileOut.delete();  //Deletes previously made KML file

回答1:

Here's Java code to create a sample KMZ file using ZipOutputStream with root KML file and an image file entry. If you don't properly close the KML entry before adding the image entries then the KMZ file can become corrupt.

IMPORTANT: You must make sure the zip file entries match exactly to the URL references within the KML. The zip file entries should NOT start with '/' or '../' or 'C:/'. Likewise, the URL/href references to the KMZ entries in the KML should be relative (e.g. icons/b-lv.png) to the particular KML file.

To reduce the lines of code in the example below, the Apache commons IOUtils library is used to copy the input file to the KMZ output stream and to close the stream.

import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.IOUtils;
import java.io.*;

public class TestKmz {

    public static void main(String[] args) throws IOException {     
        createKMZ();
        System.out.println("file out.kmz created");
    }

    public static void createKMZ()  throws IOException  {
        FileOutputStream fos = new FileOutputStream("out.kmz");
        ZipOutputStream zoS = new ZipOutputStream(fos);     
        ZipEntry ze = new ZipEntry("doc.kml");
        zoS.putNextEntry(ze);
        PrintStream ps = new PrintStream(zoS);          
        ps.println("<?xml version='1.0' encoding='UTF-8'?>");
        ps.println("<kml xmlns='http://www.opengis.net/kml/2.2'>");     
        // write out contents of KML file ...
        ps.println("<Placemark>");
        // add reference to image via inline style
        ps.println("  <Style><IconStyle>");
        ps.println("    <Icon><href>image.png</href></Icon>");
        ps.println("  </IconStyle></Style>");
        ps.println("</Placemark>");
        ps.println("</kml>");
        ps.flush();                 
        zoS.closeEntry(); // close KML entry

        // now add image file entry to KMZ
        FileInputStream is = null;
        try {                   
            is = new FileInputStream("image.png");
            ZipEntry zEnt = new ZipEntry("image.png");
            zoS.putNextEntry(zEnt);
            // copy image input to KMZ output
            // write contents to entry within compressed KMZ file
            IOUtils.copy(is, zoS);
        } finally {
            IOUtils.closeQuietly(is);
        }
        zoS.closeEntry();
        zoS.close();
    }   
}