]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/GpxExporter.java
fd4c869c03fac79608b194eb17ca3ffdf0720b08
[GpsPrune.git] / tim / prune / save / GpxExporter.java
1 package tim.prune.save;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.GridLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.File;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.OutputStreamWriter;
13 import java.io.Writer;
14 import java.nio.charset.Charset;
15
16 import javax.swing.BorderFactory;
17 import javax.swing.Box;
18 import javax.swing.BoxLayout;
19 import javax.swing.JButton;
20 import javax.swing.JCheckBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JPanel;
27 import javax.swing.JTextField;
28
29 import tim.prune.App;
30 import tim.prune.GenericFunction;
31 import tim.prune.GpsPruner;
32 import tim.prune.I18nManager;
33 import tim.prune.UpdateMessageBroker;
34 import tim.prune.config.Config;
35 import tim.prune.data.Altitude;
36 import tim.prune.data.AudioFile;
37 import tim.prune.data.Coordinate;
38 import tim.prune.data.DataPoint;
39 import tim.prune.data.Field;
40 import tim.prune.data.MediaFile;
41 import tim.prune.data.Photo;
42 import tim.prune.data.Timestamp;
43 import tim.prune.data.TrackInfo;
44 import tim.prune.gui.DialogCloser;
45 import tim.prune.load.GenericFileFilter;
46 import tim.prune.save.xml.GpxCacherList;
47
48
49 /**
50  * Class to export track information
51  * into a specified Gpx file
52  */
53 public class GpxExporter extends GenericFunction implements Runnable
54 {
55         private TrackInfo _trackInfo = null;
56         private JDialog _dialog = null;
57         private JTextField _nameField = null;
58         private JTextField _descriptionField = null;
59         private PointTypeSelector _pointTypeSelector = null;
60         private JCheckBox _timestampsCheckbox = null;
61         private JCheckBox _copySourceCheckbox = null;
62         private File _exportFile = null;
63
64         /** this program name */
65         private static final String GPX_CREATOR = "Prune v" + GpsPruner.VERSION_NUMBER + " activityworkshop.net";
66
67
68         /**
69          * Constructor
70          * @param inApp app object
71          */
72         public GpxExporter(App inApp)
73         {
74                 super(inApp);
75                 _trackInfo = inApp.getTrackInfo();
76         }
77
78         /** Get name key */
79         public String getNameKey() {
80                 return "function.exportgpx";
81         }
82
83         /**
84          * Show the dialog to select options and export file
85          */
86         public void begin()
87         {
88                 // Make dialog window
89                 if (_dialog == null)
90                 {
91                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
92                         _dialog.setLocationRelativeTo(_parentFrame);
93                         _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
94                         _dialog.getContentPane().add(makeDialogComponents());
95                         _dialog.pack();
96                 }
97                 _pointTypeSelector.init(_app.getTrackInfo());
98                 _dialog.setVisible(true);
99         }
100
101
102         /**
103          * Create dialog components
104          * @return Panel containing all gui elements in dialog
105          */
106         private Component makeDialogComponents()
107         {
108                 JPanel dialogPanel = new JPanel();
109                 dialogPanel.setLayout(new BorderLayout());
110                 JPanel mainPanel = new JPanel();
111                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
112                 // Make a central panel with the text boxes
113                 JPanel descPanel = new JPanel();
114                 descPanel.setLayout(new GridLayout(2, 2));
115                 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
116                 _nameField = new JTextField(10);
117                 descPanel.add(_nameField);
118                 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
119                 _descriptionField = new JTextField(10);
120                 descPanel.add(_descriptionField);
121                 mainPanel.add(descPanel);
122                 mainPanel.add(Box.createVerticalStrut(5));
123                 // point type selection (track points, waypoints, photo points)
124                 _pointTypeSelector = new PointTypeSelector();
125                 mainPanel.add(_pointTypeSelector);
126                 // checkboxes for timestamps and copying
127                 JPanel checkPanel = new JPanel();
128                 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
129                 _timestampsCheckbox.setSelected(true);
130                 checkPanel.add(_timestampsCheckbox);
131                 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
132                 _copySourceCheckbox.setSelected(true);
133                 checkPanel.add(_copySourceCheckbox);
134                 mainPanel.add(checkPanel);
135                 dialogPanel.add(mainPanel, BorderLayout.CENTER);
136
137                 // close dialog if escape pressed
138                 _nameField.addKeyListener(new DialogCloser(_dialog));
139                 // button panel at bottom
140                 JPanel buttonPanel = new JPanel();
141                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
142                 JButton okButton = new JButton(I18nManager.getText("button.ok"));
143                 ActionListener okListener = new ActionListener() {
144                         public void actionPerformed(ActionEvent e)
145                         {
146                                 startExport();
147                         }
148                 };
149                 okButton.addActionListener(okListener);
150                 _descriptionField.addActionListener(okListener);
151                 buttonPanel.add(okButton);
152                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
153                 cancelButton.addActionListener(new ActionListener() {
154                         public void actionPerformed(ActionEvent e) {
155                                 _dialog.dispose();
156                         }
157                 });
158                 buttonPanel.add(cancelButton);
159                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
160                 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
161                 return dialogPanel;
162         }
163
164
165         /**
166          * Start the export process based on the input parameters
167          */
168         private void startExport()
169         {
170                 // OK pressed, so check selections
171                 if (!_pointTypeSelector.getAnythingSelected()) {
172                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
173                                 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
174                         return;
175                 }
176                 // Choose output file
177                 File saveFile = chooseGpxFile(_parentFrame);
178                 if (saveFile != null)
179                 {
180                         // New file or overwrite confirmed, so initiate export in separate thread
181                         _exportFile = saveFile;
182                         new Thread(this).start();
183                 }
184         }
185
186         /**
187          * Select a GPX file to save to
188          * @param inParentFrame parent frame for file chooser dialog
189          * @return selected File, or null if selection cancelled
190          */
191         public static File chooseGpxFile(JFrame inParentFrame)
192         {
193                 File saveFile = null;
194                 JFileChooser fileChooser = new JFileChooser();
195                 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
196                 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
197                 fileChooser.setAcceptAllFileFilterUsed(false);
198                 // start from directory in config which should be set
199                 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
200                 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
201
202                 // Allow choose again if an existing file is selected
203                 boolean chooseAgain = false;
204                 do
205                 {
206                         chooseAgain = false;
207                         if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
208                         {
209                                 // OK pressed and file chosen
210                                 File file = fileChooser.getSelectedFile();
211                                 // Check file extension
212                                 if (!file.getName().toLowerCase().endsWith(".gpx"))
213                                 {
214                                         file = new File(file.getAbsolutePath() + ".gpx");
215                                 }
216                                 // Check if file exists and if necessary prompt for overwrite
217                                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
218                                 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
219                                                 I18nManager.getText("dialog.save.overwrite.text"),
220                                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
221                                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
222                                         == JOptionPane.YES_OPTION)
223                                 {
224                                         // new file or overwrite confirmed
225                                         saveFile = file;
226                                 }
227                                 else
228                                 {
229                                         // file exists and overwrite cancelled - select again
230                                         chooseAgain = true;
231                                 }
232                         }
233                 } while (chooseAgain);
234                 return saveFile;
235         }
236
237         /**
238          * Run method for controlling separate thread for exporting
239          */
240         public void run()
241         {
242                 OutputStreamWriter writer = null;
243                 try
244                 {
245                         // normal writing to file
246                         writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
247                         final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
248                                 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
249                                 _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
250                         // write file
251                         final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
252                                 _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
253
254                         // close file
255                         writer.close();
256                         // Store directory in config for later
257                         Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
258                         // Show confirmation
259                         UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
260                                  + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
261                                  + " " + _exportFile.getAbsolutePath());
262                         // export successful so need to close dialog and return
263                         _dialog.dispose();
264                         return;
265                 }
266                 catch (IOException ioe)
267                 {
268                         // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
269                         try {
270                                 if (writer != null) writer.close();
271                         }
272                         catch (IOException ioe2) {}
273                         JOptionPane.showMessageDialog(_parentFrame,
274                                 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
275                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
276                 }
277                 // if not returned already, export failed so need to recall the file selection
278                 startExport();
279         }
280
281
282         /**
283          * Export the information to the given writer
284          * @param inWriter writer object
285          * @param inInfo track info object
286          * @param inName name of track (optional)
287          * @param inDesc description of track (optional)
288          * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
289          * @param inUseCopy true to copy source if available
290          * @return number of points written
291          * @throws IOException if io errors occur on write
292          */
293         public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
294                 String inDesc, boolean[] inSaveFlags, boolean inUseCopy) throws IOException
295         {
296                 // Instantiate source file cachers in case we want to copy output
297                 GpxCacherList gpxCachers = null;
298                 if (inUseCopy) gpxCachers = new GpxCacherList(inInfo.getFileInfo());
299                 // Write or copy headers
300                 inWriter.write(getXmlHeaderString(inWriter));
301                 inWriter.write(getGpxHeaderString(gpxCachers));
302                 // Name field
303                 String trackName = "PruneTrack";
304                 if (inName != null && !inName.equals(""))
305                 {
306                         trackName = inName;
307                         inWriter.write("\t<name>");
308                         inWriter.write(trackName);
309                         inWriter.write("</name>\n");
310                 }
311                 // Description field
312                 inWriter.write("\t<desc>");
313                 inWriter.write((inDesc != null && !inDesc.equals(""))?inDesc:"Export from Prune");
314                 inWriter.write("</desc>\n");
315
316                 int i = 0;
317                 DataPoint point = null;
318                 final boolean exportTrackpoints = inSaveFlags[0];
319                 final boolean exportWaypoints = inSaveFlags[1];
320                 final boolean exportPhotos = inSaveFlags[2];
321                 final boolean exportAudios = inSaveFlags[3];
322                 final boolean exportSelection = inSaveFlags[4];
323                 final boolean exportTimestamps = inSaveFlags[5];
324                 // Examine selection
325                 int selStart = -1, selEnd = -1;
326                 if (exportSelection) {
327                         selStart = inInfo.getSelection().getStart();
328                         selEnd = inInfo.getSelection().getEnd();
329                 }
330                 // Loop over waypoints
331                 final int numPoints = inInfo.getTrack().getNumPoints();
332                 int numSaved = 0;
333                 for (i=0; i<numPoints; i++)
334                 {
335                         point = inInfo.getTrack().getPoint(i);
336                         if (!exportSelection || (i>=selStart && i<=selEnd)) {
337                                 // Make a wpt element for each waypoint
338                                 if (point.isWaypoint()) {
339                                         if (exportWaypoints)
340                                         {
341                                                 String pointSource = (inUseCopy?getPointSource(gpxCachers, point):null);
342                                                 if (pointSource != null) {
343                                                         inWriter.write(pointSource);
344                                                         inWriter.write('\n');
345                                                 }
346                                                 else {
347                                                         exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
348                                                 }
349                                                 numSaved++;
350                                         }
351                                 }
352                         }
353                 }
354                 // Export both route points and then track points
355                 if (exportTrackpoints || exportPhotos || exportAudios)
356                 {
357                         // Output all route points (if any)
358                         numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
359                                 exportAudios, exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n",
360                                 null, "\t</rte>\n");
361                         // Output all track points, if any
362                         String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
363                         numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
364                                 exportAudios, exportTimestamps, false, gpxCachers, "<trkpt", trackStart,
365                                 "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
366                 }
367
368                 inWriter.write("</gpx>\n");
369                 return numSaved;
370         }
371
372         /**
373          * Loop through the track outputting the relevant track points
374          * @param inWriter writer object for output
375          * @param inInfo track info object containing track
376          * @param inExportSelection true to just output current selection
377          * @param inExportTrackpoints true to output track points
378          * @param inExportPhotos true to output photo points
379          * @param inExportAudios true to output audio points
380          * @param inExportTimestamps true to include timestamps in export
381          * @param inOnlyCopies true to only export if source can be copied
382          * @param inCachers list of GpxCachers
383          * @param inPointTag tag to match for each point
384          * @param inStartTag start tag to output
385          * @param inSegmentTag tag to output between segments (or null)
386          * @param inEndTag end tag to output
387          */
388         private static int writeTrackPoints(OutputStreamWriter inWriter,
389                 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
390                 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
391                 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
392                 String inStartTag, String inSegmentTag, String inEndTag)
393         throws IOException
394         {
395                 // Note: far too many input parameters to this method but avoids duplication
396                 // of output functionality for writing track points and route points
397                 int numPoints = inInfo.getTrack().getNumPoints();
398                 int selStart = inInfo.getSelection().getStart();
399                 int selEnd = inInfo.getSelection().getEnd();
400                 int numSaved = 0;
401                 // Loop over track points
402                 for (int i=0; i<numPoints; i++)
403                 {
404                         DataPoint point = inInfo.getTrack().getPoint(i);
405                         if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
406                         {
407                                 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
408                                         || (point.getAudio()!=null && inExportAudios))
409                                 {
410                                         // get the source from the point (if any)
411                                         String pointSource = getPointSource(inCachers, point);
412                                         // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
413                                         if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
414                                         if (pointSource != null || !inOnlyCopies)
415                                         {
416                                                 // restart track segment if necessary
417                                                 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
418                                                         inWriter.write(inSegmentTag);
419                                                 }
420                                                 if (numSaved == 0) {inWriter.write(inStartTag);}
421                                                 if (pointSource != null) {
422                                                         inWriter.write(pointSource);
423                                                         inWriter.write('\n');
424                                                 }
425                                                 else {
426                                                         if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
427                                                 }
428                                                 numSaved++;
429                                         }
430                                 }
431                         }
432                 }
433                 if (numSaved > 0) {inWriter.write(inEndTag);}
434                 return numSaved;
435         }
436
437
438         /**
439          * Get the point source for the specified point
440          * @param inCachers list of GPX cachers to ask for source
441          * @param inPoint point object
442          * @return xml source if available, or null otherwise
443          */
444         private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
445         {
446                 if (inCachers == null || inPoint == null) {return null;}
447                 String source = inCachers.getSourceString(inPoint);
448                 if (source == null || !inPoint.isModified()) {return source;}
449                 // Point has been modified - maybe it's possible to modify the source
450                 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
451                 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
452                 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
453                 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
454                 if (inPoint.isWaypoint()) {source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());}  // only for waypoints
455                 // photo / audio links
456                 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
457                         source = replaceMediaLinks(source, makeMediaLink(inPoint));
458                 }
459                 return source;
460         }
461
462         /**
463          * Replace the given value into the given XML string
464          * @param inSource source XML for point
465          * @param inStartTag start tag for field
466          * @param inEndTag end tag for field
467          * @param inValue value to replace between start tag and end tag
468          * @return modified String, or null if not possible
469          */
470         private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
471         {
472                 if (inSource == null) {return null;}
473                 // Look for start and end tags within source
474                 final int startPos = inSource.indexOf(inStartTag);
475                 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
476                 if (startPos > 0 && endPos > 0)
477                 {
478                         String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
479                         if (inValue != null && origValue.equals(inValue)) {
480                                 // Value unchanged
481                                 return inSource;
482                         }
483                         else if (inValue == null || inValue.equals("")) {
484                                 // Need to delete value
485                                 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
486                         }
487                         else {
488                                 // Need to replace value
489                                 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
490                         }
491                 }
492                 // Value not found for this field in original source
493                 if (inValue == null || inValue.equals("")) {return inSource;}
494                 return null;
495         }
496
497         /**
498          * Replace the media tags in the given XML string
499          * @param inSource source XML for point
500          * @param inValue value for the current point
501          * @return modified String, or null if not possible
502          */
503         private static String replaceMediaLinks(String inSource, String inValue)
504         {
505                 if (inSource == null) {return null;}
506                 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
507                 // and the tags must have attributes.  So either one heavily parameterized method or two.
508                 // Look for start and end tags within source
509                 final String STARTTEXT = "<link";
510                 final String ENDTEXT = "</link>";
511                 final int startPos = inSource.indexOf(STARTTEXT);
512                 final int endPos = inSource.lastIndexOf(ENDTEXT);
513                 if (startPos > 0 && endPos > 0)
514                 {
515                         String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
516                         if (inValue != null && origValue.equals(inValue)) {
517                                 // Value unchanged
518                                 return inSource;
519                         }
520                         else if (inValue == null || inValue.equals("")) {
521                                 // Need to delete value
522                                 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
523                         }
524                         else {
525                                 // Need to replace value
526                                 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
527                         }
528                 }
529                 // Value not found for this field in original source
530                 if (inValue == null || inValue.equals("")) {return inSource;}
531                 return null;
532         }
533
534         /**
535          * Get the header string for the xml document including encoding
536          * @param inWriter writer object
537          * @return header string defining encoding
538          */
539         private static String getXmlHeaderString(OutputStreamWriter inWriter)
540         {
541                 String encoding = inWriter.getEncoding();
542                 try {
543                         encoding =  Charset.forName(encoding).name();
544                 }
545                 catch (Exception e) {} // ignore failure to find encoding
546                 return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n";
547         }
548
549         /**
550          * Get the header string for the gpx tag
551          * @param inCachers cacher list to ask for headers, if available
552          * @return header string from cachers or as default
553          */
554         private static String getGpxHeaderString(GpxCacherList inCachers)
555         {
556                 String gpxHeader = null;
557                 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
558                 if (gpxHeader == null || gpxHeader.length() < 5)
559                 {
560                         // Create default (1.0) header
561                         gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
562                                 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
563                                 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
564                                 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
565                 }
566                 return gpxHeader + "\n";
567         }
568
569         /**
570          * Export the specified waypoint into the file
571          * @param inPoint waypoint to export
572          * @param inWriter writer object
573          * @param inTimestamps true to export timestamps too
574          * @param inPhoto true to export link to photo
575          * @param inAudio true to export link to audio
576          * @throws IOException on write failure
577          */
578         private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
579                 boolean inPhoto, boolean inAudio)
580                 throws IOException
581         {
582                 inWriter.write("\t<wpt lat=\"");
583                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
584                 inWriter.write("\" lon=\"");
585                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
586                 inWriter.write("\">\n");
587                 // altitude if available
588                 if (inPoint.hasAltitude())
589                 {
590                         inWriter.write("\t\t<ele>");
591                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
592                         inWriter.write("</ele>\n");
593                 }
594                 // timestamp if available (point might have timestamp and then be turned into a waypoint)
595                 if (inPoint.hasTimestamp() && inTimestamps)
596                 {
597                         inWriter.write("\t\t<time>");
598                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
599                         inWriter.write("</time>\n");
600                 }
601                 // write waypoint name after elevation and time
602                 inWriter.write("\t\t<name>");
603                 inWriter.write(inPoint.getWaypointName().trim());
604                 inWriter.write("</name>\n");
605                 // Media links, if any
606                 if (inPhoto && inPoint.getPhoto() != null)
607                 {
608                         inWriter.write("\t\t");
609                         inWriter.write(makeMediaLink(inPoint.getPhoto()));
610                         inWriter.write('\n');
611                 }
612                 if (inAudio && inPoint.getAudio() != null)
613                 {
614                         inWriter.write("\t\t");
615                         inWriter.write(makeMediaLink(inPoint.getAudio()));
616                         inWriter.write('\n');
617                 }
618                 // write waypoint type if any
619                 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
620                 if (type != null)
621                 {
622                         type = type.trim();
623                         if (!type.equals(""))
624                         {
625                                 inWriter.write("\t\t<type>");
626                                 inWriter.write(type);
627                                 inWriter.write("</type>\n");
628                         }
629                 }
630                 inWriter.write("\t</wpt>\n");
631         }
632
633
634         /**
635          * Export the specified trackpoint into the file
636          * @param inPoint trackpoint to export
637          * @param inWriter writer object
638          * @param inTimestamps true to export timestamps too
639          * @param inExportPhoto true to export photo link
640          * @param inExportAudio true to export audio link
641          */
642         private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
643                 boolean inExportPhoto, boolean inExportAudio)
644                 throws IOException
645         {
646                 inWriter.write("\t\t<trkpt lat=\"");
647                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
648                 inWriter.write("\" lon=\"");
649                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
650                 inWriter.write("\">");
651                 // altitude
652                 if (inPoint.hasAltitude())
653                 {
654                         inWriter.write("<ele>");
655                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
656                         inWriter.write("</ele>");
657                 }
658                 // timestamp if available (and selected)
659                 if (inPoint.hasTimestamp() && inTimestamps)
660                 {
661                         inWriter.write("<time>");
662                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
663                         inWriter.write("</time>");
664                 }
665                 // photo, audio
666                 if (inPoint.getPhoto() != null && inExportPhoto) {
667                         inWriter.write(makeMediaLink(inPoint.getPhoto()));
668                 }
669                 if (inPoint.getAudio() != null && inExportAudio) {
670                         inWriter.write(makeMediaLink(inPoint.getAudio()));
671                 }
672                 inWriter.write("</trkpt>\n");
673         }
674
675         /**
676          * Make the xml for the media link(s)
677          * @param inPoint point to generate text for
678          * @return link tags, or null if no links
679          */
680         private static String makeMediaLink(DataPoint inPoint)
681         {
682                 Photo photo = inPoint.getPhoto();
683                 AudioFile audio = inPoint.getAudio();
684                 if (photo == null && audio == null) {
685                         return null;
686                 }
687                 String linkText = "";
688                 if (photo != null) {
689                         linkText = makeMediaLink(photo);
690                 }
691                 if (audio != null) {
692                         linkText += makeMediaLink(audio);
693                 }
694                 return linkText;
695         }
696
697         /**
698          * Make the media link for a single media item
699          * @param inMedia media item, either photo or audio
700          * @return link for this media
701          */
702         private static String makeMediaLink(MediaFile inMedia)
703         {
704                 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getFile().getName() + "</text></link>";
705         }
706 }