X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fsave%2FKmlExporter.java;h=82b4f1c12ca7cc7f679f8896bbe86555142137a0;hb=52bf9e8686c916be37a26a0b75340393d4478b05;hp=16624ad1495dd26c549a0af26a3c087fbb0364c7;hpb=d3679d647d57c2ee7376ddbf6def2d5b23c04307;p=GpsPrune.git diff --git a/tim/prune/save/KmlExporter.java b/tim/prune/save/KmlExporter.java index 16624ad..82b4f1c 100644 --- a/tim/prune/save/KmlExporter.java +++ b/tim/prune/save/KmlExporter.java @@ -1,193 +1,420 @@ package tim.prune.save; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileWriter; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.Writer; +import java.util.Iterator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileFilter; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JTextField; +import javax.swing.SwingConstants; -import tim.prune.App; +import tim.prune.Config; import tim.prune.I18nManager; +import tim.prune.UpdateMessageBroker; +import tim.prune.data.Altitude; import tim.prune.data.Coordinate; import tim.prune.data.DataPoint; +import tim.prune.data.Field; import tim.prune.data.Track; +import tim.prune.data.TrackInfo; +import tim.prune.gui.ImageUtils; +import tim.prune.load.GenericFileFilter; /** * Class to export track information * into a specified Kml file */ -public class KmlExporter +public class KmlExporter implements Runnable { - private App _app = null; private JFrame _parentFrame = null; + private TrackInfo _trackInfo = null; private Track _track = null; + private JDialog _dialog = null; + private JTextField _descriptionField = null; + private JCheckBox _altitudesCheckbox = null; + private JCheckBox _kmzCheckbox = null; + private JCheckBox _exportImagesCheckbox = null; + private JProgressBar _progressBar = null; private JFileChooser _fileChooser = null; + private File _exportFile = null; + + // Filename of Kml file within zip archive + private static final String KML_FILENAME_IN_KMZ = "doc.kml"; + // Width and height of thumbnail images in Kmz + private static final int THUMBNAIL_WIDTH = 240; + private static final int THUMBNAIL_HEIGHT = 180; /** - * Constructor giving App object, frame and track - * @param inApp application object to inform of success + * Constructor giving frame and track * @param inParentFrame parent frame - * @param inTrack track object to save + * @param inTrackInfo track info object to save */ - public KmlExporter(App inApp, JFrame inParentFrame, Track inTrack) + public KmlExporter(JFrame inParentFrame, TrackInfo inTrackInfo) { - _app = inApp; _parentFrame = inParentFrame; - _track = inTrack; + _trackInfo = inTrackInfo; + _track = inTrackInfo.getTrack(); } /** * Show the dialog to select options and export file */ - public boolean showDialog() + public void showDialog() { - boolean fileSaved = false; - Object description = JOptionPane.showInputDialog(_parentFrame, - I18nManager.getText("dialog.exportkml.text"), - I18nManager.getText("dialog.exportkml.title"), - JOptionPane.QUESTION_MESSAGE, null, null, ""); - // TODO: Make dialog window including colour selection, line width, track description - if (description != null) + // Make dialog window including whether to compress to kmz (and include pictures) or not + if (_dialog == null) { - // OK pressed, so choose output file - if (_fileChooser == null) - _fileChooser = new JFileChooser(); - _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); - _fileChooser.setFileFilter(new FileFilter() { - public boolean accept(File f) - { - return (f != null && (f.isDirectory() || f.getName().toLowerCase().endsWith(".kml"))); - } - public String getDescription() - { - return I18nManager.getText("dialog.exportkml.filetype"); - } - }); - _fileChooser.setAcceptAllFileFilterUsed(false); - // Allow choose again if an existing file is selected - boolean chooseAgain = false; - do - { - chooseAgain = false; - if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) - { - // OK pressed and file chosen - File file = _fileChooser.getSelectedFile(); - if (!file.getName().toLowerCase().endsWith(".kml")) - { - file = new File(file.getAbsolutePath() + ".kml"); - } - // Check if file exists and if necessary prompt for overwrite - Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; - if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame, - I18nManager.getText("dialog.save.overwrite.text"), - I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) - == JOptionPane.YES_OPTION) - { - if (exportFile(file, description.toString())) - { - fileSaved = true; - } - else - { - chooseAgain = true; - } - } - else - { - chooseAgain = true; - } - } - } while (chooseAgain); + _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.exportkml.title"), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); } - return fileSaved; + enableCheckboxes(); + _progressBar.setVisible(false); + _dialog.show(); } /** - * Export the track data to the specified file with description - * @param inFile File object to save to - * @param inDescription description to use, if any + * Create dialog components + * @return Panel containing all gui elements in dialog */ - private boolean exportFile(File inFile, String inDescription) + private Component makeDialogComponents() { - FileWriter writer = null; - try + JPanel dialogPanel = new JPanel(); + dialogPanel.setLayout(new BorderLayout()); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + // Make a central panel with the text box and checkboxes + JPanel descPanel = new JPanel(); + descPanel.setLayout(new FlowLayout()); + descPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.text"))); + _descriptionField = new JTextField(20); + descPanel.add(_descriptionField); + mainPanel.add(descPanel); + dialogPanel.add(mainPanel, BorderLayout.CENTER); + // Checkbox for altitude export + _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude")); + _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + mainPanel.add(_altitudesCheckbox); + // Checkboxes for kmz export and image export + _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz")); + _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + _kmzCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + // enable image checkbox if kmz activated + enableCheckboxes(); + } + }); + mainPanel.add(_kmzCheckbox); + _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages")); + _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + mainPanel.add(_exportImagesCheckbox); + mainPanel.add(Box.createVerticalStrut(10)); + _progressBar = new JProgressBar(0, 100); + _progressBar.setVisible(false); + mainPanel.add(_progressBar); + mainPanel.add(Box.createVerticalStrut(10)); + // button panel at bottom + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + JButton okButton = new JButton(I18nManager.getText("button.ok")); + ActionListener okListener = new ActionListener() { + public void actionPerformed(ActionEvent e) + { + startExport(); + } + }; + okButton.addActionListener(okListener); + _descriptionField.addActionListener(okListener); + buttonPanel.add(okButton); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _dialog.dispose(); + } + }); + buttonPanel.add(cancelButton); + dialogPanel.add(buttonPanel, BorderLayout.SOUTH); + return dialogPanel; + } + + + /** + * Enable the checkboxes according to data + */ + private void enableCheckboxes() + { + boolean hasAltitudes = _track.hasData(Field.ALTITUDE); + if (!hasAltitudes) {_altitudesCheckbox.setSelected(false);} + boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0; + _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected()); + _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected()); + } + + + /** + * Start the export process based on the input parameters + */ + private void startExport() + { + // OK pressed, so choose output file + if (_fileChooser == null) { - writer = new FileWriter(inFile); - writer.write("\n\n\n"); - writer.write("\t"); - writer.write(inDescription); - writer.write("\n"); - - int i = 0; - DataPoint point = null; - boolean hasTrackpoints = false; - // Loop over waypoints - boolean writtenPhotoHeader = false; - int numPoints = _track.getNumPoints(); - for (i=0; ihttp://maps.google.com/mapfiles/kml/pal4/icon46.png"); - writtenPhotoHeader = true; - } - exportPhotoPoint(point, writer); + // New file or overwrite confirmed, so initiate export in separate thread + _exportFile = file; + new Thread(this).start(); } else { - hasTrackpoints = true; + chooseAgain = true; } } - if (hasTrackpoints) + } while (chooseAgain); + } + + + /** + * Run method for controlling separate thread for exporting + */ + public void run() + { + // Initialise progress bar + _progressBar.setVisible(true); + _progressBar.setValue(0); + boolean exportToKmz = _kmzCheckbox.isSelected(); + boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected(); + _progressBar.setMaximum(exportImages?getNumPhotosToExport():1); + OutputStreamWriter writer = null; + ZipOutputStream zipOutputStream = null; + try + { + // Select writer according to whether kmz requested or not + if (!_kmzCheckbox.isSelected()) + { + // normal writing to file + writer = new OutputStreamWriter(new FileOutputStream(_exportFile)); + } + else + { + // kmz requested - need zip output stream + zipOutputStream = new ZipOutputStream(new FileOutputStream(_exportFile)); + writer = new OutputStreamWriter(zipOutputStream); + // Make an entry in the zip file for the kml file + ZipEntry kmlEntry = new ZipEntry(KML_FILENAME_IN_KMZ); + zipOutputStream.putNextEntry(kmlEntry); + } + // write file + int numPoints = exportData(writer, exportImages); + // update progress bar + _progressBar.setValue(1); + + // close zip entry if necessary + if (zipOutputStream != null) { - writer.write("\t\n\t\ttrack\n\t\t\n\t\t\n\t\t\t"); - // Loop over track points - for (i=0; i.jpg + exportThumbnails(zipOutputStream); } - writer.write("\t\t\t\n\t\t\n\t"); } - writer.write("\n"); + + // close file writer.close(); - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1") - + " " + numPoints + " " + I18nManager.getText("dialog.save.ok2") - + " " + inFile.getAbsolutePath(), - I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE); - return true; + // Store directory in config for later + Config.setWorkingDirectory(_exportFile.getParentFile()); + // show confirmation + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1") + + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2") + + " " + _exportFile.getAbsolutePath()); + // export successful so need to close dialog and return + _dialog.dispose(); + return; } catch (IOException ioe) { + // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage()); try { if (writer != null) writer.close(); } catch (IOException ioe2) {} - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(), + JOptionPane.showMessageDialog(_parentFrame, + I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(), I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); } - return false; + // if not returned already, export failed so need to recall the file selection + startExport(); + } + + + /** + * Export the information to the given writer + * @param inWriter writer object + * @param inExportImages true if image thumbnails are to be referenced + * @return number of points written + */ + private int exportData(OutputStreamWriter inWriter, boolean inExportImages) + throws IOException + { + inWriter.write("\n\n\n"); + inWriter.write("\t"); + if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals("")) + { + inWriter.write(_descriptionField.getText()); + } + else + { + inWriter.write("Export from Prune"); + } + inWriter.write("\n"); + + boolean exportAltitudes = _altitudesCheckbox.isSelected(); + int i = 0; + DataPoint point = null; + boolean hasTrackpoints = false; + // Loop over waypoints + boolean writtenPhotoHeader = false; + int numPoints = _track.getNumPoints(); + int photoNum = 0; + for (i=0; ihttp://maps.google.com/mapfiles/kml/pal4/icon46.png"); + writtenPhotoHeader = true; + } + photoNum++; + exportPhotoPoint(point, inWriter, inExportImages, photoNum, exportAltitudes); + } + } + // Make a line for the track, if there is one + if (hasTrackpoints) + { + // Set up strings for start and end of track segment + String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n"; + if (exportAltitudes) { + trackStart += "\t\t\t1\n\t\t\tabsolute\n"; + } + trackStart += "\t\t\t"; + String trackEnd = "\t\t\t\n\t\t\n\t"; + + // Start segment + inWriter.write(trackStart); + // Loop over track points + boolean firstTrackpoint = true; + for (i=0; i\n"); + return numPoints; } @@ -195,18 +422,30 @@ public class KmlExporter * Export the specified waypoint into the file * @param inPoint waypoint to export * @param inWriter writer object + * @param inExportAltitude true to include altitude * @throws IOException on write failure */ - private void exportWaypoint(DataPoint inPoint, Writer inWriter) throws IOException + private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inExportAltitude) throws IOException { inWriter.write("\t\n\t\t"); inWriter.write(inPoint.getWaypointName().trim()); inWriter.write("\n"); - inWriter.write("\t\t\n\t\t\t"); + inWriter.write("\t\t\n"); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("\t\t\tabsolute\n"); + } + inWriter.write("\t\t\t"); inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - inWriter.write(",0\n\t\t\n\t\n"); + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n\t\t\n\t\n"); } @@ -214,20 +453,45 @@ public class KmlExporter * Export the specified photo into the file * @param inPoint data point including photo * @param inWriter writer object + * @param inImageLink flag to set whether to export image links or not + * @param inImageNumber number of image for filename + * @param inExportAltitude true to include altitude * @throws IOException on write failure */ - private void exportPhotoPoint(DataPoint inPoint, Writer inWriter) throws IOException + private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, + int inImageNumber, boolean inExportAltitude) + throws IOException { - // TODO: Export photos to KML too - for photos need kmz! inWriter.write("\t\n\t\t"); inWriter.write(inPoint.getPhoto().getFile().getName()); inWriter.write("\n"); + if (inImageLink) + { + // Work out image dimensions of thumbnail + Dimension picSize = inPoint.getPhoto().getSize(); + Dimension thumbSize = ImageUtils.getThumbnailSize(picSize.width, picSize.height, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); + // Write out some html for the thumbnail images + inWriter.write("" + + "
Caption for the photo
]]>
"); + } inWriter.write("#camera_icon\n"); - inWriter.write("\t\t\n\t\t\t"); + inWriter.write("\t\t\n"); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("\t\t\tabsolute\n"); + } + inWriter.write("\t\t\t"); inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - inWriter.write(",0\n\t\t\n\t
\n"); + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n\t\t\n\t\n"); } @@ -235,13 +499,89 @@ public class KmlExporter * Export the specified trackpoint into the file * @param inPoint trackpoint to export * @param inWriter writer object + * @param inExportAltitude true to include altitude */ - private void exportTrackpoint(DataPoint inPoint, Writer inWriter) throws IOException + private void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inExportAltitude) throws IOException { inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - // Altitude not exported, locked to ground by Google Earth - inWriter.write(",0\n"); + // Altitude either absolute or locked to ground by Google Earth + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n"); + } + + + /** + * Loop through the photos and create thumbnails + * @param inZipStream zip stream to save image files to + */ + private void exportThumbnails(ZipOutputStream inZipStream) throws IOException + { + // set up image writer + Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); + if (writers == null || !writers.hasNext()) + { + throw new IOException("no JPEG writer found"); + } + ImageWriter imageWriter = (ImageWriter) writers.next(); + + int numPoints = _track.getNumPoints(); + DataPoint point = null; + int photoNum = 0; + // Loop over all points in track + for (int i=0; i