]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/GpxExporter.java
Version 11.1, August 2010
[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 headers
294                 inWriter.write(getXmlHeaderString(inWriter));
295                 inWriter.write(getGpxHeaderString(gpxCachers));
296                 // Name field
297                 String trackName = "PruneTrack";
298                 if (inName != null && !inName.equals(""))
299                 {
300                         trackName = inName;
301                         inWriter.write("\t<name>");
302                         inWriter.write(trackName);
303                         inWriter.write("</name>\n");
304                 }
305                 // Description field
306                 inWriter.write("\t<desc>");
307                 inWriter.write((inDesc != null && !inDesc.equals(""))?inDesc:"Export from Prune");
308                 inWriter.write("</desc>\n");
309
310                 int i = 0;
311                 DataPoint point = null;
312                 boolean hasTrackpoints = false;
313                 final boolean exportTrackpoints = inSaveFlags[0];
314                 final boolean exportWaypoints = inSaveFlags[1];
315                 final boolean exportPhotos = inSaveFlags[2];
316                 final boolean exportSelection = inSaveFlags[3];
317                 final boolean exportTimestamps = inSaveFlags[4];
318                 // Examine selection
319                 int selStart = -1, selEnd = -1;
320                 if (exportSelection) {
321                         selStart = inInfo.getSelection().getStart();
322                         selEnd = inInfo.getSelection().getEnd();
323                 }
324                 // Loop over waypoints
325                 final int numPoints = inInfo.getTrack().getNumPoints();
326                 int numSaved = 0;
327                 for (i=0; i<numPoints; i++)
328                 {
329                         point = inInfo.getTrack().getPoint(i);
330                         if (!exportSelection || (i>=selStart && i<=selEnd)) {
331                                 // Make a wpt element for each waypoint
332                                 if (point.isWaypoint()) {
333                                         if (exportWaypoints)
334                                         {
335                                                 String pointSource = (inUseCopy?getPointSource(gpxCachers, point):null);
336                                                 if (pointSource != null) {
337                                                         inWriter.write(pointSource);
338                                                         inWriter.write('\n');
339                                                 }
340                                                 else {
341                                                         exportWaypoint(point, inWriter, exportTimestamps);
342                                                 }
343                                                 numSaved++;
344                                         }
345                                 }
346                                 else {
347                                         hasTrackpoints = true;
348                                 }
349                         }
350                 }
351                 // Export both route points and then track points
352                 if (hasTrackpoints && (exportTrackpoints || exportPhotos))
353                 {
354                         // Output all route points (if any)
355                         numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
356                                 exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n", null, "\t</rte>\n");
357                         // Output all track points, if any
358                         String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
359                         numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
360                                 exportTimestamps, false, gpxCachers, "<trkpt", trackStart, "\t</trkseg>\n\t<trkseg>\n",
361                                 "\t</trkseg></trk>\n");
362                 }
363
364                 inWriter.write("</gpx>\n");
365                 return numSaved;
366         }
367
368         /**
369          * Loop through the track outputting the relevant track points
370          * @param inWriter writer object for output
371          * @param inInfo track info object containing track
372          * @param inExportSelection true to just output current selection
373          * @param inExportTrackpoints true to output track points
374          * @param inExportPhotos true to output photo points
375          * @param exportTimestamps true to include timestamps in export
376          * @param inOnlyCopies true to only export if source can be copied
377          * @param inCachers list of GpxCachers
378          * @param inPointTag tag to match for each point
379          * @param inStartTag start tag to output
380          * @param inSegmentTag tag to output between segments (or null)
381          * @param inEndTag end tag to output
382          */
383         private static int writeTrackPoints(OutputStreamWriter inWriter,
384                 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
385                 boolean inExportPhotos, boolean exportTimestamps, boolean inOnlyCopies,
386                 GpxCacherList inCachers, String inPointTag, String inStartTag,
387                 String inSegmentTag, String inEndTag)
388         throws IOException
389         {
390                 // Note: far too many input parameters to this method but avoids duplication
391                 // of output functionality for writing track points and route points
392                 int numPoints = inInfo.getTrack().getNumPoints();
393                 int selStart = inInfo.getSelection().getStart();
394                 int selEnd = inInfo.getSelection().getEnd();
395                 int numSaved = 0;
396                 // Loop over track points
397                 for (int i=0; i<numPoints; i++)
398                 {
399                         DataPoint point = inInfo.getTrack().getPoint(i);
400                         if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
401                         {
402                                 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos))
403                                 {
404                                         // get the source from the point (if any)
405                                         String pointSource = getPointSource(inCachers, point);
406                                         boolean writePoint = (pointSource != null && pointSource.toLowerCase().startsWith(inPointTag))
407                                                 || (pointSource == null && !inOnlyCopies);
408                                         if (writePoint)
409                                         {
410                                                 // restart track segment if necessary
411                                                 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
412                                                         inWriter.write(inSegmentTag);
413                                                 }
414                                                 if (numSaved == 0) {inWriter.write(inStartTag);}
415                                                 if (pointSource != null) {
416                                                         inWriter.write(pointSource);
417                                                         inWriter.write('\n');
418                                                 }
419                                                 else {
420                                                         if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps);}
421                                                 }
422                                                 numSaved++;
423                                         }
424                                 }
425                         }
426                 }
427                 if (numSaved > 0) {inWriter.write(inEndTag);}
428                 return numSaved;
429         }
430
431
432         /**
433          * Get the point source for the specified point
434          * @param inCachers list of GPX cachers to ask for source
435          * @param inPoint point object
436          * @return xml source if available, or null otherwise
437          */
438         private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
439         {
440                 if (inCachers == null || inPoint == null) {return null;}
441                 String source = inCachers.getSourceString(inPoint);
442                 if (source == null || !inPoint.isModified()) {return source;}
443                 // Point has been modified - maybe it's possible to modify the source
444                 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
445                 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
446                 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
447                 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
448                 if (inPoint.isWaypoint()) {source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());}  // only for waypoints
449                 return source;
450         }
451
452         /**
453          * Replace the given value into the given XML string
454          * @param inSource source XML for point
455          * @param inStartTag start tag for field
456          * @param inEndTag end tag for field
457          * @param inValue value to replace between start tag and end tag
458          * @return modified String, or null if not possible
459          */
460         private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
461         {
462                 if (inSource == null) {return null;}
463                 // Look for start and end tags within source
464                 final int startPos = inSource.indexOf(inStartTag);
465                 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
466                 if (startPos > 0 && endPos > 0)
467                 {
468                         String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
469                         if (inValue != null && origValue.equals(inValue)) {
470                                 // Value unchanged
471                                 return inSource;
472                         }
473                         else if (inValue == null || inValue.equals("")) {
474                                 // Need to delete value
475                                 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
476                         }
477                         else {
478                                 // Need to replace value
479                                 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
480                         }
481                 }
482                 // Value not found for this field in original source
483                 if (inValue == null || inValue.equals("")) {return inSource;}
484                 return null;
485         }
486
487         /**
488          * Get the header string for the xml document including encoding
489          * @param inWriter writer object
490          * @return header string defining encoding
491          */
492         private static String getXmlHeaderString(OutputStreamWriter inWriter)
493         {
494                 String encoding = inWriter.getEncoding();
495                 final String encodingUpper = encoding.toUpperCase();
496                 if (encodingUpper.equals("UTF8") || encodingUpper.equals("UTF-8")) {
497                         encoding = "UTF-8";
498                 }
499                 return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n";
500         }
501
502         /**
503          * Get the header string for the gpx tag
504          * @param inCachers cacher list to ask for headers, if available
505          * @return header string from cachers or as default
506          */
507         private static String getGpxHeaderString(GpxCacherList inCachers)
508         {
509                 String gpxHeader = null;
510                 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
511                 if (gpxHeader == null || gpxHeader.length() < 5)
512                 {
513                         // Create default (1.0) header
514                         gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
515                                 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
516                                 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
517                                 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
518                 }
519                 return gpxHeader + "\n";
520         }
521
522         /**
523          * Export the specified waypoint into the file
524          * @param inPoint waypoint to export
525          * @param inWriter writer object
526          * @param inTimestamps true to export timestamps too
527          * @throws IOException on write failure
528          */
529         private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
530                 throws IOException
531         {
532                 inWriter.write("\t<wpt lat=\"");
533                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
534                 inWriter.write("\" lon=\"");
535                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
536                 inWriter.write("\">\n");
537                 // altitude if available
538                 if (inPoint.hasAltitude())
539                 {
540                         inWriter.write("\t\t<ele>");
541                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
542                         inWriter.write("</ele>\n");
543                 }
544                 // timestamp if available (point might have timestamp and then be turned into a waypoint)
545                 if (inPoint.hasTimestamp() && inTimestamps)
546                 {
547                         inWriter.write("\t\t<time>");
548                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
549                         inWriter.write("</time>\n");
550                 }
551                 // write waypoint name after elevation and time
552                 inWriter.write("\t\t<name>");
553                 inWriter.write(inPoint.getWaypointName().trim());
554                 inWriter.write("</name>\n");
555                 // write waypoint type if any
556                 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
557                 if (type != null)
558                 {
559                         type = type.trim();
560                         if (!type.equals(""))
561                         {
562                                 inWriter.write("\t\t<type>");
563                                 inWriter.write(type);
564                                 inWriter.write("</type>\n");
565                         }
566                 }
567                 inWriter.write("\t</wpt>\n");
568         }
569
570
571         /**
572          * Export the specified trackpoint into the file
573          * @param inPoint trackpoint to export
574          * @param inWriter writer object
575          * @param inTimestamps true to export timestamps too
576          */
577         private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
578                 throws IOException
579         {
580                 inWriter.write("\t\t<trkpt lat=\"");
581                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
582                 inWriter.write("\" lon=\"");
583                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
584                 inWriter.write("\">");
585                 // altitude
586                 if (inPoint.hasAltitude())
587                 {
588                         inWriter.write("<ele>");
589                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
590                         inWriter.write("</ele>");
591                 }
592                 // timestamp if available (and selected)
593                 if (inPoint.hasTimestamp() && inTimestamps)
594                 {
595                         inWriter.write("<time>");
596                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
597                         inWriter.write("</time>");
598                 }
599                 inWriter.write("</trkpt>\n");
600         }
601 }