X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=tim%2Fprune%2Fsave%2FKmlExporter.java;h=6fbf7498bf4651e3445e00ae7f566703bc615cd1;hp=e1cc1516ac77e4e8c9da1967f748bd3ee78cd89c;hb=7f5ed2be62905bd37717376dc22d09e5ea7edb4d;hpb=b361869e590bbca32664c16ac24d6296926594a5 diff --git a/tim/prune/save/KmlExporter.java b/tim/prune/save/KmlExporter.java index e1cc151..6fbf749 100644 --- a/tim/prune/save/KmlExporter.java +++ b/tim/prune/save/KmlExporter.java @@ -23,6 +23,7 @@ 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; @@ -32,6 +33,7 @@ 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; @@ -41,17 +43,19 @@ import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; import tim.prune.config.ColourUtils; import tim.prune.config.Config; -import tim.prune.data.Altitude; 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.ColourChooser; import tim.prune.gui.ColourPatch; import tim.prune.gui.DialogCloser; import tim.prune.gui.ImageUtils; +import tim.prune.gui.WholeNumberField; import tim.prune.load.GenericFileFilter; import tim.prune.save.xml.XmlUtils; @@ -66,9 +70,12 @@ public class KmlExporter extends GenericFunction implements Runnable 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; @@ -83,7 +90,6 @@ public class KmlExporter extends GenericFunction implements Runnable 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; - private static final int DEFAULT_THUMBNAIL_HEIGHT = 240; // Default track colour private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red @@ -119,6 +125,8 @@ public class KmlExporter extends GenericFunction implements Runnable _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); @@ -169,19 +177,31 @@ public class KmlExporter extends GenericFunction implements Runnable 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) { - // enable image checkbox if kmz activated enableCheckboxes(); } }); @@ -189,7 +209,23 @@ public class KmlExporter extends GenericFunction implements Runnable _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); @@ -237,9 +273,26 @@ public class KmlExporter extends GenericFunction implements Runnable 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 */ @@ -325,11 +378,6 @@ public class KmlExporter extends GenericFunction implements Runnable boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected(); _progressBar.setMaximum(exportImages?getNumPhotosToExport():1); - // Determine photo thumbnail size from config - int thumbWidth = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH); - if (thumbWidth < DEFAULT_THUMBNAIL_WIDTH) {thumbWidth = DEFAULT_THUMBNAIL_WIDTH;} - int thumbHeight = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT); - if (thumbHeight < DEFAULT_THUMBNAIL_HEIGHT) {thumbHeight = DEFAULT_THUMBNAIL_HEIGHT;} // Create array for image dimensions in case it's required _imageDimensions = new Dimension[_track.getNumPoints()]; @@ -350,9 +398,14 @@ public class KmlExporter extends GenericFunction implements Runnable // 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, thumbWidth, thumbHeight); + exportThumbnails(zipOutputStream, thumbSize); } writer = new OutputStreamWriter(zipOutputStream); // Make an entry in the zip file for the kml file @@ -420,8 +473,14 @@ public class KmlExporter extends GenericFunction implements Runnable boolean writePhotos = _pointTypeSelector.getPhotopointsSelected(); boolean writeAudios = _pointTypeSelector.getAudiopointsSelected(); boolean justSelection = _pointTypeSelector.getJustSelection(); - inWriter.write("\n\n\n"); - inWriter.write("\t"); + // 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(_descriptionField.getText()); @@ -492,51 +551,168 @@ public class KmlExporter extends GenericFunction implements Runnable // Make a line for the track, if there is one if (hasTrackpoints && writeTrack) { - // Set up strings for start and end of track segment - String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n"; - if (absoluteAltitudes) { - trackStart += "\t\t\t1\n\t\t\tabsolute\n"; + boolean useGxExtensions = _gxExtensionsRadio.isSelected(); + if (useGxExtensions) + { + // Write track using the Google Extensions to KML including gx:Track + numSaved += writeGxTrack(inWriter, absoluteAltitudes, selStart, selEnd); } else { - trackStart += "\t\t\tclampToGround\n"; + // Write track using standard KML + numSaved += writeStandardTrack(inWriter, absoluteAltitudes, selStart, selEnd); } - 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 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) { - point = _track.getPoint(i); - boolean writeCurrentPoint = !justSelection || (i>=selStart && i<=selEnd); - if (!point.isWaypoint() && writeCurrentPoint) + // start new track segment if necessary + if (point.getSegmentStart() && !firstTrackpoint) { + inWriter.write(trackEnd); + inWriter.write(trackStart); + } + if (point.getPhoto() == null) { - // start new track segment if necessary - if (point.getSegmentStart() && !firstTrackpoint) { - inWriter.write(trackEnd); - inWriter.write(trackStart); + 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_ISO_8601)); } - if (point.getPhoto() == null) - { - exportTrackpoint(point, inWriter); - numSaved++; - firstTrackpoint = false; + 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(trackEnd); } - inWriter.write("\n"); + // 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 @@ -652,7 +828,7 @@ public class KmlExporter extends GenericFunction implements Runnable inWriter.write(','); // Altitude if point has one if (inPoint.hasAltitude()) { - inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES)); + inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES)); } else { inWriter.write('0'); @@ -674,7 +850,7 @@ public class KmlExporter extends GenericFunction implements Runnable // Altitude if point has one inWriter.write(','); if (inPoint.hasAltitude()) { - inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES)); + inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES)); } else { inWriter.write('0'); @@ -686,10 +862,9 @@ public class KmlExporter extends GenericFunction implements Runnable /** * Loop through the photos and create thumbnails * @param inZipStream zip stream to save image files to - * @param inThumbWidth thumbnail width - * @param inThumbHeight thumbnail height + * @param inThumbSize thumbnail size */ - private void exportThumbnails(ZipOutputStream inZipStream, int inThumbWidth, int inThumbHeight) + private void exportThumbnails(ZipOutputStream inZipStream, int inThumbSize) throws IOException { // set up image writer @@ -724,9 +899,9 @@ public class KmlExporter extends GenericFunction implements Runnable // Load image and write to outstream ImageIcon icon = point.getPhoto().createImageIcon(); - // Scale image to required size TODO: should it also be smoothed, or only if it's smaller than a certain size? + // Scale image to required size (not smoothed) BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(), - inThumbWidth, inThumbHeight, point.getPhoto().getRotationDegrees()); + 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());