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