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 javax.swing.SwingConstants;
import tim.prune.App;
-import tim.prune.Config;
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.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.Track;
import tim.prune.data.TrackInfo;
+import tim.prune.gui.ColourChooser;
+import tim.prune.gui.ColourPatch;
+import tim.prune.gui.DialogCloser;
import tim.prune.gui.ImageUtils;
import tim.prune.load.GenericFileFilter;
+import tim.prune.save.xml.XmlUtils;
/**
* Class to export track information
- * into a specified Kml file
+ * into a specified Kml or Kmz file
*/
public class KmlExporter extends GenericFunction implements Runnable
{
private JCheckBox _altitudesCheckbox = null;
private JCheckBox _kmzCheckbox = null;
private JCheckBox _exportImagesCheckbox = 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;
- private static final int DEFAULT_THUMBNAIL_HEIGHT = 180;
- // Actual selected width and height of thumbnail images in Kmz
- private static int THUMBNAIL_WIDTH = 0;
- private static int THUMBNAIL_HEIGHT = 0;
+ private static final int DEFAULT_THUMBNAIL_HEIGHT = 240;
+ // Default track colour
+ private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red
/**
_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
_dialog.getContentPane().add(makeDialogComponents());
_dialog.pack();
+ _colourChooser = new ColourChooser(_dialog);
}
enableCheckboxes();
_descriptionField.setEnabled(true);
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);
_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);
// Checkbox for altitude export
_altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude"));
_altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
{
// 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 (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
}
String requiredExtension = null, otherExtension = null;
- if (_kmzCheckbox.isSelected())
- {
+ if (_kmzCheckbox.isSelected()) {
requiredExtension = ".kmz"; otherExtension = ".kml";
}
- else
- {
+ else {
requiredExtension = ".kml"; otherExtension = ".kmz";
}
_fileChooser.setAcceptAllFileFilterUsed(false);
_progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
// Determine photo thumbnail size from config
- THUMBNAIL_WIDTH = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
- if (THUMBNAIL_WIDTH < DEFAULT_THUMBNAIL_WIDTH) {THUMBNAIL_WIDTH = DEFAULT_THUMBNAIL_WIDTH;}
- THUMBNAIL_HEIGHT = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
- if (THUMBNAIL_HEIGHT < DEFAULT_THUMBNAIL_HEIGHT) {THUMBNAIL_HEIGHT = DEFAULT_THUMBNAIL_HEIGHT;}
+ 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()];
OutputStreamWriter writer = null;
ZipOutputStream zipOutputStream = null;
{
// Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
// This is done first so that photo sizes are known for later
- exportThumbnails(zipOutputStream);
+ exportThumbnails(zipOutputStream, thumbWidth, thumbHeight);
}
writer = new OutputStreamWriter(zipOutputStream);
// Make an entry in the zip file for the kml file
}
// 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 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());
}
catch (IOException ioe)
{
- // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
try {
if (writer != null) writer.close();
}
boolean writeTrack = _pointTypeSelector.getTrackpointsSelected();
boolean writeWaypoints = _pointTypeSelector.getWaypointsSelected();
boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
+ boolean writeAudios = _pointTypeSelector.getAudiopointsSelected();
+ boolean justSelection = _pointTypeSelector.getJustSelection();
inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
inWriter.write("\t<name>");
if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
{
inWriter.write(_descriptionField.getText());
}
- else
- {
- inWriter.write("Export from Prune");
+ else {
+ inWriter.write("Export from GpsPrune");
}
inWriter.write("</name>\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;
- // Loop over waypoints (if any)
- boolean writtenPhotoHeader = false;
+ boolean writtenPhotoHeader = false, writtenAudioHeader = false;
final int numPoints = _track.getNumPoints();
int numSaved = 0;
int photoNum = 0;
for (i=0; i<numPoints; i++)
{
point = _track.getPoint(i);
+ boolean writeCurrentPoint = !justSelection || (i>=selStart && i<=selEnd);
// Make a blob for each waypoint
if (point.isWaypoint())
{
- if (writeWaypoints) {
+ if (writeWaypoints && writeCurrentPoint)
+ {
exportWaypoint(point, inWriter, absoluteAltitudes);
numSaved++;
}
}
- else if (point.getPhoto() == null)
+ 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 && writePhotos)
+ if (point.getPhoto() != null && writePhotos && writeCurrentPoint)
{
if (!writtenPhotoHeader)
{
writtenPhotoHeader = true;
}
photoNum++;
- exportPhotoPoint(point, inWriter, inExportImages, photoNum, absoluteAltitudes);
+ 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("<Style id=\"audio_icon\"><IconStyle><color>ff00ffff</color><Icon><href>http://maps.google.com/mapfiles/kml/shapes/star.png</href></Icon></IconStyle></Style>");
+ writtenAudioHeader = true;
+ }
+ exportAudioPoint(point, inWriter, absoluteAltitudes);
numSaved++;
}
}
{
// Set up strings for start and end of track segment
String trackStart = "\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
- + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+ + "\t\t\t\t<color>cc" + reverse(ColourUtils.makeHexCode(_colourPatch.getBackground())) + "</color>\n"
+ + "\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+ "\t\t\t<PolyStyle><color>33cc0000</color></PolyStyle>\n"
+ "\t\t</Style>\n\t\t<LineString>\n";
if (absoluteAltitudes) {
for (i=0; i<numPoints; i++)
{
point = _track.getPoint(i);
- // start new track segment if necessary
- if (point.getSegmentStart() && !firstTrackpoint) {
- inWriter.write(trackEnd);
- inWriter.write(trackStart);
- }
- if (!point.isWaypoint() && point.getPhoto() == null)
+ boolean writeCurrentPoint = !justSelection || (i>=selStart && i<=selEnd);
+ if (!point.isWaypoint() && writeCurrentPoint)
{
- exportTrackpoint(point, inWriter);
- numSaved++;
- firstTrackpoint = false;
+ // 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
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
*/
private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException
{
- inWriter.write("\t<Placemark>\n\t\t<name>");
- inWriter.write(inPoint.getWaypointName().trim());
- inWriter.write("</name>\n");
- inWriter.write("\t\t<Point>\n");
- if (inAbsoluteAltitude && inPoint.hasAltitude()) {
- inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
- }
- else {
- inWriter.write("\t\t\t<altitudeMode>clampToGround</altitudeMode>\n");
- }
- inWriter.write("\t\t\t<coordinates>");
- inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
- inWriter.write(',');
- inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
- inWriter.write(",");
- if (inPoint.hasAltitude()) {
- inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
- }
- else {
- inWriter.write("0");
+ 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();
}
- inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
+ exportNamedPoint(inPoint, inWriter, name, desc, "audio_icon", inAbsoluteAltitude);
}
* @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 inImageNumber, boolean inAbsoluteAltitude)
+ 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 = "<![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
+ + inImageNumber + ".jpg' width='" + imageSize.width + "' height='" + imageSize.height + "'></center></td></tr>"
+ + "<tr><td><center>" + name + "</center></td></tr></table>]]>";
+ }
+ // 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<Placemark>\n\t\t<name>");
- inWriter.write(inPoint.getPhoto().getFile().getName());
+ inWriter.write(inName);
inWriter.write("</name>\n");
- if (inImageLink)
+ if (inDesc != null)
+ {
+ // Write out description
+ inWriter.write("\t\t<description>");
+ inWriter.write(XmlUtils.fixCdata(inDesc));
+ inWriter.write("</description>\n");
+ }
+ if (inStyle != null)
{
- // 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("<description><![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
- + inImageNumber + ".jpg' width='" + thumbSize.width + "' height='" + thumbSize.height + "'></center></td></tr>"
- + "<tr><td><center>Caption for the photo</center></td></tr></table>]]></description>");
+ inWriter.write("<styleUrl>#");
+ inWriter.write(inStyle);
+ inWriter.write("</styleUrl>\n");
}
- inWriter.write("<styleUrl>#camera_icon</styleUrl>\n");
inWriter.write("\t\t<Point>\n");
if (inAbsoluteAltitude && inPoint.hasAltitude()) {
inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
inWriter.write(',');
inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
- inWriter.write(",");
+ inWriter.write(',');
// Altitude if point has one
if (inPoint.hasAltitude()) {
inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
}
else {
- inWriter.write("0");
+ inWriter.write('0');
}
inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
}
inWriter.write(',');
inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
// Altitude if point has one
- inWriter.write(",");
+ inWriter.write(',');
if (inPoint.hasAltitude()) {
inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
}
else {
- inWriter.write("0");
+ inWriter.write('0');
}
- inWriter.write("\n");
+ inWriter.write('\n');
}
/**
* Loop through the photos and create thumbnails
* @param inZipStream zip stream to save image files to
+ * @param inThumbWidth thumbnail width
+ * @param inThumbHeight thumbnail height
*/
- private void exportThumbnails(ZipOutputStream inZipStream) throws IOException
+ private void exportThumbnails(ZipOutputStream inZipStream, int inThumbWidth, int inThumbHeight)
+ throws IOException
{
// set up image writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
}
ImageWriter imageWriter = writers.next();
+ // Check selection checkbox
+ boolean justSelection = _pointTypeSelector.getJustSelection();
+ int selStart = -1, selEnd = -1;
+ if (justSelection) {
+ selStart = _trackInfo.getSelection().getStart();
+ selEnd = _trackInfo.getSelection().getEnd();
+ }
+
int numPoints = _track.getNumPoints();
DataPoint point = null;
int photoNum = 0;
for (int i=0; i<numPoints && !_cancelPressed; i++)
{
point = _track.getPoint(i);
- if (point.getPhoto() != null)
+ if (point.getPhoto() != null && (!justSelection || (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 = new ImageIcon(point.getPhoto().getFile().getAbsolutePath());
+ ImageIcon icon = point.getPhoto().createImageIcon();
- // Scale and smooth image to required size
- Dimension outputSize = ImageUtils.getThumbnailSize(
- point.getPhoto().getWidth(), point.getPhoto().getHeight(),
- THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
- BufferedImage bufferedImage = ImageUtils.createScaledImage(icon.getImage(), outputSize.width, outputSize.height);
+ // Scale image to required size TODO: should it also be smoothed, or only if it's smaller than a certain size?
+ BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(),
+ inThumbWidth, inThumbHeight, 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);