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 // TODO: Move to new method
294 SettingsForExport settings = new SettingsForExport();
295 settings.setExportTrackPoints(_pointTypeSelector.getTrackpointsSelected());
296 settings.setExportWaypoints(_pointTypeSelector.getWaypointsSelected());
297 settings.setExportPhotoPoints(_pointTypeSelector.getPhotopointsSelected());
298 settings.setExportAudiopoints(_pointTypeSelector.getAudiopointsSelected());
299 settings.setExportJustSelection(_pointTypeSelector.getJustSelection());
300 settings.setExportTimestamps(_timestampsCheckbox.isSelected());
302 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
303 _descriptionField.getText(), settings, gpxCachers);
307 // Store directory in config for later
308 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
309 // Add to recent file list
310 Config.getRecentFileList().addFile(new RecentFile(_exportFile, true));
312 UpdateMessageBroker.informSubscribers();
313 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
314 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
315 + " " + _exportFile.getAbsolutePath());
316 // export successful so need to close dialog and return
320 catch (IOException ioe)
322 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
324 if (writer != null) writer.close();
326 catch (IOException ioe2) {}
327 JOptionPane.showMessageDialog(_parentFrame,
328 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
329 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
331 // if not returned already, export failed so need to recall the file selection
337 * Export the information to the given writer
338 * @param inWriter writer object
339 * @param inInfo track info object
340 * @param inName name of track (optional)
341 * @param inDesc description of track (optional)
342 * @param inExportSettings flags for what to export and how
343 * @param inGpxCachers list of Gpx cachers containing input data
344 * @return number of points written
345 * @throws IOException if io errors occur on write
347 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
348 String inDesc, SettingsForExport inSettings, GpxCacherList inGpxCachers) throws IOException
350 // Write or copy headers
351 inWriter.write(getXmlHeaderString(inWriter));
352 final String gpxHeader = getGpxHeaderString(inGpxCachers);
353 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
354 inWriter.write(gpxHeader);
355 // name and description
356 String trackName = (inName != null && !inName.equals("")) ? XmlUtils.fixCdata(inName) : "GpsPruneTrack";
357 String desc = (inDesc != null && !inDesc.equals("")) ? XmlUtils.fixCdata(inDesc) : "Export from GpsPrune";
358 writeNameAndDescription(inWriter, trackName, desc, isVersion1_1);
360 DataPoint point = null;
361 final boolean exportWaypoints = inSettings.getExportWaypoints();
362 final boolean exportSelection = inSettings.getExportJustSelection();
363 final boolean exportTimestamps = inSettings.getExportTimestamps();
365 int selStart = -1, selEnd = -1;
366 if (exportSelection) {
367 selStart = inInfo.getSelection().getStart();
368 selEnd = inInfo.getSelection().getEnd();
370 // Loop over waypoints
371 final int numPoints = inInfo.getTrack().getNumPoints();
373 for (int i=0; i<numPoints; i++)
375 point = inInfo.getTrack().getPoint(i);
376 if (!exportSelection || (i>=selStart && i<=selEnd))
378 // Make a wpt element for each waypoint
379 if (point.isWaypoint() && exportWaypoints)
381 String pointSource = (inGpxCachers == null? null : getPointSource(inGpxCachers, point));
382 if (pointSource != null)
384 // If timestamp checkbox is off, strip time
385 if (!exportTimestamps) {
386 pointSource = stripTime(pointSource);
388 inWriter.write('\t');
389 inWriter.write(pointSource);
390 inWriter.write('\n');
393 exportWaypoint(point, inWriter, inSettings);
399 // Export both route points and then track points
400 if (inSettings.getExportTrackPoints() || inSettings.getExportPhotoPoints() || inSettings.getExportAudioPoints())
402 // Output all route points (if any)
403 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
404 true, inGpxCachers, "<rtept", "\t<rte><number>1</number>\n",
406 // Output all track points, if any
407 String trackStart = "\t<trk>\n\t\t<name>" + trackName + "</name>\n\t\t<number>1</number>\n\t\t<trkseg>\n";
408 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
409 false, inGpxCachers, "<trkpt", trackStart,
410 "\t</trkseg>\n\t<trkseg>\n", "\t\t</trkseg>\n\t</trk>\n");
413 inWriter.write("</gpx>\n");
419 * Write the name and description according to the GPX version number
420 * @param inWriter writer object
421 * @param inName name, or null if none supplied
422 * @param inDesc description, or null if none supplied
423 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
425 private static void writeNameAndDescription(OutputStreamWriter inWriter,
426 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
428 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
431 // GPX 1.1 has the name and description inside a metadata tag
432 inWriter.write("\t<metadata>\n");
434 if (inName != null && !inName.equals(""))
436 if (inIsVersion1_1) {inWriter.write('\t');}
437 inWriter.write("\t<name>");
438 inWriter.write(inName);
439 inWriter.write("</name>\n");
441 if (inIsVersion1_1) {inWriter.write('\t');}
442 inWriter.write("\t<desc>");
443 inWriter.write(inDesc);
444 inWriter.write("</desc>\n");
447 inWriter.write("\t</metadata>\n");
452 * Loop through the track outputting the relevant track points
453 * @param inWriter writer object for output
454 * @param inInfo track info object containing track
455 * @param inSettings export settings defining what should be exported
456 * @param inOnlyCopies true to only export if source can be copied
457 * @param inCachers list of GpxCachers
458 * @param inPointTag tag to match for each point
459 * @param inStartTag start tag to output
460 * @param inSegmentTag tag to output between segments (or null)
461 * @param inEndTag end tag to output
463 private static int writeTrackPoints(OutputStreamWriter inWriter,
464 TrackInfo inInfo, SettingsForExport inSettings,
465 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
466 String inStartTag, String inSegmentTag, String inEndTag)
469 // Note: Too many input parameters to this method but avoids duplication
470 // of output functionality for writing track points and route points
471 int numPoints = inInfo.getTrack().getNumPoints();
472 int selStart = inInfo.getSelection().getStart();
473 int selEnd = inInfo.getSelection().getEnd();
475 final boolean exportSelection = inSettings.getExportJustSelection();
476 final boolean exportTrackPoints = inSettings.getExportTrackPoints();
477 final boolean exportPhotos = inSettings.getExportPhotoPoints();
478 final boolean exportAudios = inSettings.getExportAudioPoints();
479 final boolean exportTimestamps = inSettings.getExportTimestamps();
480 // Loop over track points
481 for (int i=0; i<numPoints; i++)
483 DataPoint point = inInfo.getTrack().getPoint(i);
484 if ((!exportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
486 if ((point.getPhoto()==null && exportTrackPoints) || (point.getPhoto()!=null && exportPhotos)
487 || (point.getAudio()!=null && exportAudios))
489 // get the source from the point (if any)
490 String pointSource = getPointSource(inCachers, point);
491 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
492 if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
495 if (pointSource != null || !inOnlyCopies)
497 // restart track segment if necessary
498 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
499 inWriter.write(inSegmentTag);
501 if (numSaved == 0) {inWriter.write(inStartTag);}
502 if (pointSource != null)
504 // If timestamps checkbox is off, strip the time
505 if (!exportTimestamps) {
506 pointSource = stripTime(pointSource);
508 inWriter.write(pointSource);
509 inWriter.write('\n');
514 exportTrackpoint(point, inWriter, inSettings);
523 inWriter.write(inEndTag);
530 * Get the point source for the specified point
531 * @param inCachers list of GPX cachers to ask for source
532 * @param inPoint point object
533 * @return xml source if available, or null otherwise
535 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
537 if (inCachers == null || inPoint == null) {return null;}
538 String source = inCachers.getSourceString(inPoint);
539 if (source == null || !inPoint.isModified()) {return source;}
540 // Point has been modified - maybe it's possible to modify the source
541 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
542 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
543 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
544 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
545 if (inPoint.isWaypoint())
547 source = replaceGpxTags(source, "<name>", "</name>", XmlUtils.fixCdata(inPoint.getWaypointName()));
550 source = source.replaceAll("<description>", "<desc>").replaceAll("</description>", "</desc>");
552 source = replaceGpxTags(source, "<desc>", "</desc>",
553 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
555 // photo / audio links
556 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
557 source = replaceMediaLinks(source, makeMediaLink(inPoint));
563 * Replace the given value into the given XML string
564 * @param inSource source XML for point
565 * @param inStartTag start tag for field
566 * @param inEndTag end tag for field
567 * @param inValue value to replace between start tag and end tag
568 * @return modified String, or null if not possible
570 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
572 if (inSource == null) {return null;}
573 // Look for start and end tags within source
574 final int startPos = inSource.indexOf(inStartTag);
575 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
576 if (startPos > 0 && endPos > 0)
578 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
579 if (inValue != null && origValue.equals(inValue)) {
583 else if (inValue == null || inValue.equals("")) {
584 // Need to delete value
585 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
588 // Need to replace value
589 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
592 // Value not found for this field in original source
593 if (inValue == null || inValue.equals("")) {return inSource;}
599 * Replace the media tags in the given XML string
600 * @param inSource source XML for point
601 * @param inValue value for the current point
602 * @return modified String, or null if not possible
604 private static String replaceMediaLinks(String inSource, String inValue)
606 if (inSource == null) {return null;}
607 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
608 // and the tags must have attributes. So either one heavily parameterized method or two.
609 // Look for start and end tags within source
610 final String STARTTEXT = "<link";
611 final String ENDTEXT = "</link>";
612 final int startPos = inSource.indexOf(STARTTEXT);
613 final int endPos = inSource.lastIndexOf(ENDTEXT);
614 if (startPos > 0 && endPos > 0)
616 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
617 if (inValue != null && origValue.equals(inValue)) {
621 else if (inValue == null || inValue.equals("")) {
622 // Need to delete value
623 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
626 // Need to replace value
627 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
630 // Value not found for this field in original source
631 if (inValue == null || inValue.equals("")) {return inSource;}
637 * Get the header string for the xml document including encoding
638 * @param inWriter writer object
639 * @return header string defining encoding
641 private static String getXmlHeaderString(OutputStreamWriter inWriter)
643 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
648 * Get the header string for the gpx tag
649 * @param inCachers cacher list to ask for headers, if available
650 * @return header string from cachers or as default
652 private static String getGpxHeaderString(GpxCacherList inCachers)
654 String gpxHeader = null;
655 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
656 if (gpxHeader == null || gpxHeader.length() < 5)
658 // TODO: Consider changing this to default to GPX 1.1
659 // Create default (1.0) header
660 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
661 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
662 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
663 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
665 return gpxHeader + "\n";
670 * Export the specified waypoint into the file
671 * @param inPoint waypoint to export
672 * @param inWriter writer object
673 * @param inSettings export settings
674 * @throws IOException on write failure
676 private static void exportWaypoint(DataPoint inPoint, Writer inWriter,
677 SettingsForExport inSettings)
680 inWriter.write("\t<wpt lat=\"");
681 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
682 inWriter.write("\" lon=\"");
683 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
684 inWriter.write("\">\n");
685 // altitude if available
686 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
688 inWriter.write("\t\t<ele>");
689 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
690 inWriter.write("</ele>\n");
692 // timestamp if available (some waypoints have timestamps, some not)
693 if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
695 inWriter.write("\t\t<time>");
696 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
697 inWriter.write("</time>\n");
699 // write waypoint name after elevation and time
700 inWriter.write("\t\t<name>");
701 inWriter.write(XmlUtils.fixCdata(inPoint.getWaypointName().trim()));
702 inWriter.write("</name>\n");
703 // description, if any
704 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
705 if (desc != null && !desc.equals(""))
707 inWriter.write("\t\t<desc>");
708 inWriter.write(desc);
709 inWriter.write("</desc>\n");
711 // Media links, if any
712 if (inSettings.getExportPhotoPoints() && inPoint.getPhoto() != null)
714 inWriter.write("\t\t");
715 inWriter.write(makeMediaLink(inPoint.getPhoto()));
716 inWriter.write('\n');
718 if (inSettings.getExportAudioPoints() && inPoint.getAudio() != null)
720 inWriter.write("\t\t");
721 inWriter.write(makeMediaLink(inPoint.getAudio()));
722 inWriter.write('\n');
724 // write waypoint type if any
725 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
729 if (!type.equals(""))
731 inWriter.write("\t\t<type>");
732 inWriter.write(type);
733 inWriter.write("</type>\n");
736 inWriter.write("\t</wpt>\n");
741 * Export the specified trackpoint into the file
742 * @param inPoint trackpoint to export
743 * @param inWriter writer object
744 * @param inSettings export settings
746 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, SettingsForExport inSettings)
749 inWriter.write("\t\t\t<trkpt lat=\"");
750 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
751 inWriter.write("\" lon=\"");
752 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
753 inWriter.write("\">\n");
755 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
757 inWriter.write("\t\t\t\t<ele>");
758 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
759 inWriter.write("</ele>\n");
761 // timestamp if available (and selected)
762 if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
764 inWriter.write("\t\t\t\t<time>");
765 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
766 inWriter.write("</time>\n");
769 if (inPoint.getPhoto() != null && inSettings.getExportPhotoPoints()) {
770 inWriter.write(makeMediaLink(inPoint.getPhoto()));
772 if (inPoint.getAudio() != null && inSettings.getExportAudioPoints()) {
773 inWriter.write(makeMediaLink(inPoint.getAudio()));
775 inWriter.write("\t\t\t</trkpt>\n");
780 * Make the xml for the media link(s)
781 * @param inPoint point to generate text for
782 * @return link tags, or null if no links
784 private static String makeMediaLink(DataPoint inPoint)
786 Photo photo = inPoint.getPhoto();
787 AudioClip audio = inPoint.getAudio();
788 if (photo == null && audio == null) {
791 String linkText = "";
793 linkText = makeMediaLink(photo);
796 linkText += makeMediaLink(audio);
802 * Make the media link for a single media item
803 * @param inMedia media item, either photo or audio
804 * @return link for this media
806 private static String makeMediaLink(MediaObject inMedia)
808 if (inMedia.getFile() != null)
810 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
811 if (inMedia.getUrl() != null)
813 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
814 // No link available, must have been loaded from zip file - no link possible
820 * Strip the time from a GPX point source string
821 * @param inPointSource point source to copy
822 * @return point source with timestamp removed
824 private static String stripTime(String inPointSource)
826 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");