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.SourceInfo;
45 import tim.prune.data.Timestamp;
46 import tim.prune.data.TrackInfo;
47 import tim.prune.data.UnitSetLibrary;
48 import tim.prune.gui.DialogCloser;
49 import tim.prune.load.GenericFileFilter;
50 import tim.prune.save.xml.GpxCacherList;
51 import tim.prune.save.xml.XmlUtils;
55 * Class to export track information
56 * into a specified Gpx file
58 public class GpxExporter extends GenericFunction implements Runnable
60 private TrackInfo _trackInfo = null;
61 private JDialog _dialog = null;
62 private JTextField _nameField = null;
63 private JTextField _descriptionField = null;
64 private PointTypeSelector _pointTypeSelector = null;
65 private JCheckBox _timestampsCheckbox = null;
66 private JCheckBox _copySourceCheckbox = null;
67 private JPanel _encodingsPanel = null;
68 private JRadioButton _useSystemRadio = null, _forceUtf8Radio = null;
69 private File _exportFile = null;
70 /** Remember the previous sourceInfo object to tell whether it has changed */
71 private SourceInfo _previousSourceInfo = null;
73 /** this program name */
74 private static final String GPX_CREATOR = "GpsPrune v" + GpsPrune.VERSION_NUMBER + " activityworkshop.net";
79 * @param inApp app object
81 public GpxExporter(App inApp)
84 _trackInfo = inApp.getTrackInfo();
88 public String getNameKey() {
89 return "function.exportgpx";
93 * Show the dialog to select options and export file
100 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
101 _dialog.setLocationRelativeTo(_parentFrame);
102 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
103 _dialog.getContentPane().add(makeDialogComponents());
106 _pointTypeSelector.init(_app.getTrackInfo());
107 _encodingsPanel.setVisible(!XmlUtils.isSystemUtf8());
108 if (!XmlUtils.isSystemUtf8())
110 String systemEncoding = XmlUtils.getSystemEncoding();
111 _useSystemRadio.setText(I18nManager.getText("dialog.exportgpx.encoding.system")
112 + " (" + (systemEncoding == null ? "unknown" : systemEncoding) + ")");
115 _dialog.setVisible(true);
120 * Create dialog components
121 * @return Panel containing all gui elements in dialog
123 private Component makeDialogComponents()
125 JPanel dialogPanel = new JPanel();
126 dialogPanel.setLayout(new BorderLayout());
127 JPanel mainPanel = new JPanel();
128 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
129 // Make a panel for the name/desc text boxes
130 JPanel descPanel = new JPanel();
131 descPanel.setLayout(new GridLayout(2, 2));
132 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
133 _nameField = new JTextField(10);
134 descPanel.add(_nameField);
135 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
136 _descriptionField = new JTextField(10);
137 descPanel.add(_descriptionField);
138 mainPanel.add(descPanel);
139 mainPanel.add(Box.createVerticalStrut(5));
140 // point type selection (track points, waypoints, photo points)
141 _pointTypeSelector = new PointTypeSelector();
142 mainPanel.add(_pointTypeSelector);
143 // checkboxes for timestamps and copying
144 JPanel checkPanel = new JPanel();
145 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
146 _timestampsCheckbox.setSelected(true);
147 checkPanel.add(_timestampsCheckbox);
148 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
149 _copySourceCheckbox.setSelected(true);
150 checkPanel.add(_copySourceCheckbox);
151 mainPanel.add(checkPanel);
152 // panel for selecting character encoding
153 _encodingsPanel = new JPanel();
154 if (!XmlUtils.isSystemUtf8())
156 // only add this panel if system isn't utf8 (or can't be identified yet)
157 _encodingsPanel.setBorder(BorderFactory.createCompoundBorder(
158 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4)));
159 _encodingsPanel.setLayout(new BorderLayout());
160 _encodingsPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.encoding")), BorderLayout.NORTH);
161 JPanel radioPanel = new JPanel();
162 radioPanel.setLayout(new FlowLayout());
163 ButtonGroup radioGroup = new ButtonGroup();
164 _useSystemRadio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.system"));
165 _forceUtf8Radio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.utf8"));
166 radioGroup.add(_useSystemRadio);
167 radioGroup.add(_forceUtf8Radio);
168 radioPanel.add(_useSystemRadio);
169 radioPanel.add(_forceUtf8Radio);
170 _useSystemRadio.setSelected(true);
171 _encodingsPanel.add(radioPanel, BorderLayout.CENTER);
172 mainPanel.add(_encodingsPanel);
174 dialogPanel.add(mainPanel, BorderLayout.CENTER);
176 // close dialog if escape pressed
177 _nameField.addKeyListener(new DialogCloser(_dialog));
178 // button panel at bottom
179 JPanel buttonPanel = new JPanel();
180 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
181 JButton okButton = new JButton(I18nManager.getText("button.ok"));
182 ActionListener okListener = new ActionListener() {
183 public void actionPerformed(ActionEvent e)
188 okButton.addActionListener(okListener);
189 _descriptionField.addActionListener(okListener);
190 buttonPanel.add(okButton);
191 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
192 cancelButton.addActionListener(new ActionListener() {
193 public void actionPerformed(ActionEvent e) {
197 buttonPanel.add(cancelButton);
198 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
199 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
204 * Set the suggestion for the name with which to export
206 private void setFileTitle()
208 // Get the most recent file info
209 SourceInfo currentSource = _app.getTrackInfo().getFileInfo().getLastFileInfo();
210 if (currentSource != _previousSourceInfo)
212 String lastTitle = currentSource.getFileTitle();
213 if (lastTitle != null && !lastTitle.equals(""))
215 // Take the title of the last file loaded
216 _nameField.setText(lastTitle);
219 if (_nameField.getText().equals(""))
221 // no name given in the field already, so try to overwrite it
222 String lastTitle = _app.getTrackInfo().getFileInfo().getLastFileTitle();
223 if (lastTitle != null && !lastTitle.equals(""))
225 _nameField.setText(lastTitle);
228 // Remember this source info so we don't use it again
229 _previousSourceInfo = currentSource;
233 * Start the export process based on the input parameters
235 private void startExport()
237 // OK pressed, so check selections
238 if (!_pointTypeSelector.getAnythingSelected())
240 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
241 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
244 // Choose output file
245 File saveFile = chooseGpxFile(_parentFrame);
246 if (saveFile != null)
248 // New file or overwrite confirmed, so initiate export in separate thread
249 _exportFile = saveFile;
250 new Thread(this).start();
256 * Select a GPX file to save to
257 * @param inParentFrame parent frame for file chooser dialog
258 * @return selected File, or null if selection cancelled
260 public static File chooseGpxFile(JFrame inParentFrame)
262 File saveFile = null;
263 JFileChooser fileChooser = new JFileChooser();
264 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
265 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
266 fileChooser.setAcceptAllFileFilterUsed(false);
267 // start from directory in config which should be set
268 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
269 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
271 // Allow choose again if an existing file is selected
272 boolean chooseAgain = false;
276 if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
278 // OK pressed and file chosen
279 File file = fileChooser.getSelectedFile();
280 // Check file extension
281 if (!file.getName().toLowerCase().endsWith(".gpx"))
283 file = new File(file.getAbsolutePath() + ".gpx");
285 // Check if file exists and if necessary prompt for overwrite
286 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
287 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
288 I18nManager.getText("dialog.save.overwrite.text"),
289 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
290 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
291 == JOptionPane.YES_OPTION)
293 // new file or overwrite confirmed
298 // file exists and overwrite cancelled - select again
302 } while (chooseAgain);
308 * Run method for controlling separate thread for exporting
312 // Instantiate source file cachers in case we want to copy output
313 GpxCacherList gpxCachers = null;
314 if (_copySourceCheckbox.isSelected()) {
315 gpxCachers = new GpxCacherList(_trackInfo.getFileInfo());
317 OutputStreamWriter writer = null;
320 // normal writing to file - firstly specify UTF8 encoding if requested
321 if (_forceUtf8Radio != null && _forceUtf8Radio.isSelected())
322 writer = new OutputStreamWriter(new FileOutputStream(_exportFile), "UTF-8");
324 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
325 // TODO: Move to new method
326 SettingsForExport settings = new SettingsForExport();
327 settings.setExportTrackPoints(_pointTypeSelector.getTrackpointsSelected());
328 settings.setExportWaypoints(_pointTypeSelector.getWaypointsSelected());
329 settings.setExportPhotoPoints(_pointTypeSelector.getPhotopointsSelected());
330 settings.setExportAudiopoints(_pointTypeSelector.getAudiopointsSelected());
331 settings.setExportJustSelection(_pointTypeSelector.getJustSelection());
332 settings.setExportTimestamps(_timestampsCheckbox.isSelected());
334 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
335 _descriptionField.getText(), settings, gpxCachers);
339 // Store directory in config for later
340 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
341 // Add to recent file list
342 Config.getRecentFileList().addFile(new RecentFile(_exportFile, true));
344 UpdateMessageBroker.informSubscribers();
345 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
346 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
347 + " " + _exportFile.getAbsolutePath());
348 // export successful so need to close dialog and return
352 catch (IOException ioe)
354 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
356 if (writer != null) writer.close();
358 catch (IOException ioe2) {}
359 JOptionPane.showMessageDialog(_parentFrame,
360 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
361 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
363 // if not returned already, export failed so need to recall the file selection
369 * Export the information to the given writer
370 * @param inWriter writer object
371 * @param inInfo track info object
372 * @param inName name of track (optional)
373 * @param inDesc description of track (optional)
374 * @param inExportSettings flags for what to export and how
375 * @param inGpxCachers list of Gpx cachers containing input data
376 * @return number of points written
377 * @throws IOException if io errors occur on write
379 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
380 String inDesc, SettingsForExport inSettings, GpxCacherList inGpxCachers) throws IOException
382 // Write or copy headers
383 inWriter.write(getXmlHeaderString(inWriter));
384 final String gpxHeader = getGpxHeaderString(inGpxCachers);
385 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
386 inWriter.write(gpxHeader);
387 // name and description
388 String trackName = (inName != null && !inName.equals("")) ? XmlUtils.fixCdata(inName) : "GpsPruneTrack";
389 String desc = (inDesc != null && !inDesc.equals("")) ? XmlUtils.fixCdata(inDesc) : "Export from GpsPrune";
390 writeNameAndDescription(inWriter, trackName, desc, isVersion1_1);
392 DataPoint point = null;
393 final boolean exportWaypoints = inSettings.getExportWaypoints();
394 final boolean exportSelection = inSettings.getExportJustSelection();
395 final boolean exportTimestamps = inSettings.getExportTimestamps();
397 int selStart = -1, selEnd = -1;
398 if (exportSelection) {
399 selStart = inInfo.getSelection().getStart();
400 selEnd = inInfo.getSelection().getEnd();
402 // Loop over waypoints
403 final int numPoints = inInfo.getTrack().getNumPoints();
405 for (int i=0; i<numPoints; i++)
407 point = inInfo.getTrack().getPoint(i);
408 if (!exportSelection || (i>=selStart && i<=selEnd))
410 // Make a wpt element for each waypoint
411 if (point.isWaypoint() && exportWaypoints)
413 String pointSource = (inGpxCachers == null? null : getPointSource(inGpxCachers, point));
414 if (pointSource != null)
416 // If timestamp checkbox is off, strip time
417 if (!exportTimestamps) {
418 pointSource = stripTime(pointSource);
420 inWriter.write('\t');
421 inWriter.write(pointSource);
422 inWriter.write('\n');
425 exportWaypoint(point, inWriter, inSettings);
431 // Export both route points and then track points
432 if (inSettings.getExportTrackPoints() || inSettings.getExportPhotoPoints() || inSettings.getExportAudioPoints())
434 // Output all route points (if any)
435 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
436 true, inGpxCachers, "<rtept", "\t<rte><number>1</number>\n",
438 // Output all track points, if any
439 String trackStart = "\t<trk>\n\t\t<name>" + trackName + "</name>\n\t\t<number>1</number>\n\t\t<trkseg>\n";
440 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
441 false, inGpxCachers, "<trkpt", trackStart,
442 "\t</trkseg>\n\t<trkseg>\n", "\t\t</trkseg>\n\t</trk>\n");
445 inWriter.write("</gpx>\n");
451 * Write the name and description according to the GPX version number
452 * @param inWriter writer object
453 * @param inName name, or null if none supplied
454 * @param inDesc description, or null if none supplied
455 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
457 private static void writeNameAndDescription(OutputStreamWriter inWriter,
458 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
460 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
463 // GPX 1.1 has the name and description inside a metadata tag
464 inWriter.write("\t<metadata>\n");
466 if (inName != null && !inName.equals(""))
468 if (inIsVersion1_1) {inWriter.write('\t');}
469 inWriter.write("\t<name>");
470 inWriter.write(inName);
471 inWriter.write("</name>\n");
473 if (inIsVersion1_1) {inWriter.write('\t');}
474 inWriter.write("\t<desc>");
475 inWriter.write(inDesc);
476 inWriter.write("</desc>\n");
479 inWriter.write("\t</metadata>\n");
484 * Loop through the track outputting the relevant track points
485 * @param inWriter writer object for output
486 * @param inInfo track info object containing track
487 * @param inSettings export settings defining what should be exported
488 * @param inOnlyCopies true to only export if source can be copied
489 * @param inCachers list of GpxCachers
490 * @param inPointTag tag to match for each point
491 * @param inStartTag start tag to output
492 * @param inSegmentTag tag to output between segments (or null)
493 * @param inEndTag end tag to output
495 private static int writeTrackPoints(OutputStreamWriter inWriter,
496 TrackInfo inInfo, SettingsForExport inSettings,
497 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
498 String inStartTag, String inSegmentTag, String inEndTag)
501 // Note: Too many input parameters to this method but avoids duplication
502 // of output functionality for writing track points and route points
503 int numPoints = inInfo.getTrack().getNumPoints();
504 int selStart = inInfo.getSelection().getStart();
505 int selEnd = inInfo.getSelection().getEnd();
507 final boolean exportSelection = inSettings.getExportJustSelection();
508 final boolean exportTrackPoints = inSettings.getExportTrackPoints();
509 final boolean exportPhotos = inSettings.getExportPhotoPoints();
510 final boolean exportAudios = inSettings.getExportAudioPoints();
511 final boolean exportTimestamps = inSettings.getExportTimestamps();
512 // Loop over track points
513 for (int i=0; i<numPoints; i++)
515 DataPoint point = inInfo.getTrack().getPoint(i);
516 if ((!exportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
518 if ((point.getPhoto()==null && exportTrackPoints) || (point.getPhoto()!=null && exportPhotos)
519 || (point.getAudio()!=null && exportAudios))
521 // get the source from the point (if any)
522 String pointSource = getPointSource(inCachers, point);
523 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
524 if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
527 if (pointSource != null || !inOnlyCopies)
529 // restart track segment if necessary
530 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
531 inWriter.write(inSegmentTag);
533 if (numSaved == 0) {inWriter.write(inStartTag);}
534 if (pointSource != null)
536 // If timestamps checkbox is off, strip the time
537 if (!exportTimestamps) {
538 pointSource = stripTime(pointSource);
540 inWriter.write(pointSource);
541 inWriter.write('\n');
546 exportTrackpoint(point, inWriter, inSettings);
555 inWriter.write(inEndTag);
562 * Get the point source for the specified point
563 * @param inCachers list of GPX cachers to ask for source
564 * @param inPoint point object
565 * @return xml source if available, or null otherwise
567 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
569 if (inCachers == null || inPoint == null) {return null;}
570 String source = inCachers.getSourceString(inPoint);
571 if (source == null || !inPoint.isModified()) {return source;}
572 // Point has been modified - maybe it's possible to modify the source
573 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
574 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
575 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
576 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
577 if (inPoint.isWaypoint())
579 source = replaceGpxTags(source, "<name>", "</name>", XmlUtils.fixCdata(inPoint.getWaypointName()));
582 source = source.replaceAll("<description>", "<desc>").replaceAll("</description>", "</desc>");
584 source = replaceGpxTags(source, "<desc>", "</desc>",
585 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
586 source = replaceGpxTags(source, "<cmt>", "</cmt>", inPoint.getFieldValue(Field.COMMENT));
588 // photo / audio links
589 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
590 source = replaceMediaLinks(source, makeMediaLink(inPoint));
596 * Replace the given value into the given XML string
597 * @param inSource source XML for point
598 * @param inStartTag start tag for field
599 * @param inEndTag end tag for field
600 * @param inValue value to replace between start tag and end tag
601 * @return modified String, or null if not possible
603 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
605 if (inSource == null) {return null;}
606 // Look for start and end tags within source
607 final int startPos = inSource.indexOf(inStartTag);
608 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
609 if (startPos > 0 && endPos > 0)
611 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
612 if (inValue != null && origValue.equals(inValue)) {
616 else if (inValue == null || inValue.equals("")) {
617 // Need to delete value
618 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
621 // Need to replace value
622 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
625 // Value not found for this field in original source
626 if (inValue == null || inValue.equals("")) {return inSource;}
632 * Replace the media tags in the given XML string
633 * @param inSource source XML for point
634 * @param inValue value for the current point
635 * @return modified String, or null if not possible
637 private static String replaceMediaLinks(String inSource, String inValue)
639 if (inSource == null) {return null;}
640 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
641 // and the tags must have attributes. So either one heavily parameterized method or two.
642 // Look for start and end tags within source
643 final String STARTTEXT = "<link";
644 final String ENDTEXT = "</link>";
645 final int startPos = inSource.indexOf(STARTTEXT);
646 final int endPos = inSource.lastIndexOf(ENDTEXT);
647 if (startPos > 0 && endPos > 0)
649 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
650 if (inValue != null && origValue.equals(inValue)) {
654 else if (inValue == null || inValue.equals("")) {
655 // Need to delete value
656 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
659 // Need to replace value
660 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
663 // Value not found for this field in original source
664 if (inValue == null || inValue.equals("")) {return inSource;}
670 * Get the header string for the xml document including encoding
671 * @param inWriter writer object
672 * @return header string defining encoding
674 private static String getXmlHeaderString(OutputStreamWriter inWriter)
676 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
681 * Get the header string for the gpx tag
682 * @param inCachers cacher list to ask for headers, if available
683 * @return header string from cachers or as default
685 private static String getGpxHeaderString(GpxCacherList inCachers)
687 String gpxHeader = null;
688 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
689 if (gpxHeader == null || gpxHeader.length() < 5)
691 // TODO: Consider changing this to default to GPX 1.1
692 // Create default (1.0) header
693 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
694 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
695 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
696 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
698 return gpxHeader + "\n";
703 * Export the specified waypoint into the file
704 * @param inPoint waypoint to export
705 * @param inWriter writer object
706 * @param inSettings export settings
707 * @throws IOException on write failure
709 private static void exportWaypoint(DataPoint inPoint, Writer inWriter,
710 SettingsForExport inSettings)
713 inWriter.write("\t<wpt lat=\"");
714 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
715 inWriter.write("\" lon=\"");
716 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
717 inWriter.write("\">\n");
718 // altitude if available
719 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
721 inWriter.write("\t\t<ele>");
722 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
723 inWriter.write("</ele>\n");
725 // timestamp if available (some waypoints have timestamps, some not)
726 if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
728 inWriter.write("\t\t<time>");
729 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
730 inWriter.write("</time>\n");
732 // write waypoint name after elevation and time
733 inWriter.write("\t\t<name>");
734 inWriter.write(XmlUtils.fixCdata(inPoint.getWaypointName().trim()));
735 inWriter.write("</name>\n");
736 // description, if any
737 final String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
738 if (desc != null && !desc.equals(""))
740 inWriter.write("\t\t<desc>");
741 inWriter.write(desc);
742 inWriter.write("</desc>\n");
745 final String comment = XmlUtils.fixCdata(inPoint.getFieldValue(Field.COMMENT));
746 if (comment != null && !comment.equals(""))
748 inWriter.write("\t\t<cmt>");
749 inWriter.write(comment);
750 inWriter.write("</cmt>\n");
752 // Media links, if any
753 if (inSettings.getExportPhotoPoints() && inPoint.getPhoto() != null)
755 inWriter.write("\t\t");
756 inWriter.write(makeMediaLink(inPoint.getPhoto()));
757 inWriter.write('\n');
759 if (inSettings.getExportAudioPoints() && inPoint.getAudio() != null)
761 inWriter.write("\t\t");
762 inWriter.write(makeMediaLink(inPoint.getAudio()));
763 inWriter.write('\n');
765 // write waypoint type if any
766 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
770 if (!type.equals(""))
772 inWriter.write("\t\t<type>");
773 inWriter.write(type);
774 inWriter.write("</type>\n");
777 inWriter.write("\t</wpt>\n");
782 * Export the specified trackpoint into the file
783 * @param inPoint trackpoint to export
784 * @param inWriter writer object
785 * @param inSettings export settings
787 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, SettingsForExport inSettings)
790 inWriter.write("\t\t\t<trkpt lat=\"");
791 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
792 inWriter.write("\" lon=\"");
793 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
794 inWriter.write("\">\n");
796 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
798 inWriter.write("\t\t\t\t<ele>");
799 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
800 inWriter.write("</ele>\n");
802 // Maybe take timestamp from photo if the point hasn't got one
803 Timestamp pointTimestamp = getPointTimestamp(inPoint, inSettings);
804 // timestamp if available (and selected)
805 if (pointTimestamp != null && inSettings.getExportTimestamps())
807 inWriter.write("\t\t\t\t<time>");
808 inWriter.write(pointTimestamp.getText(Timestamp.Format.ISO8601, null));
809 inWriter.write("</time>\n");
812 if (inPoint.getPhoto() != null && inSettings.getExportPhotoPoints())
814 inWriter.write("\t\t\t\t");
815 inWriter.write(makeMediaLink(inPoint.getPhoto()));
816 inWriter.write("\n");
818 if (inPoint.getAudio() != null && inSettings.getExportAudioPoints()) {
819 inWriter.write(makeMediaLink(inPoint.getAudio()));
821 inWriter.write("\t\t\t</trkpt>\n");
826 * Make the xml for the media link(s)
827 * @param inPoint point to generate text for
828 * @return link tags, or null if no links
830 private static String makeMediaLink(DataPoint inPoint)
832 Photo photo = inPoint.getPhoto();
833 AudioClip audio = inPoint.getAudio();
834 if (photo == null && audio == null) {
837 String linkText = "";
839 linkText = makeMediaLink(photo);
842 linkText += makeMediaLink(audio);
848 * Make the media link for a single media item
849 * @param inMedia media item, either photo or audio
850 * @return link for this media
852 private static String makeMediaLink(MediaObject inMedia)
854 if (inMedia.getFile() != null)
856 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
857 if (inMedia.getUrl() != null)
859 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
860 // No link available, must have been loaded from zip file - no link possible
866 * Strip the time from a GPX point source string
867 * @param inPointSource point source to copy
868 * @return point source with timestamp removed
870 private static String stripTime(String inPointSource)
872 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");
876 * Get the timestamp from the point or its media
877 * @param inPoint point object
878 * @param inSettings export settings
879 * @return Timestamp object if available, or null
881 private static Timestamp getPointTimestamp(DataPoint inPoint, SettingsForExport inSettings)
883 if (inPoint.hasTimestamp())
885 return inPoint.getTimestamp();
887 if (inPoint.getPhoto() != null && inSettings.getExportPhotoPoints())
889 if (inPoint.getPhoto().hasTimestamp())
891 return inPoint.getPhoto().getTimestamp();
894 if (inPoint.getAudio() != null && inSettings.getExportAudioPoints())
896 if (inPoint.getAudio().hasTimestamp())
898 return inPoint.getAudio().getTimestamp();