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
350 _app.informDataSaved();
353 catch (IOException ioe)
355 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
357 if (writer != null) writer.close();
359 catch (IOException ioe2) {}
360 JOptionPane.showMessageDialog(_parentFrame,
361 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
362 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
364 // if not returned already, export failed so need to recall the file selection
370 * Export the information to the given writer
371 * @param inWriter writer object
372 * @param inInfo track info object
373 * @param inName name of track (optional)
374 * @param inDesc description of track (optional)
375 * @param inExportSettings flags for what to export and how
376 * @param inGpxCachers list of Gpx cachers containing input data
377 * @return number of points written
378 * @throws IOException if io errors occur on write
380 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
381 String inDesc, SettingsForExport inSettings, GpxCacherList inGpxCachers) throws IOException
383 // Write or copy headers
384 inWriter.write(getXmlHeaderString(inWriter));
385 final String gpxHeader = getGpxHeaderString(inGpxCachers);
386 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
387 inWriter.write(gpxHeader);
388 // name and description
389 String trackName = (inName != null && !inName.equals("")) ? XmlUtils.fixCdata(inName) : "GpsPruneTrack";
390 String desc = (inDesc != null && !inDesc.equals("")) ? XmlUtils.fixCdata(inDesc) : "Export from GpsPrune";
391 writeNameAndDescription(inWriter, trackName, desc, isVersion1_1);
393 DataPoint point = null;
394 final boolean exportWaypoints = inSettings.getExportWaypoints();
395 final boolean exportSelection = inSettings.getExportJustSelection();
396 final boolean exportTimestamps = inSettings.getExportTimestamps();
398 int selStart = -1, selEnd = -1;
399 if (exportSelection) {
400 selStart = inInfo.getSelection().getStart();
401 selEnd = inInfo.getSelection().getEnd();
403 // Loop over waypoints
404 final int numPoints = inInfo.getTrack().getNumPoints();
406 for (int i=0; i<numPoints; i++)
408 point = inInfo.getTrack().getPoint(i);
409 if (!exportSelection || (i>=selStart && i<=selEnd))
411 // Make a wpt element for each waypoint
412 if (point.isWaypoint() && exportWaypoints)
414 String pointSource = (inGpxCachers == null? null : getPointSource(inGpxCachers, point));
415 if (pointSource != null)
417 // If timestamp checkbox is off, strip time
418 if (!exportTimestamps) {
419 pointSource = stripTime(pointSource);
421 inWriter.write('\t');
422 inWriter.write(pointSource);
423 inWriter.write('\n');
426 exportWaypoint(point, inWriter, inSettings);
432 // Export both route points and then track points
433 if (inSettings.getExportTrackPoints() || inSettings.getExportPhotoPoints() || inSettings.getExportAudioPoints())
435 // Output all route points (if any)
436 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
437 true, inGpxCachers, "<rtept", "\t<rte><number>1</number>\n",
439 // Output all track points, if any
440 String trackStart = "\t<trk>\n\t\t<name>" + trackName + "</name>\n\t\t<number>1</number>\n\t\t<trkseg>\n";
441 numSaved += writeTrackPoints(inWriter, inInfo, inSettings,
442 false, inGpxCachers, "<trkpt", trackStart,
443 "\t</trkseg>\n\t<trkseg>\n", "\t\t</trkseg>\n\t</trk>\n");
446 inWriter.write("</gpx>\n");
452 * Write the name and description according to the GPX version number
453 * @param inWriter writer object
454 * @param inName name, or null if none supplied
455 * @param inDesc description, or null if none supplied
456 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
458 private static void writeNameAndDescription(OutputStreamWriter inWriter,
459 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
461 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
464 // GPX 1.1 has the name and description inside a metadata tag
465 inWriter.write("\t<metadata>\n");
467 if (inName != null && !inName.equals(""))
469 if (inIsVersion1_1) {inWriter.write('\t');}
470 inWriter.write("\t<name>");
471 inWriter.write(inName);
472 inWriter.write("</name>\n");
474 if (inIsVersion1_1) {inWriter.write('\t');}
475 inWriter.write("\t<desc>");
476 inWriter.write(inDesc);
477 inWriter.write("</desc>\n");
480 inWriter.write("\t</metadata>\n");
485 * Loop through the track outputting the relevant track points
486 * @param inWriter writer object for output
487 * @param inInfo track info object containing track
488 * @param inSettings export settings defining what should be exported
489 * @param inOnlyCopies true to only export if source can be copied
490 * @param inCachers list of GpxCachers
491 * @param inPointTag tag to match for each point
492 * @param inStartTag start tag to output
493 * @param inSegmentTag tag to output between segments (or null)
494 * @param inEndTag end tag to output
496 private static int writeTrackPoints(OutputStreamWriter inWriter,
497 TrackInfo inInfo, SettingsForExport inSettings,
498 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
499 String inStartTag, String inSegmentTag, String inEndTag)
502 // Note: Too many input parameters to this method but avoids duplication
503 // of output functionality for writing track points and route points
504 int numPoints = inInfo.getTrack().getNumPoints();
505 int selStart = inInfo.getSelection().getStart();
506 int selEnd = inInfo.getSelection().getEnd();
508 final boolean exportSelection = inSettings.getExportJustSelection();
509 final boolean exportTrackPoints = inSettings.getExportTrackPoints();
510 final boolean exportPhotos = inSettings.getExportPhotoPoints();
511 final boolean exportAudios = inSettings.getExportAudioPoints();
512 final boolean exportTimestamps = inSettings.getExportTimestamps();
513 // Loop over track points
514 for (int i=0; i<numPoints; i++)
516 DataPoint point = inInfo.getTrack().getPoint(i);
517 if ((!exportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
519 if ((point.getPhoto()==null && exportTrackPoints) || (point.getPhoto()!=null && exportPhotos)
520 || (point.getAudio()!=null && exportAudios))
522 // get the source from the point (if any)
523 String pointSource = getPointSource(inCachers, point);
524 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
525 if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
528 if (pointSource != null || !inOnlyCopies)
530 // restart track segment if necessary
531 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
532 inWriter.write(inSegmentTag);
534 if (numSaved == 0) {inWriter.write(inStartTag);}
535 if (pointSource != null)
537 // If timestamps checkbox is off, strip the time
538 if (!exportTimestamps) {
539 pointSource = stripTime(pointSource);
541 inWriter.write(pointSource);
542 inWriter.write('\n');
547 exportTrackpoint(point, inWriter, inSettings);
556 inWriter.write(inEndTag);
563 * Get the point source for the specified point
564 * @param inCachers list of GPX cachers to ask for source
565 * @param inPoint point object
566 * @return xml source if available, or null otherwise
568 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
570 if (inCachers == null || inPoint == null) {return null;}
571 String source = inCachers.getSourceString(inPoint);
572 if (source == null || !inPoint.isModified()) {return source;}
573 // Point has been modified - maybe it's possible to modify the source
574 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
575 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
576 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
577 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
578 if (inPoint.isWaypoint())
580 source = replaceGpxTags(source, "<name>", "</name>", XmlUtils.fixCdata(inPoint.getWaypointName()));
583 source = source.replaceAll("<description>", "<desc>").replaceAll("</description>", "</desc>");
585 source = replaceGpxTags(source, "<desc>", "</desc>",
586 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
587 source = replaceGpxTags(source, "<cmt>", "</cmt>", inPoint.getFieldValue(Field.COMMENT));
589 // photo / audio links
590 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
591 source = replaceMediaLinks(source, makeMediaLink(inPoint));
597 * Replace the given value into the given XML string
598 * @param inSource source XML for point
599 * @param inStartTag start tag for field
600 * @param inEndTag end tag for field
601 * @param inValue value to replace between start tag and end tag
602 * @return modified String, or null if not possible
604 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
606 if (inSource == null) {return null;}
607 // Look for start and end tags within source
608 final int startPos = inSource.indexOf(inStartTag);
609 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
610 if (startPos > 0 && endPos > 0)
612 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
613 if (inValue != null && origValue.equals(inValue)) {
617 else if (inValue == null || inValue.equals("")) {
618 // Need to delete value
619 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
622 // Need to replace value
623 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
626 // Value not found for this field in original source
627 if (inValue == null || inValue.equals("")) {return inSource;}
633 * Replace the media tags in the given XML string
634 * @param inSource source XML for point
635 * @param inValue value for the current point
636 * @return modified String, or null if not possible
638 private static String replaceMediaLinks(String inSource, String inValue)
640 if (inSource == null) {return null;}
641 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
642 // and the tags must have attributes. So either one heavily parameterized method or two.
643 // Look for start and end tags within source
644 final String STARTTEXT = "<link";
645 final String ENDTEXT = "</link>";
646 final int startPos = inSource.indexOf(STARTTEXT);
647 final int endPos = inSource.lastIndexOf(ENDTEXT);
648 if (startPos > 0 && endPos > 0)
650 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
651 if (inValue != null && origValue.equals(inValue)) {
655 else if (inValue == null || inValue.equals("")) {
656 // Need to delete value
657 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
660 // Need to replace value
661 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
664 // Value not found for this field in original source
665 if (inValue == null || inValue.equals("")) {return inSource;}
671 * Get the header string for the xml document including encoding
672 * @param inWriter writer object
673 * @return header string defining encoding
675 private static String getXmlHeaderString(OutputStreamWriter inWriter)
677 return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
682 * Get the header string for the gpx tag
683 * @param inCachers cacher list to ask for headers, if available
684 * @return header string from cachers or as default
686 private static String getGpxHeaderString(GpxCacherList inCachers)
688 String gpxHeader = null;
689 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
690 if (gpxHeader == null || gpxHeader.length() < 5)
692 // TODO: Consider changing this to default to GPX 1.1
693 // Create default (1.0) header
694 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
695 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
696 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
697 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
699 return gpxHeader + "\n";
704 * Export the specified waypoint into the file
705 * @param inPoint waypoint to export
706 * @param inWriter writer object
707 * @param inSettings export settings
708 * @throws IOException on write failure
710 private static void exportWaypoint(DataPoint inPoint, Writer inWriter,
711 SettingsForExport inSettings)
714 inWriter.write("\t<wpt lat=\"");
715 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
716 inWriter.write("\" lon=\"");
717 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
718 inWriter.write("\">\n");
719 // altitude if available
720 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
722 inWriter.write("\t\t<ele>");
723 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
724 inWriter.write("</ele>\n");
726 // timestamp if available (some waypoints have timestamps, some not)
727 if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
729 inWriter.write("\t\t<time>");
730 inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
731 inWriter.write("</time>\n");
733 // write waypoint name after elevation and time
734 inWriter.write("\t\t<name>");
735 inWriter.write(XmlUtils.fixCdata(inPoint.getWaypointName().trim()));
736 inWriter.write("</name>\n");
737 // description, if any
738 final String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
739 if (desc != null && !desc.equals(""))
741 inWriter.write("\t\t<desc>");
742 inWriter.write(desc);
743 inWriter.write("</desc>\n");
746 final String comment = XmlUtils.fixCdata(inPoint.getFieldValue(Field.COMMENT));
747 if (comment != null && !comment.equals(""))
749 inWriter.write("\t\t<cmt>");
750 inWriter.write(comment);
751 inWriter.write("</cmt>\n");
753 // Media links, if any
754 if (inSettings.getExportPhotoPoints() && inPoint.getPhoto() != null)
756 inWriter.write("\t\t");
757 inWriter.write(makeMediaLink(inPoint.getPhoto()));
758 inWriter.write('\n');
760 if (inSettings.getExportAudioPoints() && inPoint.getAudio() != null)
762 inWriter.write("\t\t");
763 inWriter.write(makeMediaLink(inPoint.getAudio()));
764 inWriter.write('\n');
766 // write waypoint type if any
767 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
771 if (!type.equals(""))
773 inWriter.write("\t\t<type>");
774 inWriter.write(type);
775 inWriter.write("</type>\n");
778 inWriter.write("\t</wpt>\n");
783 * Export the specified trackpoint into the file
784 * @param inPoint trackpoint to export
785 * @param inWriter writer object
786 * @param inSettings export settings
788 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, SettingsForExport inSettings)
791 inWriter.write("\t\t\t<trkpt lat=\"");
792 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
793 inWriter.write("\" lon=\"");
794 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
795 inWriter.write("\">\n");
797 if (inPoint.hasAltitude() || inSettings.getExportMissingAltitudesAsZero())
799 inWriter.write("\t\t\t\t<ele>");
800 inWriter.write(inPoint.hasAltitude() ? inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES) : "0");
801 inWriter.write("</ele>\n");
803 // Maybe take timestamp from photo if the point hasn't got one
804 Timestamp pointTimestamp = getPointTimestamp(inPoint, inSettings);
805 // timestamp if available (and selected)
806 if (pointTimestamp != null && inSettings.getExportTimestamps())
808 inWriter.write("\t\t\t\t<time>");
809 inWriter.write(pointTimestamp.getText(Timestamp.Format.ISO8601, null));
810 inWriter.write("</time>\n");
813 if (inPoint.getPhoto() != null && inSettings.getExportPhotoPoints())
815 inWriter.write("\t\t\t\t");
816 inWriter.write(makeMediaLink(inPoint.getPhoto()));
817 inWriter.write("\n");
819 if (inPoint.getAudio() != null && inSettings.getExportAudioPoints()) {
820 inWriter.write(makeMediaLink(inPoint.getAudio()));
822 inWriter.write("\t\t\t</trkpt>\n");
827 * Make the xml for the media link(s)
828 * @param inPoint point to generate text for
829 * @return link tags, or null if no links
831 private static String makeMediaLink(DataPoint inPoint)
833 Photo photo = inPoint.getPhoto();
834 AudioClip audio = inPoint.getAudio();
835 if (photo == null && audio == null) {
838 String linkText = "";
840 linkText = makeMediaLink(photo);
843 linkText += makeMediaLink(audio);
849 * Make the media link for a single media item
850 * @param inMedia media item, either photo or audio
851 * @return link for this media
853 private static String makeMediaLink(MediaObject inMedia)
855 if (inMedia.getFile() != null)
857 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
858 if (inMedia.getUrl() != null)
860 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
861 // No link available, must have been loaded from zip file - no link possible
867 * Strip the time from a GPX point source string
868 * @param inPointSource point source to copy
869 * @return point source with timestamp removed
871 private static String stripTime(String inPointSource)
873 return inPointSource.replaceAll("[ \t]*<time>.*?</time>", "");
877 * Get the timestamp from the point or its media
878 * @param inPoint point object
879 * @param inSettings export settings
880 * @return Timestamp object if available, or null
882 private static Timestamp getPointTimestamp(DataPoint inPoint, SettingsForExport inSettings)
884 if (inPoint.hasTimestamp())
886 return inPoint.getTimestamp();
888 if (inPoint.getPhoto() != null && inSettings.getExportPhotoPoints())
890 if (inPoint.getPhoto().hasTimestamp())
892 return inPoint.getPhoto().getTimestamp();
895 if (inPoint.getAudio() != null && inSettings.getExportAudioPoints())
897 if (inPoint.getAudio().hasTimestamp())
899 return inPoint.getAudio().getTimestamp();