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>", XmlUtils.fixCdata(inPoint.getWaypointName()));
544 source = source.replaceAll("<description>", "<desc>").replaceAll("</description>", "</desc>");
546 source = replaceGpxTags(source, "<desc>", "</desc>",
547 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
549 // photo / audio links
550 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
551 source = replaceMediaLinks(source, makeMediaLink(inPoint));
557 * Replace the given value into the given XML string
558 * @param inSource source XML for point
559 * @param inStartTag start tag for field
560 * @param inEndTag end tag for field
561 * @param inValue value to replace between start tag and end tag
562 * @return modified String, or null if not possible
564 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
566 if (inSource == null) {return null;}
567 // Look for start and end tags within source
568 final int startPos = inSource.indexOf(inStartTag);
569 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
570 if (startPos > 0 && endPos > 0)
572 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
573 if (inValue != null && origValue.equals(inValue)) {
577 else if (inValue == null || inValue.equals("")) {
578 // Need to delete value
579 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
582 // Need to replace value
583 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
586 // Value not found for this field in original source
587 if (inValue == null || inValue.equals("")) {return inSource;}
593 * Replace the media tags in the given XML string
594 * @param inSource source XML for point
595 * @param inValue value for the current point
596 * @return modified String, or null if not possible
598 private static String replaceMediaLinks(String inSource, String inValue)
600 if (inSource == null) {return null;}
601 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
602 // and the tags must have attributes. So either one heavily parameterized method or two.
603 // Look for start and end tags within source
604 final String STARTTEXT = "<link";
605 final String ENDTEXT = "</link>";
606 final int startPos = inSource.indexOf(STARTTEXT);
607 final int endPos = inSource.lastIndexOf(ENDTEXT);
608 if (startPos > 0 && endPos > 0)
610 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
611 if (inValue != null && origValue.equals(inValue)) {
615 else if (inValue == null || inValue.equals("")) {
616 // Need to delete value
617 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
620 // Need to replace value
621 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
624 // Value not found for this field in original source
625 if (inValue == null || inValue.equals("")) {return inSource;}
631 * Get the header string for the xml document including encoding
632 * @param inWriter writer object
633 * @return header string defining encoding
635 private static String getXmlHeaderString(OutputStreamWriter inWriter)
637 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
642 * Get the header string for the gpx tag
643 * @param inCachers cacher list to ask for headers, if available
644 * @return header string from cachers or as default
646 private static String getGpxHeaderString(GpxCacherList inCachers)
648 String gpxHeader = null;
649 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
650 if (gpxHeader == null || gpxHeader.length() < 5)
652 // TODO: Consider changing this to default to GPX 1.1
653 // Create default (1.0) header
654 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
655 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
656 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
657 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
659 return gpxHeader + "\n";
664 * Export the specified waypoint into the file
665 * @param inPoint waypoint to export
666 * @param inWriter writer object
667 * @param inTimestamps true to export timestamps too
668 * @param inPhoto true to export link to photo
669 * @param inAudio true to export link to audio
670 * @throws IOException on write failure
672 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
673 boolean inPhoto, boolean inAudio)
676 inWriter.write("\t<wpt lat=\"");
677 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
678 inWriter.write("\" lon=\"");
679 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
680 inWriter.write("\">\n");
681 // altitude if available
682 if (inPoint.hasAltitude())
684 inWriter.write("\t\t<ele>");
685 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
686 inWriter.write("</ele>\n");
688 // timestamp if available (point might have timestamp and then be turned into a waypoint)
689 if (inPoint.hasTimestamp() && inTimestamps)
691 inWriter.write("\t\t<time>");
692 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
693 inWriter.write("</time>\n");
695 // write waypoint name after elevation and time
696 inWriter.write("\t\t<name>");
697 inWriter.write(XmlUtils.fixCdata(inPoint.getWaypointName().trim()));
698 inWriter.write("</name>\n");
699 // description, if any
700 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
701 if (desc != null && !desc.equals(""))
703 inWriter.write("\t\t<desc>");
704 inWriter.write(desc);
705 inWriter.write("</desc>\n");
707 // Media links, if any
708 if (inPhoto && inPoint.getPhoto() != null)
710 inWriter.write("\t\t");
711 inWriter.write(makeMediaLink(inPoint.getPhoto()));
712 inWriter.write('\n');
714 if (inAudio && inPoint.getAudio() != null)
716 inWriter.write("\t\t");
717 inWriter.write(makeMediaLink(inPoint.getAudio()));
718 inWriter.write('\n');
720 // write waypoint type if any
721 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
725 if (!type.equals(""))
727 inWriter.write("\t\t<type>");
728 inWriter.write(type);
729 inWriter.write("</type>\n");
732 inWriter.write("\t</wpt>\n");
737 * Export the specified trackpoint into the file
738 * @param inPoint trackpoint to export
739 * @param inWriter writer object
740 * @param inTimestamps true to export timestamps too
741 * @param inExportPhoto true to export photo link
742 * @param inExportAudio true to export audio link
744 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
745 boolean inExportPhoto, boolean inExportAudio)
748 inWriter.write("\t\t\t<trkpt lat=\"");
749 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
750 inWriter.write("\" lon=\"");
751 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
752 inWriter.write("\">\n");
754 if (inPoint.hasAltitude())
756 inWriter.write("\t\t\t\t<ele>");
757 inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
758 inWriter.write("</ele>\n");
760 // timestamp if available (and selected)
761 if (inPoint.hasTimestamp() && inTimestamps)
763 inWriter.write("\t\t\t\t<time>");
764 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
765 inWriter.write("</time>\n");
768 if (inPoint.getPhoto() != null && inExportPhoto) {
769 inWriter.write(makeMediaLink(inPoint.getPhoto()));
771 if (inPoint.getAudio() != null && inExportAudio) {
772 inWriter.write(makeMediaLink(inPoint.getAudio()));
774 inWriter.write("\t\t\t</trkpt>\n");
779 * Make the xml for the media link(s)
780 * @param inPoint point to generate text for
781 * @return link tags, or null if no links
783 private static String makeMediaLink(DataPoint inPoint)
785 Photo photo = inPoint.getPhoto();
786 AudioClip audio = inPoint.getAudio();
787 if (photo == null && audio == null) {
790 String linkText = "";
792 linkText = makeMediaLink(photo);
795 linkText += makeMediaLink(audio);
801 * Make the media link for a single media item
802 * @param inMedia media item, either photo or audio
803 * @return link for this media
805 private static String makeMediaLink(MediaObject inMedia)
807 if (inMedia.getFile() != null)
809 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
810 if (inMedia.getUrl() != null)
812 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
813 // No link available, must have been loaded from zip file - no link possible
819 * Strip the time from a GPX point source string
820 * @param inPointSource point source to copy
821 * @return point source with timestamp removed
823 private static String stripTime(String inPointSource)
825 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");