1 package tim.prune.save;
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;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.OutputStreamWriter;
13 import java.io.Writer;
15 import javax.swing.BorderFactory;
16 import javax.swing.Box;
17 import javax.swing.BoxLayout;
18 import javax.swing.ButtonGroup;
19 import javax.swing.JButton;
20 import javax.swing.JCheckBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JPanel;
27 import javax.swing.JRadioButton;
28 import javax.swing.JTextField;
29 import javax.swing.border.EtchedBorder;
32 import tim.prune.GenericFunction;
33 import tim.prune.GpsPrune;
34 import tim.prune.I18nManager;
35 import tim.prune.UpdateMessageBroker;
36 import tim.prune.config.Config;
37 import tim.prune.data.AudioClip;
38 import tim.prune.data.Coordinate;
39 import tim.prune.data.DataPoint;
40 import tim.prune.data.Field;
41 import tim.prune.data.MediaObject;
42 import tim.prune.data.Photo;
43 import tim.prune.data.RecentFile;
44 import tim.prune.data.Timestamp;
45 import tim.prune.data.TrackInfo;
46 import tim.prune.data.UnitSetLibrary;
47 import tim.prune.gui.DialogCloser;
48 import tim.prune.load.GenericFileFilter;
49 import tim.prune.save.xml.GpxCacherList;
50 import tim.prune.save.xml.XmlUtils;
54 * Class to export track information
55 * into a specified Gpx file
57 public class GpxExporter extends GenericFunction implements Runnable
59 private TrackInfo _trackInfo = null;
60 private JDialog _dialog = null;
61 private JTextField _nameField = null;
62 private JTextField _descriptionField = null;
63 private PointTypeSelector _pointTypeSelector = null;
64 private JCheckBox _timestampsCheckbox = null;
65 private JCheckBox _copySourceCheckbox = null;
66 private JPanel _encodingsPanel = null;
67 private JRadioButton _useSystemRadio = null, _forceUtf8Radio = null;
68 private File _exportFile = null;
70 /** this program name */
71 private static final String GPX_CREATOR = "GpsPrune v" + GpsPrune.VERSION_NUMBER + " activityworkshop.net";
76 * @param inApp app object
78 public GpxExporter(App inApp)
81 _trackInfo = inApp.getTrackInfo();
85 public String getNameKey() {
86 return "function.exportgpx";
90 * Show the dialog to select options and export file
97 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
98 _dialog.setLocationRelativeTo(_parentFrame);
99 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
100 _dialog.getContentPane().add(makeDialogComponents());
103 _pointTypeSelector.init(_app.getTrackInfo());
104 _encodingsPanel.setVisible(!XmlUtils.isSystemUtf8());
105 if (!XmlUtils.isSystemUtf8())
107 String systemEncoding = XmlUtils.getSystemEncoding();
108 _useSystemRadio.setText(I18nManager.getText("dialog.exportgpx.encoding.system")
109 + " (" + (systemEncoding == null ? "unknown" : systemEncoding) + ")");
111 _dialog.setVisible(true);
116 * Create dialog components
117 * @return Panel containing all gui elements in dialog
119 private Component makeDialogComponents()
121 JPanel dialogPanel = new JPanel();
122 dialogPanel.setLayout(new BorderLayout());
123 JPanel mainPanel = new JPanel();
124 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
125 // Make a panel for the name/desc text boxes
126 JPanel descPanel = new JPanel();
127 descPanel.setLayout(new GridLayout(2, 2));
128 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
129 _nameField = new JTextField(10);
130 descPanel.add(_nameField);
131 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
132 _descriptionField = new JTextField(10);
133 descPanel.add(_descriptionField);
134 mainPanel.add(descPanel);
135 mainPanel.add(Box.createVerticalStrut(5));
136 // point type selection (track points, waypoints, photo points)
137 _pointTypeSelector = new PointTypeSelector();
138 mainPanel.add(_pointTypeSelector);
139 // checkboxes for timestamps and copying
140 JPanel checkPanel = new JPanel();
141 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
142 _timestampsCheckbox.setSelected(true);
143 checkPanel.add(_timestampsCheckbox);
144 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
145 _copySourceCheckbox.setSelected(true);
146 checkPanel.add(_copySourceCheckbox);
147 mainPanel.add(checkPanel);
148 // panel for selecting character encoding
149 _encodingsPanel = new JPanel();
150 if (!XmlUtils.isSystemUtf8())
152 // only add this panel if system isn't utf8 (or can't be identified yet)
153 _encodingsPanel.setBorder(BorderFactory.createCompoundBorder(
154 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4)));
155 _encodingsPanel.setLayout(new BorderLayout());
156 _encodingsPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.encoding")), BorderLayout.NORTH);
157 JPanel radioPanel = new JPanel();
158 radioPanel.setLayout(new FlowLayout());
159 ButtonGroup radioGroup = new ButtonGroup();
160 _useSystemRadio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.system"));
161 _forceUtf8Radio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.utf8"));
162 radioGroup.add(_useSystemRadio);
163 radioGroup.add(_forceUtf8Radio);
164 radioPanel.add(_useSystemRadio);
165 radioPanel.add(_forceUtf8Radio);
166 _useSystemRadio.setSelected(true);
167 _encodingsPanel.add(radioPanel, BorderLayout.CENTER);
168 mainPanel.add(_encodingsPanel);
170 dialogPanel.add(mainPanel, BorderLayout.CENTER);
172 // close dialog if escape pressed
173 _nameField.addKeyListener(new DialogCloser(_dialog));
174 // button panel at bottom
175 JPanel buttonPanel = new JPanel();
176 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
177 JButton okButton = new JButton(I18nManager.getText("button.ok"));
178 ActionListener okListener = new ActionListener() {
179 public void actionPerformed(ActionEvent e)
184 okButton.addActionListener(okListener);
185 _descriptionField.addActionListener(okListener);
186 buttonPanel.add(okButton);
187 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
188 cancelButton.addActionListener(new ActionListener() {
189 public void actionPerformed(ActionEvent e) {
193 buttonPanel.add(cancelButton);
194 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
195 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
201 * Start the export process based on the input parameters
203 private void startExport()
205 // OK pressed, so check selections
206 if (!_pointTypeSelector.getAnythingSelected())
208 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
209 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
212 // Choose output file
213 File saveFile = chooseGpxFile(_parentFrame);
214 if (saveFile != null)
216 // New file or overwrite confirmed, so initiate export in separate thread
217 _exportFile = saveFile;
218 new Thread(this).start();
224 * Select a GPX file to save to
225 * @param inParentFrame parent frame for file chooser dialog
226 * @return selected File, or null if selection cancelled
228 public static File chooseGpxFile(JFrame inParentFrame)
230 File saveFile = null;
231 JFileChooser fileChooser = new JFileChooser();
232 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
233 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
234 fileChooser.setAcceptAllFileFilterUsed(false);
235 // start from directory in config which should be set
236 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
237 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
239 // Allow choose again if an existing file is selected
240 boolean chooseAgain = false;
244 if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
246 // OK pressed and file chosen
247 File file = fileChooser.getSelectedFile();
248 // Check file extension
249 if (!file.getName().toLowerCase().endsWith(".gpx"))
251 file = new File(file.getAbsolutePath() + ".gpx");
253 // Check if file exists and if necessary prompt for overwrite
254 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
255 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
256 I18nManager.getText("dialog.save.overwrite.text"),
257 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
258 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
259 == JOptionPane.YES_OPTION)
261 // new file or overwrite confirmed
266 // file exists and overwrite cancelled - select again
270 } while (chooseAgain);
276 * Run method for controlling separate thread for exporting
280 // Instantiate source file cachers in case we want to copy output
281 GpxCacherList gpxCachers = null;
282 if (_copySourceCheckbox.isSelected()) {
283 gpxCachers = new GpxCacherList(_trackInfo.getFileInfo());
285 OutputStreamWriter writer = null;
288 // normal writing to file - firstly specify UTF8 encoding if requested
289 if (_forceUtf8Radio != null && _forceUtf8Radio.isSelected())
290 writer = new OutputStreamWriter(new FileOutputStream(_exportFile), "UTF-8");
292 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
293 final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
294 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
295 _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
297 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
298 _descriptionField.getText(), saveFlags, gpxCachers);
302 // Store directory in config for later
303 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
304 // Add to recent file list
305 Config.getRecentFileList().addFile(new RecentFile(_exportFile, true));
307 UpdateMessageBroker.informSubscribers();
308 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
309 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
310 + " " + _exportFile.getAbsolutePath());
311 // export successful so need to close dialog and return
315 catch (IOException ioe)
317 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
319 if (writer != null) writer.close();
321 catch (IOException ioe2) {}
322 JOptionPane.showMessageDialog(_parentFrame,
323 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
324 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
326 // if not returned already, export failed so need to recall the file selection
332 * Export the information to the given writer
333 * @param inWriter writer object
334 * @param inInfo track info object
335 * @param inName name of track (optional)
336 * @param inDesc description of track (optional)
337 * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
338 * @param inGpxCachers list of Gpx cachers containing input data
339 * @return number of points written
340 * @throws IOException if io errors occur on write
342 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
343 String inDesc, boolean[] inSaveFlags, GpxCacherList inGpxCachers) throws IOException
345 // Write or copy headers
346 inWriter.write(getXmlHeaderString(inWriter));
347 final String gpxHeader = getGpxHeaderString(inGpxCachers);
348 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
349 inWriter.write(gpxHeader);
351 String trackName = (inName != null && !inName.equals("")) ? inName : "GpsPruneTrack";
352 writeNameAndDescription(inWriter, inName, inDesc, isVersion1_1);
355 DataPoint point = null;
356 final boolean exportTrackpoints = inSaveFlags[0];
357 final boolean exportWaypoints = inSaveFlags[1];
358 final boolean exportPhotos = inSaveFlags[2];
359 final boolean exportAudios = inSaveFlags[3];
360 final boolean exportSelection = inSaveFlags[4];
361 final boolean exportTimestamps = inSaveFlags[5];
363 int selStart = -1, selEnd = -1;
364 if (exportSelection) {
365 selStart = inInfo.getSelection().getStart();
366 selEnd = inInfo.getSelection().getEnd();
368 // Loop over waypoints
369 final int numPoints = inInfo.getTrack().getNumPoints();
371 for (i=0; i<numPoints; i++)
373 point = inInfo.getTrack().getPoint(i);
374 if (!exportSelection || (i>=selStart && i<=selEnd))
376 // Make a wpt element for each waypoint
377 if (point.isWaypoint() && exportWaypoints)
379 String pointSource = (inGpxCachers == null? null : getPointSource(inGpxCachers, point));
380 if (pointSource != null)
382 // If timestamp checkbox is off, strip time
383 if (!exportTimestamps) {
384 pointSource = stripTime(pointSource);
386 inWriter.write('\t');
387 inWriter.write(pointSource);
388 inWriter.write('\n');
391 exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
397 // Export both route points and then track points
398 if (exportTrackpoints || exportPhotos || exportAudios)
400 // Output all route points (if any)
401 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
402 exportAudios, exportTimestamps, true, inGpxCachers, "<rtept", "\t<rte><number>1</number>\n",
404 // Output all track points, if any
405 String trackStart = "\t<trk>\n\t\t<name>" + trackName + "</name>\n\t\t<number>1</number>\n\t\t<trkseg>\n";
406 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
407 exportAudios, exportTimestamps, false, inGpxCachers, "<trkpt", trackStart,
408 "\t</trkseg>\n\t<trkseg>\n", "\t\t</trkseg>\n\t</trk>\n");
411 inWriter.write("</gpx>\n");
417 * Write the name and description according to the GPX version number
418 * @param inWriter writer object
419 * @param inName name, or null if none supplied
420 * @param inDesc description, or null if none supplied
421 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
423 private static void writeNameAndDescription(OutputStreamWriter inWriter,
424 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
426 String desc = (inDesc != null && !inDesc.equals("")) ? inDesc : "Export from GpsPrune";
427 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
430 // GPX 1.1 has the name and description inside a metadata tag
431 inWriter.write("\t<metadata>\n");
433 if (inName != null && !inName.equals(""))
435 if (inIsVersion1_1) {inWriter.write('\t');}
436 inWriter.write("\t<name>");
437 inWriter.write(inName);
438 inWriter.write("</name>\n");
440 if (inIsVersion1_1) {inWriter.write('\t');}
441 inWriter.write("\t<desc>");
442 inWriter.write(desc);
443 inWriter.write("</desc>\n");
446 inWriter.write("\t</metadata>\n");
451 * Loop through the track outputting the relevant track points
452 * @param inWriter writer object for output
453 * @param inInfo track info object containing track
454 * @param inExportSelection true to just output current selection
455 * @param inExportTrackpoints true to output track points
456 * @param inExportPhotos true to output photo points
457 * @param inExportAudios true to output audio points
458 * @param inExportTimestamps true to include timestamps in export
459 * @param inOnlyCopies true to only export if source can be copied
460 * @param inCachers list of GpxCachers
461 * @param inPointTag tag to match for each point
462 * @param inStartTag start tag to output
463 * @param inSegmentTag tag to output between segments (or null)
464 * @param inEndTag end tag to output
466 private static int writeTrackPoints(OutputStreamWriter inWriter,
467 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
468 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
469 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
470 String inStartTag, String inSegmentTag, String inEndTag)
473 // Note: far too many input parameters to this method but avoids duplication
474 // of output functionality for writing track points and route points
475 int numPoints = inInfo.getTrack().getNumPoints();
476 int selStart = inInfo.getSelection().getStart();
477 int selEnd = inInfo.getSelection().getEnd();
479 // Loop over track points
480 for (int i=0; i<numPoints; i++)
482 DataPoint point = inInfo.getTrack().getPoint(i);
483 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
485 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
486 || (point.getAudio()!=null && inExportAudios))
488 // get the source from the point (if any)
489 String pointSource = getPointSource(inCachers, point);
490 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
491 if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
494 if (pointSource != null || !inOnlyCopies)
496 // restart track segment if necessary
497 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
498 inWriter.write(inSegmentTag);
500 if (numSaved == 0) {inWriter.write(inStartTag);}
501 if (pointSource != null)
503 // If timestamps checkbox is off, strip the time
504 if (!exportTimestamps) {
505 pointSource = stripTime(pointSource);
507 inWriter.write(pointSource);
508 inWriter.write('\n');
511 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
518 if (numSaved > 0) {inWriter.write(inEndTag);}
524 * Get the point source for the specified point
525 * @param inCachers list of GPX cachers to ask for source
526 * @param inPoint point object
527 * @return xml source if available, or null otherwise
529 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
531 if (inCachers == null || inPoint == null) {return null;}
532 String source = inCachers.getSourceString(inPoint);
533 if (source == null || !inPoint.isModified()) {return source;}
534 // Point has been modified - maybe it's possible to modify the source
535 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
536 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
537 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
538 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
539 if (inPoint.isWaypoint())
541 source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());
542 source = replaceGpxTags(source, "<description>", "</description>",
543 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
545 // photo / audio links
546 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
547 source = replaceMediaLinks(source, makeMediaLink(inPoint));
553 * Replace the given value into the given XML string
554 * @param inSource source XML for point
555 * @param inStartTag start tag for field
556 * @param inEndTag end tag for field
557 * @param inValue value to replace between start tag and end tag
558 * @return modified String, or null if not possible
560 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
562 if (inSource == null) {return null;}
563 // Look for start and end tags within source
564 final int startPos = inSource.indexOf(inStartTag);
565 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
566 if (startPos > 0 && endPos > 0)
568 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
569 if (inValue != null && origValue.equals(inValue)) {
573 else if (inValue == null || inValue.equals("")) {
574 // Need to delete value
575 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
578 // Need to replace value
579 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
582 // Value not found for this field in original source
583 if (inValue == null || inValue.equals("")) {return inSource;}
589 * Replace the media tags in the given XML string
590 * @param inSource source XML for point
591 * @param inValue value for the current point
592 * @return modified String, or null if not possible
594 private static String replaceMediaLinks(String inSource, String inValue)
596 if (inSource == null) {return null;}
597 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
598 // and the tags must have attributes. So either one heavily parameterized method or two.
599 // Look for start and end tags within source
600 final String STARTTEXT = "<link";
601 final String ENDTEXT = "</link>";
602 final int startPos = inSource.indexOf(STARTTEXT);
603 final int endPos = inSource.lastIndexOf(ENDTEXT);
604 if (startPos > 0 && endPos > 0)
606 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
607 if (inValue != null && origValue.equals(inValue)) {
611 else if (inValue == null || inValue.equals("")) {
612 // Need to delete value
613 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
616 // Need to replace value
617 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
620 // Value not found for this field in original source
621 if (inValue == null || inValue.equals("")) {return inSource;}
627 * Get the header string for the xml document including encoding
628 * @param inWriter writer object
629 * @return header string defining encoding
631 private static String getXmlHeaderString(OutputStreamWriter inWriter)
633 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
638 * Get the header string for the gpx tag
639 * @param inCachers cacher list to ask for headers, if available
640 * @return header string from cachers or as default
642 private static String getGpxHeaderString(GpxCacherList inCachers)
644 String gpxHeader = null;
645 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
646 if (gpxHeader == null || gpxHeader.length() < 5)
648 // TODO: Consider changing this to default to GPX 1.1
649 // Create default (1.0) header
650 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
651 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
652 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
653 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
655 return gpxHeader + "\n";
660 * Export the specified waypoint into the file
661 * @param inPoint waypoint to export
662 * @param inWriter writer object
663 * @param inTimestamps true to export timestamps too
664 * @param inPhoto true to export link to photo
665 * @param inAudio true to export link to audio
666 * @throws IOException on write failure
668 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
669 boolean inPhoto, boolean inAudio)
672 inWriter.write("\t<wpt lat=\"");
673 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
674 inWriter.write("\" lon=\"");
675 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
676 inWriter.write("\">\n");
677 // altitude if available
678 if (inPoint.hasAltitude())
680 inWriter.write("\t\t<ele>");
681 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
682 inWriter.write("</ele>\n");
684 // timestamp if available (point might have timestamp and then be turned into a waypoint)
685 if (inPoint.hasTimestamp() && inTimestamps)
687 inWriter.write("\t\t<time>");
688 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
689 inWriter.write("</time>\n");
691 // write waypoint name after elevation and time
692 inWriter.write("\t\t<name>");
693 inWriter.write(inPoint.getWaypointName().trim());
694 inWriter.write("</name>\n");
695 // description, if any
696 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
697 if (desc != null && !desc.equals(""))
699 inWriter.write("\t\t<description>");
700 inWriter.write(desc);
701 inWriter.write("</description>\n");
703 // Media links, if any
704 if (inPhoto && inPoint.getPhoto() != null)
706 inWriter.write("\t\t");
707 inWriter.write(makeMediaLink(inPoint.getPhoto()));
708 inWriter.write('\n');
710 if (inAudio && inPoint.getAudio() != null)
712 inWriter.write("\t\t");
713 inWriter.write(makeMediaLink(inPoint.getAudio()));
714 inWriter.write('\n');
716 // write waypoint type if any
717 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
721 if (!type.equals(""))
723 inWriter.write("\t\t<type>");
724 inWriter.write(type);
725 inWriter.write("</type>\n");
728 inWriter.write("\t</wpt>\n");
733 * Export the specified trackpoint into the file
734 * @param inPoint trackpoint to export
735 * @param inWriter writer object
736 * @param inTimestamps true to export timestamps too
737 * @param inExportPhoto true to export photo link
738 * @param inExportAudio true to export audio link
740 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
741 boolean inExportPhoto, boolean inExportAudio)
744 inWriter.write("\t\t\t<trkpt lat=\"");
745 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
746 inWriter.write("\" lon=\"");
747 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
748 inWriter.write("\">\n");
750 if (inPoint.hasAltitude())
752 inWriter.write("\t\t\t\t<ele>");
753 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
754 inWriter.write("</ele>\n");
756 // timestamp if available (and selected)
757 if (inPoint.hasTimestamp() && inTimestamps)
759 inWriter.write("\t\t\t\t<time>");
760 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
761 inWriter.write("</time>\n");
764 if (inPoint.getPhoto() != null && inExportPhoto) {
765 inWriter.write(makeMediaLink(inPoint.getPhoto()));
767 if (inPoint.getAudio() != null && inExportAudio) {
768 inWriter.write(makeMediaLink(inPoint.getAudio()));
770 inWriter.write("\t\t\t</trkpt>\n");
775 * Make the xml for the media link(s)
776 * @param inPoint point to generate text for
777 * @return link tags, or null if no links
779 private static String makeMediaLink(DataPoint inPoint)
781 Photo photo = inPoint.getPhoto();
782 AudioClip audio = inPoint.getAudio();
783 if (photo == null && audio == null) {
786 String linkText = "";
788 linkText = makeMediaLink(photo);
791 linkText += makeMediaLink(audio);
797 * Make the media link for a single media item
798 * @param inMedia media item, either photo or audio
799 * @return link for this media
801 private static String makeMediaLink(MediaObject inMedia)
803 if (inMedia.getFile() != null)
805 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
806 if (inMedia.getUrl() != null)
808 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
809 // No link available, must have been loaded from zip file - no link possible
815 * Strip the time from a GPX point source string
816 * @param inPointSource point source to copy
817 * @return point source with timestamp removed
819 private static String stripTime(String inPointSource)
821 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");