]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/KmlExporter.java
Version 8, September 2009
[GpsPrune.git] / tim / prune / save / KmlExporter.java
1 package tim.prune.save;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.image.BufferedImage;
10 import java.io.File;
11 import java.io.FileOutputStream;
12 import java.io.IOException;
13 import java.io.OutputStreamWriter;
14 import java.io.Writer;
15 import java.util.Iterator;
16 import java.util.zip.ZipEntry;
17 import java.util.zip.ZipOutputStream;
18
19 import javax.imageio.ImageIO;
20 import javax.imageio.ImageWriter;
21 import javax.swing.Box;
22 import javax.swing.BoxLayout;
23 import javax.swing.ImageIcon;
24 import javax.swing.JButton;
25 import javax.swing.JCheckBox;
26 import javax.swing.JDialog;
27 import javax.swing.JFileChooser;
28 import javax.swing.JLabel;
29 import javax.swing.JOptionPane;
30 import javax.swing.JPanel;
31 import javax.swing.JProgressBar;
32 import javax.swing.JTextField;
33 import javax.swing.SwingConstants;
34
35 import tim.prune.App;
36 import tim.prune.Config;
37 import tim.prune.GenericFunction;
38 import tim.prune.I18nManager;
39 import tim.prune.UpdateMessageBroker;
40 import tim.prune.data.Altitude;
41 import tim.prune.data.Coordinate;
42 import tim.prune.data.DataPoint;
43 import tim.prune.data.Field;
44 import tim.prune.data.Track;
45 import tim.prune.data.TrackInfo;
46 import tim.prune.gui.ImageUtils;
47 import tim.prune.load.GenericFileFilter;
48
49 /**
50  * Class to export track information
51  * into a specified Kml file
52  */
53 public class KmlExporter extends GenericFunction implements Runnable
54 {
55         private TrackInfo _trackInfo = null;
56         private Track _track = null;
57         private JDialog _dialog = null;
58         private JTextField _descriptionField = null;
59         private PointTypeSelector _pointTypeSelector = null;
60         private JCheckBox _altitudesCheckbox = null;
61         private JCheckBox _kmzCheckbox = null;
62         private JCheckBox _exportImagesCheckbox = null;
63         private JLabel _progressLabel = null;
64         private JProgressBar _progressBar = null;
65         private JFileChooser _fileChooser = null;
66         private File _exportFile = null;
67         private JButton _okButton = null;
68         private boolean _cancelPressed = false;
69
70         // Filename of Kml file within zip archive
71         private static final String KML_FILENAME_IN_KMZ = "doc.kml";
72         // Default width and height of thumbnail images in Kmz
73         private static final int DEFAULT_THUMBNAIL_WIDTH = 240;
74         private static final int DEFAULT_THUMBNAIL_HEIGHT = 180;
75         // Actual selected width and height of thumbnail images in Kmz
76         private static int THUMBNAIL_WIDTH = 0;
77         private static int THUMBNAIL_HEIGHT = 0;
78
79
80         /**
81          * Constructor
82          * @param inApp app object
83          */
84         public KmlExporter(App inApp)
85         {
86                 super(inApp);
87                 _trackInfo = inApp.getTrackInfo();
88                 _track = _trackInfo.getTrack();
89         }
90
91         /** Get name key */
92         public String getNameKey() {
93                 return "function.exportkml";
94         }
95
96         /**
97          * Show the dialog to select options and export file
98          */
99         public void begin()
100         {
101                 // Make dialog window including whether to compress to kmz (and include pictures) or not
102                 if (_dialog == null)
103                 {
104                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
105                         _dialog.setLocationRelativeTo(_parentFrame);
106                         _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
107                         _dialog.getContentPane().add(makeDialogComponents());
108                         _dialog.pack();
109                 }
110                 enableCheckboxes();
111                 _descriptionField.setEnabled(true);
112                 _okButton.setEnabled(true);
113                 _progressLabel.setText("");
114                 _progressBar.setVisible(false);
115                 _dialog.setVisible(true);
116         }
117
118
119         /**
120          * Create dialog components
121          * @return Panel containing all gui elements in dialog
122          */
123         private Component makeDialogComponents()
124         {
125                 JPanel dialogPanel = new JPanel();
126                 dialogPanel.setLayout(new BorderLayout(0, 5));
127                 JPanel mainPanel = new JPanel();
128                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
129                 // Make a central panel with the text box and checkboxes
130                 JPanel descPanel = new JPanel();
131                 descPanel.setLayout(new FlowLayout());
132                 descPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.text")));
133                 _descriptionField = new JTextField(20);
134                 descPanel.add(_descriptionField);
135                 descPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
136                 mainPanel.add(descPanel);
137                 dialogPanel.add(mainPanel, BorderLayout.CENTER);
138                 // point type selection
139                 _pointTypeSelector = new PointTypeSelector();
140                 _pointTypeSelector.setAlignmentX(Component.CENTER_ALIGNMENT);
141                 mainPanel.add(_pointTypeSelector);
142                 // Checkbox for altitude export
143                 _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude"));
144                 _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
145                 _altitudesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
146                 mainPanel.add(_altitudesCheckbox);
147                 // Checkboxes for kmz export and image export
148                 _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
149                 _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
150                 _kmzCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
151                 _kmzCheckbox.addActionListener(new ActionListener() {
152                         public void actionPerformed(ActionEvent e)
153                         {
154                                 // enable image checkbox if kmz activated
155                                 enableCheckboxes();
156                         }
157                 });
158                 mainPanel.add(_kmzCheckbox);
159                 _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
160                 _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
161                 _exportImagesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
162                 mainPanel.add(_exportImagesCheckbox);
163                 mainPanel.add(Box.createVerticalStrut(10));
164                 _progressLabel = new JLabel("...");
165                 _progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
166                 mainPanel.add(_progressLabel);
167                 _progressBar = new JProgressBar(0, 100);
168                 _progressBar.setVisible(false);
169                 _progressBar.setAlignmentX(Component.CENTER_ALIGNMENT);
170                 mainPanel.add(_progressBar);
171                 mainPanel.add(Box.createVerticalStrut(10));
172                 // button panel at bottom
173                 JPanel buttonPanel = new JPanel();
174                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
175                 _okButton = new JButton(I18nManager.getText("button.ok"));
176                 ActionListener okListener = new ActionListener() {
177                         public void actionPerformed(ActionEvent e)
178                         {
179                                 startExport();
180                         }
181                 };
182                 _okButton.addActionListener(okListener);
183                 _descriptionField.addActionListener(okListener);
184                 buttonPanel.add(_okButton);
185                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
186                 cancelButton.addActionListener(new ActionListener() {
187                         public void actionPerformed(ActionEvent e)
188                         {
189                                 _cancelPressed = true;
190                                 _dialog.dispose();
191                         }
192                 });
193                 buttonPanel.add(cancelButton);
194                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
195                 return dialogPanel;
196         }
197
198
199         /**
200          * Enable the checkboxes according to data
201          */
202         private void enableCheckboxes()
203         {
204                 _pointTypeSelector.init(_trackInfo);
205                 boolean hasAltitudes = _track.hasData(Field.ALTITUDE);
206                 if (!hasAltitudes) {_altitudesCheckbox.setSelected(false);}
207                 boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
208                 _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
209                 _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
210         }
211
212
213         /**
214          * Start the export process based on the input parameters
215          */
216         private void startExport()
217         {
218                 // OK pressed, now validate selection checkboxes
219                 if (!_pointTypeSelector.getAnythingSelected()) {
220                         return;
221                 }
222                 // Choose output file
223                 if (_fileChooser == null)
224                 {
225                         _fileChooser = new JFileChooser();
226                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
227                         _fileChooser.setFileFilter(new GenericFileFilter("filetype.kmlkmz", new String[] {"kml", "kmz"}));
228                         // start from directory in config which should be set
229                         String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
230                         if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
231                 }
232                 String requiredExtension = null, otherExtension = null;
233                 if (_kmzCheckbox.isSelected())
234                 {
235                         requiredExtension = ".kmz"; otherExtension = ".kml";
236                 }
237                 else
238                 {
239                         requiredExtension = ".kml"; otherExtension = ".kmz";
240                 }
241                 _fileChooser.setAcceptAllFileFilterUsed(false);
242                 // Allow choose again if an existing file is selected
243                 boolean chooseAgain = false;
244                 do
245                 {
246                         chooseAgain = false;
247                         if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
248                         {
249                                 // OK pressed and file chosen
250                                 File file = _fileChooser.getSelectedFile();
251                                 if (file.getName().toLowerCase().endsWith(otherExtension))
252                                 {
253                                         String path = file.getAbsolutePath();
254                                         file = new File(path.substring(0, path.length()-otherExtension.length()) + requiredExtension);
255                                 }
256                                 else if (!file.getName().toLowerCase().endsWith(requiredExtension))
257                                 {
258                                         file = new File(file.getAbsolutePath() + requiredExtension);
259                                 }
260                                 // Check if file exists and if necessary prompt for overwrite
261                                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
262                                 if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
263                                                 I18nManager.getText("dialog.save.overwrite.text"),
264                                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
265                                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
266                                         == JOptionPane.YES_OPTION)
267                                 {
268                                         // New file or overwrite confirmed, so initiate export in separate thread
269                                         _exportFile = file;
270                                         _cancelPressed = false;
271                                         new Thread(this).start();
272                                 }
273                                 else
274                                 {
275                                         chooseAgain = true;
276                                 }
277                         }
278                 } while (chooseAgain);
279         }
280
281
282         /**
283          * Run method for controlling separate thread for exporting
284          */
285         public void run()
286         {
287                 // Disable ok button to stop second go
288                 _okButton.setEnabled(false);
289                 _descriptionField.setEnabled(false);
290                 // Initialise progress indicators
291                 _progressLabel.setText(I18nManager.getText("confirm.running"));
292                 _progressBar.setVisible(true);
293                 _progressBar.setValue(0);
294                 boolean exportToKmz = _kmzCheckbox.isSelected();
295                 boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
296                 _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
297
298                 // Determine photo thumbnail size from config
299                 THUMBNAIL_WIDTH = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
300                 if (THUMBNAIL_WIDTH < DEFAULT_THUMBNAIL_WIDTH) {THUMBNAIL_WIDTH = DEFAULT_THUMBNAIL_WIDTH;}
301                 THUMBNAIL_HEIGHT = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
302                 if (THUMBNAIL_HEIGHT < DEFAULT_THUMBNAIL_HEIGHT) {THUMBNAIL_HEIGHT = DEFAULT_THUMBNAIL_HEIGHT;}
303
304                 OutputStreamWriter writer = null;
305                 ZipOutputStream zipOutputStream = null;
306                 try
307                 {
308                         // Select writer according to whether kmz requested or not
309                         if (!_kmzCheckbox.isSelected())
310                         {
311                                 // normal writing to file
312                                 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
313                         }
314                         else
315                         {
316                                 // kmz requested - need zip output stream
317                                 zipOutputStream = new ZipOutputStream(new FileOutputStream(_exportFile));
318                                 // Export images into zip file too if requested
319                                 if (exportImages)
320                                 {
321                                         // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
322                                         // This is done first so that photo sizes are known for later
323                                         exportThumbnails(zipOutputStream);
324                                 }
325                                 writer = new OutputStreamWriter(zipOutputStream);
326                                 // Make an entry in the zip file for the kml file
327                                 ZipEntry kmlEntry = new ZipEntry(KML_FILENAME_IN_KMZ);
328                                 zipOutputStream.putNextEntry(kmlEntry);
329                         }
330                         // write file
331                         final int numPoints = exportData(writer, exportImages);
332                         // update progress bar
333                         _progressBar.setValue(1);
334
335                         // close zip entry if necessary
336                         if (zipOutputStream != null)
337                         {
338                                 // Make sure all buffered data in writer is flushed
339                                 writer.flush();
340                                 // Close off this entry in the zip file
341                                 zipOutputStream.closeEntry();
342                         }
343
344                         // close file
345                         writer.close();
346                         // Store directory in config for later
347                         Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
348                         // show confirmation
349                         UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
350                                  + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
351                                  + " " + _exportFile.getAbsolutePath());
352                         // export successful so need to close dialog and return
353                         _dialog.dispose();
354                         return;
355                 }
356                 catch (IOException ioe)
357                 {
358                         // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
359                         try {
360                                 if (writer != null) writer.close();
361                         }
362                         catch (IOException ioe2) {}
363                         JOptionPane.showMessageDialog(_parentFrame,
364                                 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
365                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
366                 }
367                 // if not returned already, export failed so need to recall the file selection
368                 startExport();
369         }
370
371
372         /**
373          * Export the information to the given writer
374          * @param inWriter writer object
375          * @param inExportImages true if image thumbnails are to be referenced
376          * @return number of points written
377          */
378         private int exportData(OutputStreamWriter inWriter, boolean inExportImages)
379         throws IOException
380         {
381                 boolean writeTrack = _pointTypeSelector.getTrackpointsSelected();
382                 boolean writeWaypoints = _pointTypeSelector.getWaypointsSelected();
383                 boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
384                 inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
385                 inWriter.write("\t<name>");
386                 if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
387                 {
388                         inWriter.write(_descriptionField.getText());
389                 }
390                 else
391                 {
392                         inWriter.write("Export from Prune");
393                 }
394                 inWriter.write("</name>\n");
395
396                 boolean absoluteAltitudes = _altitudesCheckbox.isSelected();
397                 int i = 0;
398                 DataPoint point = null;
399                 boolean hasTrackpoints = false;
400                 // Loop over waypoints (if any)
401                 boolean writtenPhotoHeader = false;
402                 final int numPoints = _track.getNumPoints();
403                 int numSaved = 0;
404                 int photoNum = 0;
405                 // Loop over waypoints
406                 for (i=0; i<numPoints; i++)
407                 {
408                         point = _track.getPoint(i);
409                         // Make a blob for each waypoint
410                         if (point.isWaypoint())
411                         {
412                                 if (writeWaypoints) {
413                                         exportWaypoint(point, inWriter, absoluteAltitudes);
414                                         numSaved++;
415                                 }
416                         }
417                         else if (point.getPhoto() == null)
418                         {
419                                 hasTrackpoints = true;
420                         }
421                         // Make a blob with description for each photo
422                         // Photos have already been written so picture sizes already known
423                         if (point.getPhoto() != null && writePhotos)
424                         {
425                                 if (!writtenPhotoHeader)
426                                 {
427                                         inWriter.write("<Style id=\"camera_icon\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon></IconStyle></Style>");
428                                         writtenPhotoHeader = true;
429                                 }
430                                 photoNum++;
431                                 exportPhotoPoint(point, inWriter, inExportImages, photoNum, absoluteAltitudes);
432                                 numSaved++;
433                         }
434                 }
435                 // Make a line for the track, if there is one
436                 if (hasTrackpoints && writeTrack)
437                 {
438                         // Set up strings for start and end of track segment
439                         String trackStart = "\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
440                                 + "\t\t\t\t<color>cc0000cc</color>\n\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
441                                 + "\t\t\t<PolyStyle><color>33cc0000</color></PolyStyle>\n"
442                                 + "\t\t</Style>\n\t\t<LineString>\n";
443                         if (absoluteAltitudes) {
444                                 trackStart += "\t\t\t<extrude>1</extrude>\n\t\t\t<altitudeMode>absolute</altitudeMode>\n";
445                         }
446                         else {
447                                 trackStart += "\t\t\t<altitudeMode>clampToGround</altitudeMode>\n";
448                         }
449                         trackStart += "\t\t\t<coordinates>";
450                         String trackEnd = "\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>";
451
452                         // Start segment
453                         inWriter.write(trackStart);
454                         // Loop over track points
455                         boolean firstTrackpoint = true;
456                         for (i=0; i<numPoints; i++)
457                         {
458                                 point = _track.getPoint(i);
459                                 // start new track segment if necessary
460                                 if (point.getSegmentStart() && !firstTrackpoint) {
461                                         inWriter.write(trackEnd);
462                                         inWriter.write(trackStart);
463                                 }
464                                 if (!point.isWaypoint() && point.getPhoto() == null)
465                                 {
466                                         exportTrackpoint(point, inWriter);
467                                         numSaved++;
468                                         firstTrackpoint = false;
469                                 }
470                         }
471                         // end segment
472                         inWriter.write(trackEnd);
473                 }
474                 inWriter.write("</Folder>\n</kml>");
475                 return numSaved;
476         }
477
478
479         /**
480          * Export the specified waypoint into the file
481          * @param inPoint waypoint to export
482          * @param inWriter writer object
483          * @param inAbsoluteAltitude true for absolute altitude
484          * @throws IOException on write failure
485          */
486         private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inAbsoluteAltitude) throws IOException
487         {
488                 inWriter.write("\t<Placemark>\n\t\t<name>");
489                 inWriter.write(inPoint.getWaypointName().trim());
490                 inWriter.write("</name>\n");
491                 inWriter.write("\t\t<Point>\n");
492                 if (inAbsoluteAltitude && inPoint.hasAltitude()) {
493                         inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
494                 }
495                 else {
496                         inWriter.write("\t\t\t<altitudeMode>clampToGround</altitudeMode>\n");
497                 }
498                 inWriter.write("\t\t\t<coordinates>");
499                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
500                 inWriter.write(',');
501                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
502                 inWriter.write(",");
503                 if (inPoint.hasAltitude()) {
504                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
505                 }
506                 else {
507                         inWriter.write("0");
508                 }
509                 inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
510         }
511
512
513         /**
514          * Export the specified photo into the file
515          * @param inPoint data point including photo
516          * @param inWriter writer object
517          * @param inImageLink flag to set whether to export image links or not
518          * @param inImageNumber number of image for filename
519          * @param inAbsoluteAltitude true for absolute altitudes
520          * @throws IOException on write failure
521          */
522         private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink,
523                 int inImageNumber, boolean inAbsoluteAltitude)
524         throws IOException
525         {
526                 inWriter.write("\t<Placemark>\n\t\t<name>");
527                 inWriter.write(inPoint.getPhoto().getFile().getName());
528                 inWriter.write("</name>\n");
529                 if (inImageLink)
530                 {
531                         // Work out image dimensions of thumbnail
532                         Dimension picSize = inPoint.getPhoto().getSize();
533                         Dimension thumbSize = ImageUtils.getThumbnailSize(picSize.width, picSize.height, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
534                         // Write out some html for the thumbnail images
535                         inWriter.write("<description><![CDATA[<br/><table border='0'><tr><td><center><img src='images/image"
536                                 + inImageNumber + ".jpg' width='" + thumbSize.width + "' height='" + thumbSize.height + "'></center></td></tr>"
537                                 + "<tr><td><center>Caption for the photo</center></td></tr></table>]]></description>");
538                 }
539                 inWriter.write("<styleUrl>#camera_icon</styleUrl>\n");
540                 inWriter.write("\t\t<Point>\n");
541                 if (inAbsoluteAltitude && inPoint.hasAltitude()) {
542                         inWriter.write("\t\t\t<altitudeMode>absolute</altitudeMode>\n");
543                 }
544                 else {
545                         inWriter.write("\t\t\t<altitudeMode>clampToGround</altitudeMode>\n");
546                 }
547                 inWriter.write("\t\t\t<coordinates>");
548                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
549                 inWriter.write(',');
550                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
551                 inWriter.write(",");
552                 // Altitude if point has one
553                 if (inPoint.hasAltitude()) {
554                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
555                 }
556                 else {
557                         inWriter.write("0");
558                 }
559                 inWriter.write("</coordinates>\n\t\t</Point>\n\t</Placemark>\n");
560         }
561
562
563         /**
564          * Export the specified trackpoint into the file
565          * @param inPoint trackpoint to export
566          * @param inWriter writer object
567          */
568         private void exportTrackpoint(DataPoint inPoint, Writer inWriter) throws IOException
569         {
570                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
571                 inWriter.write(',');
572                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
573                 // Altitude if point has one
574                 inWriter.write(",");
575                 if (inPoint.hasAltitude()) {
576                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
577                 }
578                 else {
579                         inWriter.write("0");
580                 }
581                 inWriter.write("\n");
582         }
583
584
585         /**
586          * Loop through the photos and create thumbnails
587          * @param inZipStream zip stream to save image files to
588          */
589         private void exportThumbnails(ZipOutputStream inZipStream) throws IOException
590         {
591                 // set up image writer
592                 Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
593                 if (writers == null || !writers.hasNext())
594                 {
595                         throw new IOException("no JPEG writer found");
596                 }
597                 ImageWriter imageWriter = writers.next();
598
599                 int numPoints = _track.getNumPoints();
600                 DataPoint point = null;
601                 int photoNum = 0;
602                 // Loop over all points in track
603                 for (int i=0; i<numPoints && !_cancelPressed; i++)
604                 {
605                         point = _track.getPoint(i);
606                         if (point.getPhoto() != null)
607                         {
608                                 photoNum++;
609                                 // Make a new entry in zip file
610                                 ZipEntry entry = new ZipEntry("images/image" + photoNum + ".jpg");
611                                 inZipStream.putNextEntry(entry);
612                                 // Load image and write to outstream
613                                 ImageIcon icon = new ImageIcon(point.getPhoto().getFile().getAbsolutePath());
614
615                                 // Scale and smooth image to required size
616                                 Dimension outputSize = ImageUtils.getThumbnailSize(
617                                         point.getPhoto().getWidth(), point.getPhoto().getHeight(),
618                                         THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
619                                 BufferedImage bufferedImage = ImageUtils.createScaledImage(icon.getImage(), outputSize.width, outputSize.height);
620
621                                 imageWriter.setOutput(ImageIO.createImageOutputStream(inZipStream));
622                                 imageWriter.write(bufferedImage);
623                                 // Close zip file entry
624                                 inZipStream.closeEntry();
625                                 // Update progress bar
626                                 _progressBar.setValue(photoNum+1);
627                         }
628                 }
629         }
630
631
632         /**
633          * @return number of correlated photos in the track
634          */
635         private int getNumPhotosToExport()
636         {
637                 int numPoints = _track.getNumPoints();
638                 int numPhotos = 0;
639                 DataPoint point = null;
640                 // Loop over all points in track
641                 for (int i=0; i<numPoints; i++)
642                 {
643                         point = _track.getPoint(i);
644                         if (point.getPhoto() != null) {
645                                 numPhotos++;
646                         }
647                 }
648                 return numPhotos;
649         }
650 }