Java API for KML (JAK) 在 kmz 文件中嵌入图像

发布于 2024-12-02 13:11:33 字数 157 浏览 0 评论 0原文

有没有办法使用 Java API for KML (JAK) 将图像文件添加到 kmz 文件中?我可以毫无问题地创建一个 kml 文件,但我试图嵌入一个资源(例如带有一些图像文件的图像文件夹),但 marshalAsKmz 方法仅将 Kml 对象作为附加文件,所以我无法理解了解如何仅包含额外的图像。

Is there a way to just add an image file into a kmz file using Java API for KML (JAK)? I can create a kml file with no problem, but I'm trying to just embed a resources (such as an images folder with some image files), but the marshalAsKmz method takes only Kml objects as additional files, so I can't figure out how to just include extra images.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

红衣飘飘貌似仙 2024-12-09 13:11:33

我在一个项目中使用 JAK 已有一年多了。我使用它来创建 KML,然后将其编组为普通 KML(而不是 KMZ)。我创建了一个单独的实用程序类,它使用 Java SE“Zip”类来手动创建 KMZ。它工作得很好。 KMZ 只不过是一个 .zip 存档,其中仅包含一个 .kml 文件和 0 个或多个资源文件(例如图像等)。唯一的区别是输出时将文件命名为 .kmz 而不是 .zip。在 KML 文档

编辑: 我实际上忘记了在压缩 JAK Kml 对象之前删除了将其编组到文件的中间步骤。下面的实用程序方法会将 Kml 对象直接编组到 ZipOutputStream

这是我创建的一个实用程序类,用于执行我所描述的操作。我将其发布在这里是希望其他人发布仅使用 JAK 的替代解决方案,以便我将来可以废弃此代码。现在,这将为您完成这项工作。

注意:如果您不使用 slf4j、Apache Commons Lang 或 Commons I/O,则只需对代码进行一些调整即可删除/用您自己的代码替换这些位。显然这段代码需要JAK库。

package com.jimtough;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.micromata.opengis.kml.v_2_2_0.Kml;

/**
 * Uses the classes in java.util.zip to package a KML file and its
 * supplementary files as a ZIP-compressed KMZ file.
 * 
 * @author JTOUGH
 */
public final class KMZPackager {

    private final static Logger logger =
        LoggerFactory.getLogger(KMZPackager.class);

    /**
     * Container for data that represents a single entry that will be added
     * to a compressed archive.
     * @author JTOUGH
     */
    public static abstract class DataSource {
        protected String archivedFileName;

        /**
         * Write the contents of this data source to the supplied
         * zip output stream.
         * 
         * @param zipOutputStream
         * @throws IOException
         */
        public abstract void writeToStream(ZipOutputStream zipOutputStream) 
            throws IOException; 
    }

    /**
     * Container for data that represents a single file that will be added
     * to a compressed archive.
     * @author JTOUGH
     */
    public static final class FileDataSource extends DataSource {
        private File sourceFile;

        /**
         * Constructor
         * 
         * @param sourceFile Actual file that will be added to the 
         *  compressed archive. 
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public FileDataSource(
                File sourceFile,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(sourceFile);
            Validate.notEmpty(archivedFileName);
            this.sourceFile = sourceFile;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream)
                throws IOException {
            Validate.notNull(zipOutputStream);

            // Check that the file exists, and throw an appropriate exception
            // before reading it
            if (!sourceFile.exists()) {
                throw new IllegalArgumentException(
                    "File referenced in parameter [" +
                    sourceFile.getAbsolutePath() + "] does not exist");
            }

            FileInputStream fis = new FileInputStream(sourceFile);

            if (logger.isDebugEnabled()) {
                logger.debug("Adding file to KMZ archive" +
                    " | archive name: " + archivedFileName +
                    " | original name: " + 
                    sourceFile.getCanonicalPath());
            }

            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Use the Apache commons-io library to do a buffered
            // stream-to-stream copy
            try {
                IOUtils.copy(fis, zipOutputStream);
            } finally {
                fis.close();
            }
        }
    }

    /**
     * Container for a single JAK Kml object that will be marshalled
     * directly to a compressed KMZ archive as it is created
     * @author JTOUGH
     */
    public static final class KMLDataSource extends DataSource {
        private Kml kml;

        /**
         * Constructor
         * 
         * @param kml JAK Kml object that will be marshalled directly to the 
         *  compressed archive. 
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public KMLDataSource(
                Kml kml,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(kml);
            Validate.notEmpty(archivedFileName);
            this.kml = kml;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream) throws IOException {
            Validate.notNull(zipOutputStream);
            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Marshal the Kml object directly to the ZipOutputStream
            if (logger.isDebugEnabled()) {
                logger.debug("Marshalling KML to KMZ archive" +
                    " | archive name: " + archivedFileName);
            }
            kml.marshal(zipOutputStream);   
        }
    }

    /**
     * Container for a stream holding a Kml document. This will be written
     * directly to a compressed KMZ archive as it is created.
     */
    public static final class StreamDataSource extends DataSource {

        private InputStream inputStream;

        /**
         * Constructor
         * 
         * @param inputStream the inputStream holding the KML text
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public StreamDataSource(
                InputStream inputStream,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(inputStream);
            Validate.notEmpty(archivedFileName);
            this.inputStream = inputStream;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream) throws IOException {
            Validate.notNull(zipOutputStream);
            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Use the Apache commons-io library to do a buffered
            // stream-to-stream copy
            if (logger.isDebugEnabled()) {
                logger.debug("Copying KML from stream to KMZ archive" +
                    " | archive name: " + archivedFileName);
            }
            try {
                IOUtils.copy(inputStream, zipOutputStream);
            } finally {
                inputStream.close();
            }
        }
    }

    /**
     * Use ZIP compression to package a KML file and its supplementary files
     * as a KMZ archive.  The compressed archive will be written to the 
     * supplied output stream.
     * 
     * @param os OutputStream to which the compressed archive will be written.
     *  This parameter must be a non-null reference to an OutputStream that
     *  is open for write operations.
     * @param kmlDataSource KML to be added to the compressed archive. 
     *  This parameter must not be null. The archivedFileName attribute must
     *  end with the .kml extension. The file is added to the compressed 
     *  archive. The KMZ specification states that a KMZ archive must only
     *  contain a single KML file.
     * @param supplementaryFileList List of file locations for supplementary
     *  files that will be included in the KMZ archive. A common example
     *  would be icons or image overlays that are referenced in the KML file.
     *  This parameter can be null or an empty list if there are no 
     *  supplementary files to include in the KMZ. Each source that is included
     *  in the list must refer to an existing file that does NOT have the
     *  file extension '.kml'.
     * @throws RuntimeException Thrown if anything unexpected occurs
     *  that prevents execution from continuing or if any of the stated
     *  conditions for the input parameters are violated
     */
    public void packageAsKMZ(
            OutputStream os,
            DataSource kmlDataSource,
            List<FileDataSource> supplementaryFileList) 
            throws RuntimeException {
        ZipOutputStream zipOutputStream = null;
        boolean isExceptionThrown = false;
        Exception caughtException = null;
        List<FileDataSource> supplFileList = supplementaryFileList;

        try {
            Validate.notNull(os, "os parameter cannot be null");
            Validate.notNull(kmlDataSource, 
                "kmlFileDataSource parameter cannot be null");

            // Treat a null parameter just like an empty list (which is OK)
            if (supplFileList == null) {
                supplFileList = Collections.emptyList();
            }

            if (logger.isDebugEnabled()) {
                logger.debug(
                    "Creating KMZ archive" +
                    " | supplementary files: " + supplFileList.size());
            }

            // Create a buffered output stream for the new KMZ file
            zipOutputStream = new ZipOutputStream(new BufferedOutputStream(os));
            Validate.isTrue(
                kmlDataSource.archivedFileName.endsWith(".kml"),
                "KML archived file name must end with .kml");
            kmlDataSource.writeToStream(zipOutputStream);

            // Now process the list of supplementary files
            if (logger.isDebugEnabled()) {
                logger.debug("Adding supplementary files to KMZ archive" +
                    " | archive name: ");
            }
            for (FileDataSource ds : supplFileList) {
                Validate.isTrue(
                    !ds.archivedFileName.endsWith(".kml"),
                    "Not legal to include .kml files in supplementary list");
                ds.writeToStream(zipOutputStream);
            }

            // Close the output stream to complete the ZIP creation
            zipOutputStream.flush();
            zipOutputStream.close();

            logger.info("KMZ archive created successfully");

        } catch (IOException e) {
            isExceptionThrown = true;
            caughtException = e;
            logger.error("IOException while creating ZIP stream");
        } catch (IllegalArgumentException e) {
            isExceptionThrown = true;
            caughtException = e;
        } catch (RuntimeException e) {
            isExceptionThrown = true;
            caughtException = e;
        } finally {
            if (isExceptionThrown) {
                try {
                    if (zipOutputStream != null) {
                        zipOutputStream.close();
                    }
                } catch (IOException ioe) {
                    // Don't care
                }
                throw new RuntimeException(caughtException);
            }
        }
    }
}

I've been using JAK for over a year on a project. I use it to create the KML, then I marshal it as just plain KML (not KMZ). I created a separate utility class that uses the Java SE 'Zip' classes to manually create the KMZ. It works just fine. A KMZ is nothing more than a .zip archive that contains exactly one .kml file and 0 or more resource files (such as images, etc). The only difference is that you name the file as .kmz instead of .zip when you output it. In the KML document <Style> definitions, refer to your resources files with paths relative to the KML document itself. The KML file is considered to be at the 'root' of the KMZ archive. If your resource files are also located in the root of the KMZ (.zip) then you don't need a path, just the filename.

EDIT: I actually forgot that I removed the intermediate step of marshalling the JAK Kml object to a file before I zip it. My utility method below will marshal the Kml object directly to the ZipOutputStream.

Here is a utility class that I created to do just what I have described. I'm posting it here in the hope that someone else posts an alternate solution that uses only JAK so I can retire this code in the future. For now, this will do the job for you.

NOTE: If you don't use slf4j, Apache Commons Lang or Commons I/O then just make a few adjustments to the code to remove/replace those bits with your own code. Obviously this code requires the JAK library.

package com.jimtough;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.micromata.opengis.kml.v_2_2_0.Kml;

/**
 * Uses the classes in java.util.zip to package a KML file and its
 * supplementary files as a ZIP-compressed KMZ file.
 * 
 * @author JTOUGH
 */
public final class KMZPackager {

    private final static Logger logger =
        LoggerFactory.getLogger(KMZPackager.class);

    /**
     * Container for data that represents a single entry that will be added
     * to a compressed archive.
     * @author JTOUGH
     */
    public static abstract class DataSource {
        protected String archivedFileName;

        /**
         * Write the contents of this data source to the supplied
         * zip output stream.
         * 
         * @param zipOutputStream
         * @throws IOException
         */
        public abstract void writeToStream(ZipOutputStream zipOutputStream) 
            throws IOException; 
    }

    /**
     * Container for data that represents a single file that will be added
     * to a compressed archive.
     * @author JTOUGH
     */
    public static final class FileDataSource extends DataSource {
        private File sourceFile;

        /**
         * Constructor
         * 
         * @param sourceFile Actual file that will be added to the 
         *  compressed archive. 
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public FileDataSource(
                File sourceFile,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(sourceFile);
            Validate.notEmpty(archivedFileName);
            this.sourceFile = sourceFile;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream)
                throws IOException {
            Validate.notNull(zipOutputStream);

            // Check that the file exists, and throw an appropriate exception
            // before reading it
            if (!sourceFile.exists()) {
                throw new IllegalArgumentException(
                    "File referenced in parameter [" +
                    sourceFile.getAbsolutePath() + "] does not exist");
            }

            FileInputStream fis = new FileInputStream(sourceFile);

            if (logger.isDebugEnabled()) {
                logger.debug("Adding file to KMZ archive" +
                    " | archive name: " + archivedFileName +
                    " | original name: " + 
                    sourceFile.getCanonicalPath());
            }

            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Use the Apache commons-io library to do a buffered
            // stream-to-stream copy
            try {
                IOUtils.copy(fis, zipOutputStream);
            } finally {
                fis.close();
            }
        }
    }

    /**
     * Container for a single JAK Kml object that will be marshalled
     * directly to a compressed KMZ archive as it is created
     * @author JTOUGH
     */
    public static final class KMLDataSource extends DataSource {
        private Kml kml;

        /**
         * Constructor
         * 
         * @param kml JAK Kml object that will be marshalled directly to the 
         *  compressed archive. 
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public KMLDataSource(
                Kml kml,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(kml);
            Validate.notEmpty(archivedFileName);
            this.kml = kml;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream) throws IOException {
            Validate.notNull(zipOutputStream);
            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Marshal the Kml object directly to the ZipOutputStream
            if (logger.isDebugEnabled()) {
                logger.debug("Marshalling KML to KMZ archive" +
                    " | archive name: " + archivedFileName);
            }
            kml.marshal(zipOutputStream);   
        }
    }

    /**
     * Container for a stream holding a Kml document. This will be written
     * directly to a compressed KMZ archive as it is created.
     */
    public static final class StreamDataSource extends DataSource {

        private InputStream inputStream;

        /**
         * Constructor
         * 
         * @param inputStream the inputStream holding the KML text
         * @param archivedFileName Name that will be assigned to the compressed
         *  file within the archive. Caller must ensure that this value is
         *  unique within the archive otherwise an exception will be thrown
         *  when a name clash occurs during creation of the archive.
         *  This string must be non-null and non-empty. Any forward-slash
         *  characters in the string will be treated as directory separators
         *  when the KMZ/ZIP archive is created.
         * @throws IllegalArgumentException If either of these parameters
         *  is a null reference
         */
        public StreamDataSource(
                InputStream inputStream,
                String archivedFileName) 
                throws IllegalArgumentException {
            Validate.notNull(inputStream);
            Validate.notEmpty(archivedFileName);
            this.inputStream = inputStream;
            this.archivedFileName = archivedFileName;
        }

        @Override
        public void writeToStream(ZipOutputStream zipOutputStream) throws IOException {
            Validate.notNull(zipOutputStream);
            // Mark the start of this new file in the ZIP stream
            ZipEntry entry = new ZipEntry(archivedFileName);
            zipOutputStream.putNextEntry(entry);

            // Use the Apache commons-io library to do a buffered
            // stream-to-stream copy
            if (logger.isDebugEnabled()) {
                logger.debug("Copying KML from stream to KMZ archive" +
                    " | archive name: " + archivedFileName);
            }
            try {
                IOUtils.copy(inputStream, zipOutputStream);
            } finally {
                inputStream.close();
            }
        }
    }

    /**
     * Use ZIP compression to package a KML file and its supplementary files
     * as a KMZ archive.  The compressed archive will be written to the 
     * supplied output stream.
     * 
     * @param os OutputStream to which the compressed archive will be written.
     *  This parameter must be a non-null reference to an OutputStream that
     *  is open for write operations.
     * @param kmlDataSource KML to be added to the compressed archive. 
     *  This parameter must not be null. The archivedFileName attribute must
     *  end with the .kml extension. The file is added to the compressed 
     *  archive. The KMZ specification states that a KMZ archive must only
     *  contain a single KML file.
     * @param supplementaryFileList List of file locations for supplementary
     *  files that will be included in the KMZ archive. A common example
     *  would be icons or image overlays that are referenced in the KML file.
     *  This parameter can be null or an empty list if there are no 
     *  supplementary files to include in the KMZ. Each source that is included
     *  in the list must refer to an existing file that does NOT have the
     *  file extension '.kml'.
     * @throws RuntimeException Thrown if anything unexpected occurs
     *  that prevents execution from continuing or if any of the stated
     *  conditions for the input parameters are violated
     */
    public void packageAsKMZ(
            OutputStream os,
            DataSource kmlDataSource,
            List<FileDataSource> supplementaryFileList) 
            throws RuntimeException {
        ZipOutputStream zipOutputStream = null;
        boolean isExceptionThrown = false;
        Exception caughtException = null;
        List<FileDataSource> supplFileList = supplementaryFileList;

        try {
            Validate.notNull(os, "os parameter cannot be null");
            Validate.notNull(kmlDataSource, 
                "kmlFileDataSource parameter cannot be null");

            // Treat a null parameter just like an empty list (which is OK)
            if (supplFileList == null) {
                supplFileList = Collections.emptyList();
            }

            if (logger.isDebugEnabled()) {
                logger.debug(
                    "Creating KMZ archive" +
                    " | supplementary files: " + supplFileList.size());
            }

            // Create a buffered output stream for the new KMZ file
            zipOutputStream = new ZipOutputStream(new BufferedOutputStream(os));
            Validate.isTrue(
                kmlDataSource.archivedFileName.endsWith(".kml"),
                "KML archived file name must end with .kml");
            kmlDataSource.writeToStream(zipOutputStream);

            // Now process the list of supplementary files
            if (logger.isDebugEnabled()) {
                logger.debug("Adding supplementary files to KMZ archive" +
                    " | archive name: ");
            }
            for (FileDataSource ds : supplFileList) {
                Validate.isTrue(
                    !ds.archivedFileName.endsWith(".kml"),
                    "Not legal to include .kml files in supplementary list");
                ds.writeToStream(zipOutputStream);
            }

            // Close the output stream to complete the ZIP creation
            zipOutputStream.flush();
            zipOutputStream.close();

            logger.info("KMZ archive created successfully");

        } catch (IOException e) {
            isExceptionThrown = true;
            caughtException = e;
            logger.error("IOException while creating ZIP stream");
        } catch (IllegalArgumentException e) {
            isExceptionThrown = true;
            caughtException = e;
        } catch (RuntimeException e) {
            isExceptionThrown = true;
            caughtException = e;
        } finally {
            if (isExceptionThrown) {
                try {
                    if (zipOutputStream != null) {
                        zipOutputStream.close();
                    }
                } catch (IOException ioe) {
                    // Don't care
                }
                throw new RuntimeException(caughtException);
            }
        }
    }
}
囍孤女 2024-12-09 13:11:33

Java 附带了一个默认的 zip 实用程序,您所需要做的就是传递需要压缩的 kml 文件以及图像文件列表。这两个函数应该可以解决问题。

   private void exportAsKmz(Kml kmlFile,File[] files) throws IOException {
      ZipOutputStream out = new ZipOutputStream(new FileOutputStream("example.kmz"));
      addKmzFile(kmlFile,out,files);
      out.close();
   }

   private void addKmzFile(Kml kmzFile, ZipOutputStream out,File[] files) throws IOException {
      String fileName = "";
      fileName = kmzFile.getFeature().getName();
      if (!fileName.endsWith(".kml")) {
         fileName += ".kml";
      }
      for(File file : files){
         out.putNextEntry(new ZipEntry(file.getName()));
         Files.copy(file.toPath(), out);
      }
      out.putNextEntry(new ZipEntry(URLEncoder.encode(fileName, "UTF-8")));
      kmzFile.marshal(out);
      out.closeEntry();
   }

Java comes with a default zip utility, all you need to do is to pass the kml file you need to zip along with the list of image files. These two functions should do the trick.

   private void exportAsKmz(Kml kmlFile,File[] files) throws IOException {
      ZipOutputStream out = new ZipOutputStream(new FileOutputStream("example.kmz"));
      addKmzFile(kmlFile,out,files);
      out.close();
   }

   private void addKmzFile(Kml kmzFile, ZipOutputStream out,File[] files) throws IOException {
      String fileName = "";
      fileName = kmzFile.getFeature().getName();
      if (!fileName.endsWith(".kml")) {
         fileName += ".kml";
      }
      for(File file : files){
         out.putNextEntry(new ZipEntry(file.getName()));
         Files.copy(file.toPath(), out);
      }
      out.putNextEntry(new ZipEntry(URLEncoder.encode(fileName, "UTF-8")));
      kmzFile.marshal(out);
      out.closeEntry();
   }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文