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);
350 // name and description
351 String trackName = (inName != null && !inName.equals("")) ? XmlUtils.fixCdata(inName) : "GpsPruneTrack";
352 String desc = (inDesc != null && !inDesc.equals("")) ? XmlUtils.fixCdata(inDesc) : "Export from GpsPrune";
353 writeNameAndDescription(inWriter, trackName, desc, 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 (int 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 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
429 // GPX 1.1 has the name and description inside a metadata tag
430 inWriter.write("\t<metadata>\n");
432 if (inName != null && !inName.equals(""))
434 if (inIsVersion1_1) {inWriter.write('\t');}
435 inWriter.write("\t<name>");
436 inWriter.write(inName);
437 inWriter.write("</name>\n");
439 if (inIsVersion1_1) {inWriter.write('\t');}
440 inWriter.write("\t<desc>");
441 inWriter.write(inDesc);
442 inWriter.write("</desc>\n");
445 inWriter.write("\t</metadata>\n");
450 * Loop through the track outputting the relevant track points
451 * @param inWriter writer object for output
452 * @param inInfo track info object containing track
453 * @param inExportSelection true to just output current selection
454 * @param inExportTrackpoints true to output track points
455 * @param inExportPhotos true to output photo points
456 * @param inExportAudios true to output audio points
457 * @param inExportTimestamps true to include timestamps in export
458 * @param inOnlyCopies true to only export if source can be copied
459 * @param inCachers list of GpxCachers
460 * @param inPointTag tag to match for each point
461 * @param inStartTag start tag to output
462 * @param inSegmentTag tag to output between segments (or null)
463 * @param inEndTag end tag to output
465 private static int writeTrackPoints(OutputStreamWriter inWriter,
466 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
467 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
468 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
469 String inStartTag, String inSegmentTag, String inEndTag)
472 // Note: far too many input parameters to this method but avoids duplication
473 // of output functionality for writing track points and route points
474 int numPoints = inInfo.getTrack().getNumPoints();
475 int selStart = inInfo.getSelection().getStart();
476 int selEnd = inInfo.getSelection().getEnd();
478 // Loop over track points
479 for (int i=0; i<numPoints; i++)
481 DataPoint point = inInfo.getTrack().getPoint(i);
482 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
484 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
485 || (point.getAudio()!=null && inExportAudios))
487 // get the source from the point (if any)
488 String pointSource = getPointSource(inCachers, point);
489 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
490 if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
493 if (pointSource != null || !inOnlyCopies)
495 // restart track segment if necessary
496 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
497 inWriter.write(inSegmentTag);
499 if (numSaved == 0) {inWriter.write(inStartTag);}
500 if (pointSource != null)
502 // If timestamps checkbox is off, strip the time
503 if (!exportTimestamps) {
504 pointSource = stripTime(pointSource);
506 inWriter.write(pointSource);
507 inWriter.write('\n');
510 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
517 if (numSaved > 0) {inWriter.write(inEndTag);}
523 * Get the point source for the specified point
524 * @param inCachers list of GPX cachers to ask for source
525 * @param inPoint point object
526 * @return xml source if available, or null otherwise
528 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
530 if (inCachers == null || inPoint == null) {return null;}
531 String source = inCachers.getSourceString(inPoint);
532 if (source == null || !inPoint.isModified()) {return source;}
533 // Point has been modified - maybe it's possible to modify the source
534 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
535 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
536 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
537 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
538 if (inPoint.isWaypoint())
540 source = replaceGpxTags(source, "<name>", "</name>", XmlUtils.fixCdata(inPoint.getWaypointName()));
543 source = source.replaceAll("<description>", "<desc>").replaceAll("</description>", "</desc>");
545 source = replaceGpxTags(source, "<desc>", "</desc>",
546 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
548 // photo / audio links
549 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
550 source = replaceMediaLinks(source, makeMediaLink(inPoint));
556 * Replace the given value into the given XML string
557 * @param inSource source XML for point
558 * @param inStartTag start tag for field
559 * @param inEndTag end tag for field
560 * @param inValue value to replace between start tag and end tag
561 * @return modified String, or null if not possible
563 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
565 if (inSource == null) {return null;}
566 // Look for start and end tags within source
567 final int startPos = inSource.indexOf(inStartTag);
568 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
569 if (startPos > 0 && endPos > 0)
571 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
572 if (inValue != null && origValue.equals(inValue)) {
576 else if (inValue == null || inValue.equals("")) {
577 // Need to delete value
578 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
581 // Need to replace value
582 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
585 // Value not found for this field in original source
586 if (inValue == null || inValue.equals("")) {return inSource;}
592 * Replace the media tags in the given XML string
593 * @param inSource source XML for point
594 * @param inValue value for the current point
595 * @return modified String, or null if not possible
597 private static String replaceMediaLinks(String inSource, String inValue)
599 if (inSource == null) {return null;}
600 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
601 // and the tags must have attributes. So either one heavily parameterized method or two.
602 // Look for start and end tags within source
603 final String STARTTEXT = "<link";
604 final String ENDTEXT = "</link>";
605 final int startPos = inSource.indexOf(STARTTEXT);
606 final int endPos = inSource.lastIndexOf(ENDTEXT);
607 if (startPos > 0 && endPos > 0)
609 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
610 if (inValue != null && origValue.equals(inValue)) {
614 else if (inValue == null || inValue.equals("")) {
615 // Need to delete value
616 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
619 // Need to replace value
620 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
623 // Value not found for this field in original source
624 if (inValue == null || inValue.equals("")) {return inSource;}
630 * Get the header string for the xml document including encoding
631 * @param inWriter writer object
632 * @return header string defining encoding
634 private static String getXmlHeaderString(OutputStreamWriter inWriter)
636 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
641 * Get the header string for the gpx tag
642 * @param inCachers cacher list to ask for headers, if available
643 * @return header string from cachers or as default
645 private static String getGpxHeaderString(GpxCacherList inCachers)
647 String gpxHeader = null;
648 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
649 if (gpxHeader == null || gpxHeader.length() < 5)
651 // TODO: Consider changing this to default to GPX 1.1
652 // Create default (1.0) header
653 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
654 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
655 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
656 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
658 return gpxHeader + "\n";
663 * Export the specified waypoint into the file
664 * @param inPoint waypoint to export
665 * @param inWriter writer object
666 * @param inTimestamps true to export timestamps too
667 * @param inPhoto true to export link to photo
668 * @param inAudio true to export link to audio
669 * @throws IOException on write failure
671 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
672 boolean inPhoto, boolean inAudio)
675 inWriter.write("\t<wpt lat=\"");
676 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
677 inWriter.write("\" lon=\"");
678 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
679 inWriter.write("\">\n");
680 // altitude if available
681 if (inPoint.hasAltitude())
683 inWriter.write("\t\t<ele>");
684 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
685 inWriter.write("</ele>\n");
687 // timestamp if available (point might have timestamp and then be turned into a waypoint)
688 if (inPoint.hasTimestamp() && inTimestamps)
690 inWriter.write("\t\t<time>");
691 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
692 inWriter.write("</time>\n");
694 // write waypoint name after elevation and time
695 inWriter.write("\t\t<name>");
696 inWriter.write(XmlUtils.fixCdata(inPoint.getWaypointName().trim()));
697 inWriter.write("</name>\n");
698 // description, if any
699 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
700 if (desc != null && !desc.equals(""))
702 inWriter.write("\t\t<desc>");
703 inWriter.write(desc);
704 inWriter.write("</desc>\n");
706 // Media links, if any
707 if (inPhoto && inPoint.getPhoto() != null)
709 inWriter.write("\t\t");
710 inWriter.write(makeMediaLink(inPoint.getPhoto()));
711 inWriter.write('\n');
713 if (inAudio && inPoint.getAudio() != null)
715 inWriter.write("\t\t");
716 inWriter.write(makeMediaLink(inPoint.getAudio()));
717 inWriter.write('\n');
719 // write waypoint type if any
720 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
724 if (!type.equals(""))
726 inWriter.write("\t\t<type>");
727 inWriter.write(type);
728 inWriter.write("</type>\n");
731 inWriter.write("\t</wpt>\n");
736 * Export the specified trackpoint into the file
737 * @param inPoint trackpoint to export
738 * @param inWriter writer object
739 * @param inTimestamps true to export timestamps too
740 * @param inExportPhoto true to export photo link
741 * @param inExportAudio true to export audio link
743 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
744 boolean inExportPhoto, boolean inExportAudio)
747 inWriter.write("\t\t\t<trkpt lat=\"");
748 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
749 inWriter.write("\" lon=\"");
750 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
751 inWriter.write("\">\n");
753 if (inPoint.hasAltitude())
755 inWriter.write("\t\t\t\t<ele>");
756 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
757 inWriter.write("</ele>\n");
759 // timestamp if available (and selected)
760 if (inPoint.hasTimestamp() && inTimestamps)
762 inWriter.write("\t\t\t\t<time>");
763 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
764 inWriter.write("</time>\n");
767 if (inPoint.getPhoto() != null && inExportPhoto) {
768 inWriter.write(makeMediaLink(inPoint.getPhoto()));
770 if (inPoint.getAudio() != null && inExportAudio) {
771 inWriter.write(makeMediaLink(inPoint.getAudio()));
773 inWriter.write("\t\t\t</trkpt>\n");
778 * Make the xml for the media link(s)
779 * @param inPoint point to generate text for
780 * @return link tags, or null if no links
782 private static String makeMediaLink(DataPoint inPoint)
784 Photo photo = inPoint.getPhoto();
785 AudioClip audio = inPoint.getAudio();
786 if (photo == null && audio == null) {
789 String linkText = "";
791 linkText = makeMediaLink(photo);
794 linkText += makeMediaLink(audio);
800 * Make the media link for a single media item
801 * @param inMedia media item, either photo or audio
802 * @return link for this media
804 private static String makeMediaLink(MediaObject inMedia)
806 if (inMedia.getFile() != null)
808 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
809 if (inMedia.getUrl() != null)
811 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
812 // No link available, must have been loaded from zip file - no link possible
818 * Strip the time from a GPX point source string
819 * @param inPointSource point source to copy
820 * @return point source with timestamp removed
822 private static String stripTime(String inPointSource)
824 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");