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