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