X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Fsave%2FKmlExporter.java;fp=src%2Ftim%2Fprune%2Fsave%2FKmlExporter.java;h=9d1c0789b4f4d9a3e6820f5883b7f7235bac74ab;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/save/KmlExporter.java b/src/tim/prune/save/KmlExporter.java new file mode 100644 index 0000000..9d1c078 --- /dev/null +++ b/src/tim/prune/save/KmlExporter.java @@ -0,0 +1,937 @@ +package tim.prune.save; + +import java.awt.BorderLayout; +import java.awt.Color; +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.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.File; +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.ButtonGroup; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +import tim.prune.App; +import tim.prune.GenericFunction; +import tim.prune.I18nManager; +import tim.prune.UpdateMessageBroker; +import tim.prune.config.ColourUtils; +import tim.prune.config.Config; +import tim.prune.data.Coordinate; +import tim.prune.data.DataPoint; +import tim.prune.data.Field; +import tim.prune.data.RecentFile; +import tim.prune.data.Timestamp; +import tim.prune.data.Track; +import tim.prune.data.TrackInfo; +import tim.prune.data.UnitSetLibrary; +import tim.prune.gui.DialogCloser; +import tim.prune.gui.ImageUtils; +import tim.prune.gui.WholeNumberField; +import tim.prune.gui.colour.ColourChooser; +import tim.prune.gui.colour.ColourPatch; +import tim.prune.load.GenericFileFilter; +import tim.prune.save.xml.XmlUtils; + +/** + * Class to export track information + * into a specified Kml or Kmz file + */ +public class KmlExporter extends GenericFunction implements Runnable +{ + private TrackInfo _trackInfo = null; + private Track _track = null; + private JDialog _dialog = null; + private JTextField _descriptionField = null; + private PointTypeSelector _pointTypeSelector = null; + private JRadioButton _gxExtensionsRadio = null; + private JCheckBox _altitudesCheckbox = null; + private JCheckBox _kmzCheckbox = null; + private JCheckBox _exportImagesCheckbox = null; + private JLabel _imageSizeLabel = null; + private WholeNumberField _imageSizeField = null; + private ColourPatch _colourPatch = null; + private JLabel _progressLabel = null; + private JProgressBar _progressBar = null; + private Dimension[] _imageDimensions = null; + private JFileChooser _fileChooser = null; + private File _exportFile = null; + private JButton _okButton = null; + private boolean _cancelPressed = false; + private ColourChooser _colourChooser = null; + + // Filename of Kml file within zip archive + private static final String KML_FILENAME_IN_KMZ = "doc.kml"; + // Default width and height of thumbnail images in Kmz + private static final int DEFAULT_THUMBNAIL_WIDTH = 240; + // Default track colour + private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red + + + /** + * Constructor + * @param inApp app object + */ + public KmlExporter(App inApp) + { + super(inApp); + _trackInfo = inApp.getTrackInfo(); + _track = _trackInfo.getTrack(); + } + + /** Get name key */ + public String getNameKey() { + return "function.exportkml"; + } + + /** + * Show the dialog to select options and export file + */ + public void begin() + { + // Make dialog window including whether to compress to kmz (and include pictures) or not + if (_dialog == null) + { + _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); + _colourChooser = new ColourChooser(_dialog); + } + // Fill in image size from config + _imageSizeField.setValue(Config.getConfigInt(Config.KEY_KMZ_IMAGE_SIZE)); + enableCheckboxes(); + _descriptionField.setEnabled(true); + _okButton.setEnabled(true); + _progressLabel.setText(""); + _progressBar.setVisible(false); + _dialog.setVisible(true); + } + + + /** + * Create dialog components + * @return Panel containing all gui elements in dialog + */ + private Component makeDialogComponents() + { + JPanel dialogPanel = new JPanel(); + dialogPanel.setLayout(new BorderLayout(0, 5)); + 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); + _descriptionField.addKeyListener(new DialogCloser(_dialog)); + descPanel.add(_descriptionField); + descPanel.setAlignmentX(Component.CENTER_ALIGNMENT); + mainPanel.add(descPanel); + dialogPanel.add(mainPanel, BorderLayout.CENTER); + // point type selection + _pointTypeSelector = new PointTypeSelector(); + _pointTypeSelector.setAlignmentX(Component.CENTER_ALIGNMENT); + mainPanel.add(_pointTypeSelector); + // Colour definition + Color trackColour = ColourUtils.colourFromHex(Config.getConfigString(Config.KEY_KML_TRACK_COLOUR)); + if (trackColour == null) { + trackColour = DEFAULT_TRACK_COLOUR; + } + _colourPatch = new ColourPatch(trackColour); + _colourPatch.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + _colourChooser.showDialog(_colourPatch.getBackground()); + Color colour = _colourChooser.getChosenColour(); + if (colour != null) _colourPatch.setColour(colour); + } + }); + JPanel colourPanel = new JPanel(); + colourPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.trackcolour"))); + colourPanel.add(_colourPatch); + mainPanel.add(colourPanel); + // Pair of radio buttons for standard/extended KML + JRadioButton standardKmlRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.standardkml")); + _gxExtensionsRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.extendedkml")); + ButtonGroup bGroup = new ButtonGroup(); + bGroup.add(standardKmlRadio); bGroup.add(_gxExtensionsRadio); + JPanel radioPanel = new JPanel(); + radioPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 1)); + radioPanel.add(standardKmlRadio); + radioPanel.add(_gxExtensionsRadio); + standardKmlRadio.setSelected(true); + mainPanel.add(radioPanel); + // Checkbox for altitude export + _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude")); + _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + _altitudesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT); + mainPanel.add(_altitudesCheckbox); + + // Checkboxes for kmz export and image export + _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz")); + _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + _kmzCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT); + // enable image checkbox if kmz activated + _kmzCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + enableCheckboxes(); + } + }); + mainPanel.add(_kmzCheckbox); + _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages")); + _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + _exportImagesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT); + // enable image size fields if image checkbox changes + _exportImagesCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + enableImageSizeFields(); + } + }); + mainPanel.add(_exportImagesCheckbox); + // Panel for the image size + JPanel imageSizePanel = new JPanel(); + imageSizePanel.setLayout(new FlowLayout(FlowLayout.CENTER)); + _imageSizeLabel = new JLabel(I18nManager.getText("dialog.exportkml.imagesize")); + _imageSizeLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); + imageSizePanel.add(_imageSizeLabel); + _imageSizeField = new WholeNumberField(4); + imageSizePanel.add(_imageSizeField); + mainPanel.add(imageSizePanel); + + mainPanel.add(Box.createVerticalStrut(10)); + _progressLabel = new JLabel("..."); + _progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + mainPanel.add(_progressLabel); + _progressBar = new JProgressBar(0, 100); + _progressBar.setVisible(false); + _progressBar.setAlignmentX(Component.CENTER_ALIGNMENT); + mainPanel.add(_progressBar); + mainPanel.add(Box.createVerticalStrut(10)); + // button panel at bottom + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + _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) + { + _cancelPressed = true; + _dialog.dispose(); + } + }); + buttonPanel.add(cancelButton); + dialogPanel.add(buttonPanel, BorderLayout.SOUTH); + return dialogPanel; + } + + + /** + * Enable the checkboxes according to data + */ + private void enableCheckboxes() + { + _pointTypeSelector.init(_trackInfo); + 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()); + enableImageSizeFields(); + } + + /** + * Enable and disable the image size fields according to the checkboxes + */ + private void enableImageSizeFields() + { + boolean exportImages = _exportImagesCheckbox.isEnabled() && _exportImagesCheckbox.isSelected(); + _imageSizeField.setEnabled(exportImages); + _imageSizeLabel.setEnabled(exportImages); + } + + + /** + * @return true if using gx extensions for kml export + */ + private boolean useGxExtensions() { + return _gxExtensionsRadio.isSelected(); + } + /** + * Start the export process based on the input parameters + */ + private void startExport() + { + // OK pressed, now validate selection checkboxes + if (!_pointTypeSelector.getAnythingSelected()) { + JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"), + I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE); + return; + } + // Choose output file + if (_fileChooser == null) + { + _fileChooser = new JFileChooser(); + _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.setFileFilter(new GenericFileFilter("filetype.kmlkmz", new String[] {"kml", "kmz"})); + // start from directory in config which should be set + String configDir = Config.getConfigString(Config.KEY_TRACK_DIR); + if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} + } + String requiredExtension = null, otherExtension = null; + if (_kmzCheckbox.isSelected()) { + requiredExtension = ".kmz"; otherExtension = ".kml"; + } + else { + requiredExtension = ".kml"; otherExtension = ".kmz"; + } + _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(otherExtension)) + { + String path = file.getAbsolutePath(); + file = new File(path.substring(0, path.length()-otherExtension.length()) + requiredExtension); + } + else if (!file.getName().toLowerCase().endsWith(requiredExtension)) + { + file = new File(file.getAbsolutePath() + requiredExtension); + } + // 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) + { + // New file or overwrite confirmed, so initiate export in separate thread + _exportFile = file; + _cancelPressed = false; + new Thread(this).start(); + } + else + { + chooseAgain = true; + } + } + } while (chooseAgain); + } + + + /** + * Run method for controlling separate thread for exporting + */ + public void run() + { + // Disable ok button to stop second go + _okButton.setEnabled(false); + _descriptionField.setEnabled(false); + // Initialise progress indicators + _progressLabel.setText(I18nManager.getText("confirm.running")); + _progressBar.setVisible(true); + _progressBar.setValue(0); + boolean exportToKmz = _kmzCheckbox.isSelected(); + boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected(); + _progressBar.setMaximum(exportImages?getNumPhotosToExport():1); + + // Create array for image dimensions in case it's required + _imageDimensions = new Dimension[_track.getNumPoints()]; + + 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)); + // Export images into zip file too if requested + if (exportImages) + { + // Get entered value for image size, store in config + int thumbSize = _imageSizeField.getValue(); + if (thumbSize < DEFAULT_THUMBNAIL_WIDTH) {thumbSize = DEFAULT_THUMBNAIL_WIDTH;} + Config.setConfigInt(Config.KEY_KMZ_IMAGE_SIZE, thumbSize); + + // Create thumbnails of each photo in turn and add to zip as images/image.jpg + // This is done first so that photo sizes are known for later + exportThumbnails(zipOutputStream, thumbSize); + } + 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 + final int numPoints = exportData(writer, exportImages); + // update config with selected track colour + Config.setConfigString(Config.KEY_KML_TRACK_COLOUR, ColourUtils.makeHexCode(_colourPatch.getBackground())); + // update progress bar + _progressBar.setValue(1); + + // close zip entry if necessary + if (zipOutputStream != null) + { + // Make sure all buffered data in writer is flushed + writer.flush(); + // Close off this entry in the zip file + zipOutputStream.closeEntry(); + } + + // close file + writer.close(); + _imageDimensions = null; + // Store directory in config for later + Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath()); + // Add to recent file list + Config.getRecentFileList().addFile(new RecentFile(_exportFile, true)); + // show confirmation + UpdateMessageBroker.informSubscribers(); + 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) + { + try { + if (writer != null) writer.close(); + } + catch (IOException ioe2) {} + JOptionPane.showMessageDialog(_parentFrame, + I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(), + I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); + } + // 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 + { + boolean writeTrack = _pointTypeSelector.getTrackpointsSelected(); + boolean writeWaypoints = _pointTypeSelector.getWaypointsSelected(); + boolean writePhotos = _pointTypeSelector.getPhotopointsSelected(); + boolean writeAudios = _pointTypeSelector.getAudiopointsSelected(); + boolean justSelection = _pointTypeSelector.getJustSelection(); + // Define xml header (depending on whether extensions are used or not) + if (useGxExtensions()) { + inWriter.write("\n\n"); + } + else { + inWriter.write("\n\n"); + } + inWriter.write("\n\t"); + if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals("")) + { + inWriter.write(XmlUtils.fixCdata(_descriptionField.getText())); + } + else { + inWriter.write("Export from GpsPrune"); + } + inWriter.write("\n"); + + // Examine selection if required + int selStart = -1, selEnd = -1; + if (justSelection) { + selStart = _trackInfo.getSelection().getStart(); + selEnd = _trackInfo.getSelection().getEnd(); + } + + boolean absoluteAltitudes = _altitudesCheckbox.isSelected(); + int i = 0; + DataPoint point = null; + boolean hasTrackpoints = false; + boolean writtenPhotoHeader = false, writtenAudioHeader = false; + final int numPoints = _track.getNumPoints(); + int numSaved = 0; + int photoNum = 0; + // Loop over waypoints + for (i=0; i=selStart && i<=selEnd); + // Make a blob for each waypoint + if (point.isWaypoint()) + { + if (writeWaypoints && writeCurrentPoint) + { + exportWaypoint(point, inWriter, absoluteAltitudes); + numSaved++; + } + } + else if (!point.hasMedia()) + { + hasTrackpoints = true; + } + // Make a blob with description for each photo + // Photos have already been written so picture sizes already known + if (point.getPhoto() != null && point.getPhoto().isValid() && writePhotos && writeCurrentPoint) + { + if (!writtenPhotoHeader) + { + inWriter.write(""); + writtenPhotoHeader = true; + } + photoNum++; + exportPhotoPoint(point, inWriter, inExportImages, i, photoNum, absoluteAltitudes); + numSaved++; + } + // Make a blob with description for each audio clip + if (point.getAudio() != null && writeAudios && writeCurrentPoint) + { + if (!writtenAudioHeader) + { + inWriter.write(""); + writtenAudioHeader = true; + } + exportAudioPoint(point, inWriter, absoluteAltitudes); + numSaved++; + } + } + // Make a line for the track, if there is one + if (hasTrackpoints && writeTrack) + { + boolean useGxExtensions = _gxExtensionsRadio.isSelected(); + if (useGxExtensions) + { + // Write track using the Google Extensions to KML including gx:Track + numSaved += writeGxTrack(inWriter, absoluteAltitudes, selStart, selEnd); + } + else { + // Write track using standard KML + numSaved += writeStandardTrack(inWriter, absoluteAltitudes, selStart, selEnd); + } + } + inWriter.write("\n\n"); + return numSaved; + } + + + /** + * Write out the track using standard KML LineString tag + * @param inWriter writer object to write to + * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground + * @param inSelStart start index of selection, or -1 if whole track + * @param inSelEnd end index of selection, or -1 if whole track + * @return number of track points written + */ + private int writeStandardTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart, + int inSelEnd) + throws IOException + { + int numSaved = 0; + // Set up strings for start and end of track segment + String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n"; + if (inAbsoluteAltitudes) { + trackStart += "\t\t\t1\n\t\t\tabsolute\n"; + } + else { + trackStart += "\t\t\tclampToGround\n"; + } + trackStart += "\t\t\t"; + String trackEnd = "\t\t\t\n\t\t\n\t"; + + boolean justSelection = _pointTypeSelector.getJustSelection(); + + // Start segment + inWriter.write(trackStart); + // Loop over track points + boolean firstTrackpoint = true; + final int numPoints = _track.getNumPoints(); + for (int i=0; i=inSelStart && i<=inSelEnd); + if (!point.isWaypoint() && writeCurrentPoint) + { + // start new track segment if necessary + if (point.getSegmentStart() && !firstTrackpoint) { + inWriter.write(trackEnd); + inWriter.write(trackStart); + } + if (point.getPhoto() == null) + { + exportTrackpoint(point, inWriter); + numSaved++; + firstTrackpoint = false; + } + } + } + // end segment + inWriter.write(trackEnd); + return numSaved; + } + + + /** + * Write out the track using Google's KML Extensions such as gx:Track + * @param inWriter writer object to write to + * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground + * @param inSelStart start index of selection, or -1 if whole track + * @param inSelEnd end index of selection, or -1 if whole track + * @return number of track points written + */ + private int writeGxTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart, + int inSelEnd) + throws IOException + { + int numSaved = 0; + // Set up strings for start and end of track segment + String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n"; + if (inAbsoluteAltitudes) { + trackStart += "\t\t\t1\n\t\t\tabsolute\n"; + } + else { + trackStart += "\t\t\tclampToGround\n"; + } + String trackEnd = "\n\t\t\n\t\n"; + + boolean justSelection = _pointTypeSelector.getJustSelection(); + + // Start segment + inWriter.write(trackStart); + StringBuilder whenList = new StringBuilder(); + StringBuilder coordList = new StringBuilder(); + + // Loop over track points + boolean firstTrackpoint = true; + final int numPoints = _track.getNumPoints(); + for (int i=0; i=inSelStart && i<=inSelEnd); + if (!point.isWaypoint() && writeCurrentPoint) + { + // start new track segment if necessary + if (point.getSegmentStart() && !firstTrackpoint) + { + inWriter.write(whenList.toString()); + inWriter.write('\n'); + inWriter.write(coordList.toString()); + inWriter.write('\n'); + inWriter.write(trackEnd); + whenList.setLength(0); coordList.setLength(0); + inWriter.write(trackStart); + } + if (point.getPhoto() == null) + { + // Add timestamp (if any) to the list + whenList.append(""); + if (point.hasTimestamp()) { + whenList.append(point.getTimestamp().getText(Timestamp.Format.ISO8601, null)); + } + whenList.append("\n"); + // Add coordinates to the list + coordList.append(""); + coordList.append(point.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' '); + coordList.append(point.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' '); + if (point.hasAltitude()) { + coordList.append("" + point.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES)); + } + else { + coordList.append('0'); + } + coordList.append("\n"); + numSaved++; + firstTrackpoint = false; + } + } + } + // end segment + inWriter.write(whenList.toString()); + inWriter.write('\n'); + inWriter.write(coordList.toString()); + inWriter.write('\n'); + inWriter.write(trackEnd); + return numSaved; + } + + + /** + * Reverse the hex code for the colours for KML's stupid backwards format + * @param inCode colour code rrggbb + * @return kml code bbggrr + */ + private static String reverse(String inCode) + { + return inCode.substring(4, 6) + inCode.substring(2, 4) + inCode.substring(0, 2); + } + + /** + * Export the specified waypoint into the file + * @param inPoint waypoint to export + * @param inWriter writer object + * @param inAbsoluteAltitude true for absolute altitude + * @throws IOException on write failure + */ + private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException + { + String name = inPoint.getWaypointName().trim(); + exportNamedPoint(inPoint, inWriter, name, inPoint.getFieldValue(Field.DESCRIPTION), null, inAbsoluteAltitude); + } + + + /** + * Export the specified audio point into the file + * @param inPoint audio point to export + * @param inWriter writer object + * @param inAbsoluteAltitude true for absolute altitude + * @throws IOException on write failure + */ + private void exportAudioPoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException + { + String name = inPoint.getAudio().getName(); + String desc = null; + if (inPoint.getAudio().getFile() != null) { + desc = inPoint.getAudio().getFile().getAbsolutePath(); + } + exportNamedPoint(inPoint, inWriter, name, desc, "audio_icon", inAbsoluteAltitude); + } + + + /** + * 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 inPointNumber number of point for accessing dimensions + * @param inImageNumber number of image for filename + * @param inAbsoluteAltitude true for absolute altitudes + * @throws IOException on write failure + */ + private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, + int inPointNumber, int inImageNumber, boolean inAbsoluteAltitude) + throws IOException + { + String name = inPoint.getPhoto().getName(); + String desc = null; + if (inImageLink) + { + Dimension imageSize = _imageDimensions[inPointNumber]; + // Create html for the thumbnail images + desc = "" + + "
" + name + "
]]>"; + } + // Export point + exportNamedPoint(inPoint, inWriter, name, desc, "camera_icon", inAbsoluteAltitude); + } + + + /** + * Export the specified named point into the file, like waypoint or photo point + * @param inPoint data point + * @param inWriter writer object + * @param inName name of point + * @param inDesc description of point, or null + * @param inStyle style of point, or null + * @param inAbsoluteAltitude true for absolute altitudes + * @throws IOException on write failure + */ + private void exportNamedPoint(DataPoint inPoint, Writer inWriter, String inName, + String inDesc, String inStyle, boolean inAbsoluteAltitude) + throws IOException + { + inWriter.write("\t\n\t\t"); + inWriter.write(XmlUtils.fixCdata(inName)); + inWriter.write("\n"); + if (inDesc != null) + { + // Write out description + inWriter.write("\t\t"); + inWriter.write(XmlUtils.fixCdata(inDesc)); + inWriter.write("\n"); + } + if (inStyle != null) + { + inWriter.write("#"); + inWriter.write(inStyle); + inWriter.write("\n"); + } + inWriter.write("\t\t\n"); + if (inAbsoluteAltitude && inPoint.hasAltitude()) { + inWriter.write("\t\t\tabsolute\n"); + } + else { + inWriter.write("\t\t\tclampToGround\n"); + } + inWriter.write("\t\t\t"); + inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)); + inWriter.write(','); + inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)); + inWriter.write(','); + // Altitude if point has one + if (inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES)); + } + else { + inWriter.write('0'); + } + inWriter.write("\n\t\t\n\t\n"); + } + + + /** + * Export the specified trackpoint into the file + * @param inPoint trackpoint to export + * @param inWriter writer object + */ + private void exportTrackpoint(DataPoint inPoint, Writer inWriter) throws IOException + { + inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)); + inWriter.write(','); + inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)); + // Altitude if point has one + inWriter.write(','); + if (inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES)); + } + else { + inWriter.write('0'); + } + inWriter.write('\n'); + } + + + /** + * Loop through the photos and create thumbnails + * @param inZipStream zip stream to save image files to + * @param inThumbSize thumbnail size + */ + private void exportThumbnails(ZipOutputStream inZipStream, int inThumbSize) + 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 = writers.next(); + + // Check selection checkbox + final boolean justSelection = _pointTypeSelector.getJustSelection(); + int selStart = -1, selEnd = -1; + if (justSelection) { + selStart = _trackInfo.getSelection().getStart(); + selEnd = _trackInfo.getSelection().getEnd(); + } + + final int numPoints = _track.getNumPoints(); + DataPoint point = null; + int photoNum = 0; + // Loop over all points in track + for (int i=0; i=selStart && i<=selEnd))) + { + photoNum++; + // Make a new entry in zip file + ZipEntry entry = new ZipEntry("images/image" + photoNum + ".jpg"); + inZipStream.putNextEntry(entry); + // Load image and write to outstream + ImageIcon icon = point.getPhoto().createImageIcon(); + + // Scale image to required size (not smoothed) + BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(), + inThumbSize, inThumbSize, point.getPhoto().getRotationDegrees()); + // Store image dimensions so that it doesn't have to be calculated again for the points + _imageDimensions[i] = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight()); + + imageWriter.setOutput(ImageIO.createImageOutputStream(inZipStream)); + imageWriter.write(bufferedImage); + // Close zip file entry + inZipStream.closeEntry(); + // Update progress bar + _progressBar.setValue(photoNum+1); + } + } + } + + + /** + * @return number of correlated photos in the track + */ + private int getNumPhotosToExport() + { + int numPoints = _track.getNumPoints(); + int numPhotos = 0; + DataPoint point = null; + // Loop over all points in track + for (int i=0; i