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;
14 import java.nio.charset.Charset;
16 import javax.swing.BorderFactory;
17 import javax.swing.Box;
18 import javax.swing.BoxLayout;
19 import javax.swing.ButtonGroup;
20 import javax.swing.JButton;
21 import javax.swing.JCheckBox;
22 import javax.swing.JDialog;
23 import javax.swing.JFileChooser;
24 import javax.swing.JFrame;
25 import javax.swing.JLabel;
26 import javax.swing.JOptionPane;
27 import javax.swing.JPanel;
28 import javax.swing.JRadioButton;
29 import javax.swing.JTextField;
30 import javax.swing.border.EtchedBorder;
33 import tim.prune.GenericFunction;
34 import tim.prune.GpsPrune;
35 import tim.prune.I18nManager;
36 import tim.prune.UpdateMessageBroker;
37 import tim.prune.config.Config;
38 import tim.prune.data.Altitude;
39 import tim.prune.data.AudioClip;
40 import tim.prune.data.Coordinate;
41 import tim.prune.data.DataPoint;
42 import tim.prune.data.Field;
43 import tim.prune.data.MediaObject;
44 import tim.prune.data.Photo;
45 import tim.prune.data.RecentFile;
46 import tim.prune.data.Timestamp;
47 import tim.prune.data.TrackInfo;
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 private static String _systemEncoding = null;
72 /** this program name */
73 private static final String GPX_CREATOR = "GpsPrune v" + GpsPrune.VERSION_NUMBER + " activityworkshop.net";
78 * @param inApp app object
80 public GpxExporter(App inApp)
83 _trackInfo = inApp.getTrackInfo();
87 public String getNameKey() {
88 return "function.exportgpx";
92 * Show the dialog to select options and export file
99 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
100 _dialog.setLocationRelativeTo(_parentFrame);
101 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
102 _systemEncoding = getSystemEncoding();
103 _dialog.getContentPane().add(makeDialogComponents());
106 _pointTypeSelector.init(_app.getTrackInfo());
107 _encodingsPanel.setVisible(!isSystemUtf8());
108 if (!isSystemUtf8()) {
109 _useSystemRadio.setText(I18nManager.getText("dialog.exportgpx.encoding.system")
110 + " (" + (_systemEncoding == null ? "unknown" : _systemEncoding) + ")");
112 _dialog.setVisible(true);
117 * Create dialog components
118 * @return Panel containing all gui elements in dialog
120 private Component makeDialogComponents()
122 JPanel dialogPanel = new JPanel();
123 dialogPanel.setLayout(new BorderLayout());
124 JPanel mainPanel = new JPanel();
125 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
126 // Make a panel for the name/desc text boxes
127 JPanel descPanel = new JPanel();
128 descPanel.setLayout(new GridLayout(2, 2));
129 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
130 _nameField = new JTextField(10);
131 descPanel.add(_nameField);
132 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
133 _descriptionField = new JTextField(10);
134 descPanel.add(_descriptionField);
135 mainPanel.add(descPanel);
136 mainPanel.add(Box.createVerticalStrut(5));
137 // point type selection (track points, waypoints, photo points)
138 _pointTypeSelector = new PointTypeSelector();
139 mainPanel.add(_pointTypeSelector);
140 // checkboxes for timestamps and copying
141 JPanel checkPanel = new JPanel();
142 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
143 _timestampsCheckbox.setSelected(true);
144 checkPanel.add(_timestampsCheckbox);
145 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
146 _copySourceCheckbox.setSelected(true);
147 checkPanel.add(_copySourceCheckbox);
148 mainPanel.add(checkPanel);
149 // panel for selecting character encoding
150 _encodingsPanel = new JPanel();
153 // only add this panel if system isn't utf8 (or can't be identified yet)
154 _encodingsPanel.setBorder(BorderFactory.createCompoundBorder(
155 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4)));
156 _encodingsPanel.setLayout(new BorderLayout());
157 _encodingsPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.encoding")), BorderLayout.NORTH);
158 JPanel radioPanel = new JPanel();
159 radioPanel.setLayout(new FlowLayout());
160 ButtonGroup radioGroup = new ButtonGroup();
161 _useSystemRadio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.system"));
162 _forceUtf8Radio = new JRadioButton(I18nManager.getText("dialog.exportgpx.encoding.utf8"));
163 radioGroup.add(_useSystemRadio);
164 radioGroup.add(_forceUtf8Radio);
165 radioPanel.add(_useSystemRadio);
166 radioPanel.add(_forceUtf8Radio);
167 _useSystemRadio.setSelected(true);
168 _encodingsPanel.add(radioPanel, BorderLayout.CENTER);
169 mainPanel.add(_encodingsPanel);
171 dialogPanel.add(mainPanel, BorderLayout.CENTER);
173 // close dialog if escape pressed
174 _nameField.addKeyListener(new DialogCloser(_dialog));
175 // button panel at bottom
176 JPanel buttonPanel = new JPanel();
177 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
178 JButton okButton = new JButton(I18nManager.getText("button.ok"));
179 ActionListener okListener = new ActionListener() {
180 public void actionPerformed(ActionEvent e)
185 okButton.addActionListener(okListener);
186 _descriptionField.addActionListener(okListener);
187 buttonPanel.add(okButton);
188 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
189 cancelButton.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
194 buttonPanel.add(cancelButton);
195 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
196 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
202 * Start the export process based on the input parameters
204 private void startExport()
206 // OK pressed, so check selections
207 if (!_pointTypeSelector.getAnythingSelected())
209 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
210 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
213 // Choose output file
214 File saveFile = chooseGpxFile(_parentFrame);
215 if (saveFile != null)
217 // New file or overwrite confirmed, so initiate export in separate thread
218 _exportFile = saveFile;
219 new Thread(this).start();
225 * Select a GPX file to save to
226 * @param inParentFrame parent frame for file chooser dialog
227 * @return selected File, or null if selection cancelled
229 public static File chooseGpxFile(JFrame inParentFrame)
231 File saveFile = null;
232 JFileChooser fileChooser = new JFileChooser();
233 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
234 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
235 fileChooser.setAcceptAllFileFilterUsed(false);
236 // start from directory in config which should be set
237 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
238 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
240 // Allow choose again if an existing file is selected
241 boolean chooseAgain = false;
245 if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
247 // OK pressed and file chosen
248 File file = fileChooser.getSelectedFile();
249 // Check file extension
250 if (!file.getName().toLowerCase().endsWith(".gpx"))
252 file = new File(file.getAbsolutePath() + ".gpx");
254 // Check if file exists and if necessary prompt for overwrite
255 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
256 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
257 I18nManager.getText("dialog.save.overwrite.text"),
258 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
259 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
260 == JOptionPane.YES_OPTION)
262 // new file or overwrite confirmed
267 // file exists and overwrite cancelled - select again
271 } while (chooseAgain);
277 * Run method for controlling separate thread for exporting
281 OutputStreamWriter writer = null;
284 // normal writing to file - firstly specify UTF8 encoding if requested
285 if (_forceUtf8Radio != null && _forceUtf8Radio.isSelected())
286 writer = new OutputStreamWriter(new FileOutputStream(_exportFile), "UTF-8");
288 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
289 final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
290 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
291 _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
293 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
294 _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
298 // Store directory in config for later
299 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
300 // Add to recent file list
301 Config.getRecentFileList().addFile(new RecentFile(_exportFile, true));
303 UpdateMessageBroker.informSubscribers();
304 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
305 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
306 + " " + _exportFile.getAbsolutePath());
307 // export successful so need to close dialog and return
311 catch (IOException ioe)
313 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
315 if (writer != null) writer.close();
317 catch (IOException ioe2) {}
318 JOptionPane.showMessageDialog(_parentFrame,
319 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
320 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
322 // if not returned already, export failed so need to recall the file selection
328 * Export the information to the given writer
329 * @param inWriter writer object
330 * @param inInfo track info object
331 * @param inName name of track (optional)
332 * @param inDesc description of track (optional)
333 * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
334 * @param inUseCopy true to copy source if available
335 * @return number of points written
336 * @throws IOException if io errors occur on write
338 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
339 String inDesc, boolean[] inSaveFlags, boolean inUseCopy) throws IOException
341 // Instantiate source file cachers in case we want to copy output
342 GpxCacherList gpxCachers = null;
343 if (inUseCopy) gpxCachers = new GpxCacherList(inInfo.getFileInfo());
344 // Write or copy headers
345 inWriter.write(getXmlHeaderString(inWriter));
346 inWriter.write(getGpxHeaderString(gpxCachers));
348 String trackName = "GpsPruneTrack";
349 if (inName != null && !inName.equals(""))
352 inWriter.write("\t<name>");
353 inWriter.write(trackName);
354 inWriter.write("</name>\n");
357 inWriter.write("\t<desc>");
358 inWriter.write((inDesc != null && !inDesc.equals(""))?inDesc:"Export from GpsPrune");
359 inWriter.write("</desc>\n");
362 DataPoint point = null;
363 final boolean exportTrackpoints = inSaveFlags[0];
364 final boolean exportWaypoints = inSaveFlags[1];
365 final boolean exportPhotos = inSaveFlags[2];
366 final boolean exportAudios = inSaveFlags[3];
367 final boolean exportSelection = inSaveFlags[4];
368 final boolean exportTimestamps = inSaveFlags[5];
370 int selStart = -1, selEnd = -1;
371 if (exportSelection) {
372 selStart = inInfo.getSelection().getStart();
373 selEnd = inInfo.getSelection().getEnd();
375 // Loop over waypoints
376 final int numPoints = inInfo.getTrack().getNumPoints();
378 for (i=0; i<numPoints; i++)
380 point = inInfo.getTrack().getPoint(i);
381 if (!exportSelection || (i>=selStart && i<=selEnd))
383 // Make a wpt element for each waypoint
384 if (point.isWaypoint() && exportWaypoints)
386 String pointSource = (inUseCopy?getPointSource(gpxCachers, point):null);
387 if (pointSource != null) {
388 inWriter.write(pointSource);
389 inWriter.write('\n');
392 exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
398 // Export both route points and then track points
399 if (exportTrackpoints || exportPhotos || exportAudios)
401 // Output all route points (if any)
402 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
403 exportAudios, exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n",
405 // Output all track points, if any
406 String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
407 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
408 exportAudios, exportTimestamps, false, gpxCachers, "<trkpt", trackStart,
409 "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
412 inWriter.write("</gpx>\n");
418 * Loop through the track outputting the relevant track points
419 * @param inWriter writer object for output
420 * @param inInfo track info object containing track
421 * @param inExportSelection true to just output current selection
422 * @param inExportTrackpoints true to output track points
423 * @param inExportPhotos true to output photo points
424 * @param inExportAudios true to output audio points
425 * @param inExportTimestamps true to include timestamps in export
426 * @param inOnlyCopies true to only export if source can be copied
427 * @param inCachers list of GpxCachers
428 * @param inPointTag tag to match for each point
429 * @param inStartTag start tag to output
430 * @param inSegmentTag tag to output between segments (or null)
431 * @param inEndTag end tag to output
433 private static int writeTrackPoints(OutputStreamWriter inWriter,
434 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
435 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
436 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
437 String inStartTag, String inSegmentTag, String inEndTag)
440 // Note: far too many input parameters to this method but avoids duplication
441 // of output functionality for writing track points and route points
442 int numPoints = inInfo.getTrack().getNumPoints();
443 int selStart = inInfo.getSelection().getStart();
444 int selEnd = inInfo.getSelection().getEnd();
446 // Loop over track points
447 for (int i=0; i<numPoints; i++)
449 DataPoint point = inInfo.getTrack().getPoint(i);
450 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
452 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
453 || (point.getAudio()!=null && inExportAudios))
455 // get the source from the point (if any)
456 String pointSource = getPointSource(inCachers, point);
457 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
458 if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
459 if (pointSource != null || !inOnlyCopies)
461 // restart track segment if necessary
462 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
463 inWriter.write(inSegmentTag);
465 if (numSaved == 0) {inWriter.write(inStartTag);}
466 if (pointSource != null) {
467 inWriter.write(pointSource);
468 inWriter.write('\n');
471 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
478 if (numSaved > 0) {inWriter.write(inEndTag);}
484 * Get the point source for the specified point
485 * @param inCachers list of GPX cachers to ask for source
486 * @param inPoint point object
487 * @return xml source if available, or null otherwise
489 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
491 if (inCachers == null || inPoint == null) {return null;}
492 String source = inCachers.getSourceString(inPoint);
493 if (source == null || !inPoint.isModified()) {return source;}
494 // Point has been modified - maybe it's possible to modify the source
495 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
496 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
497 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
498 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
499 if (inPoint.isWaypoint())
501 source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());
502 source = replaceGpxTags(source, "<description>", "</description>",
503 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
505 // photo / audio links
506 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
507 source = replaceMediaLinks(source, makeMediaLink(inPoint));
513 * Replace the given value into the given XML string
514 * @param inSource source XML for point
515 * @param inStartTag start tag for field
516 * @param inEndTag end tag for field
517 * @param inValue value to replace between start tag and end tag
518 * @return modified String, or null if not possible
520 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
522 if (inSource == null) {return null;}
523 // Look for start and end tags within source
524 final int startPos = inSource.indexOf(inStartTag);
525 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
526 if (startPos > 0 && endPos > 0)
528 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
529 if (inValue != null && origValue.equals(inValue)) {
533 else if (inValue == null || inValue.equals("")) {
534 // Need to delete value
535 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
538 // Need to replace value
539 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
542 // Value not found for this field in original source
543 if (inValue == null || inValue.equals("")) {return inSource;}
549 * Replace the media tags in the given XML string
550 * @param inSource source XML for point
551 * @param inValue value for the current point
552 * @return modified String, or null if not possible
554 private static String replaceMediaLinks(String inSource, String inValue)
556 if (inSource == null) {return null;}
557 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
558 // and the tags must have attributes. So either one heavily parameterized method or two.
559 // Look for start and end tags within source
560 final String STARTTEXT = "<link";
561 final String ENDTEXT = "</link>";
562 final int startPos = inSource.indexOf(STARTTEXT);
563 final int endPos = inSource.lastIndexOf(ENDTEXT);
564 if (startPos > 0 && endPos > 0)
566 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
567 if (inValue != null && origValue.equals(inValue)) {
571 else if (inValue == null || inValue.equals("")) {
572 // Need to delete value
573 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
576 // Need to replace value
577 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
580 // Value not found for this field in original source
581 if (inValue == null || inValue.equals("")) {return inSource;}
587 * Get the header string for the xml document including encoding
588 * @param inWriter writer object
589 * @return header string defining encoding
591 private static String getXmlHeaderString(OutputStreamWriter inWriter)
593 return "<?xml version=\"1.0\" encoding=\"" + getEncoding(inWriter) + "\"?>\n";
598 * Get the default system encoding using a writer
599 * @param inWriter writer object
600 * @return string defining encoding
602 private static String getEncoding(OutputStreamWriter inWriter)
604 String encoding = inWriter.getEncoding();
606 encoding = Charset.forName(encoding).name();
608 catch (Exception e) {} // ignore failure to find encoding
609 // Hack to fix bugs with Mac OSX (which reports MacRoman but is actually UTF-8)
610 if (encoding == null || encoding.toLowerCase().startsWith("macroman")) {
618 * Use a temporary file to obtain the name of the default system encoding
619 * @return name of default system encoding, or null if write failed
621 private static String getSystemEncoding()
623 File tempFile = null;
624 String encoding = null;
627 tempFile = File.createTempFile("prune", null);
628 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile));
629 encoding = getEncoding(writer);
632 catch (IOException e) {} // value stays null
634 if (tempFile != null && tempFile.exists()) {
635 if (!tempFile.delete()) {
636 System.err.println("Cannot delete temp file: " + tempFile.getAbsolutePath());
639 // If writing failed (eg permissions) then just ask system for default
640 if (encoding == null) encoding = Charset.defaultCharset().name();
645 * Creates temp file if necessary to check system encoding
646 * @return true if system uses UTF-8 by default
648 private static boolean isSystemUtf8()
650 if (_systemEncoding == null) _systemEncoding = getSystemEncoding();
651 return (_systemEncoding != null && _systemEncoding.toUpperCase().equals("UTF-8"));
655 * Get the header string for the gpx tag
656 * @param inCachers cacher list to ask for headers, if available
657 * @return header string from cachers or as default
659 private static String getGpxHeaderString(GpxCacherList inCachers)
661 String gpxHeader = null;
662 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
663 if (gpxHeader == null || gpxHeader.length() < 5)
665 // Create default (1.0) header
666 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
667 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
668 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
669 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
671 return gpxHeader + "\n";
676 * Export the specified waypoint into the file
677 * @param inPoint waypoint to export
678 * @param inWriter writer object
679 * @param inTimestamps true to export timestamps too
680 * @param inPhoto true to export link to photo
681 * @param inAudio true to export link to audio
682 * @throws IOException on write failure
684 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
685 boolean inPhoto, boolean inAudio)
688 inWriter.write("\t<wpt lat=\"");
689 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
690 inWriter.write("\" lon=\"");
691 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
692 inWriter.write("\">\n");
693 // altitude if available
694 if (inPoint.hasAltitude())
696 inWriter.write("\t\t<ele>");
697 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
698 inWriter.write("</ele>\n");
700 // timestamp if available (point might have timestamp and then be turned into a waypoint)
701 if (inPoint.hasTimestamp() && inTimestamps)
703 inWriter.write("\t\t<time>");
704 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
705 inWriter.write("</time>\n");
707 // write waypoint name after elevation and time
708 inWriter.write("\t\t<name>");
709 inWriter.write(inPoint.getWaypointName().trim());
710 inWriter.write("</name>\n");
711 // description, if any
712 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
713 if (desc != null && !desc.equals(""))
715 inWriter.write("\t\t<description>");
716 inWriter.write(desc);
717 inWriter.write("</description>\n");
719 // Media links, if any
720 if (inPhoto && inPoint.getPhoto() != null)
722 inWriter.write("\t\t");
723 inWriter.write(makeMediaLink(inPoint.getPhoto()));
724 inWriter.write('\n');
726 if (inAudio && inPoint.getAudio() != null)
728 inWriter.write("\t\t");
729 inWriter.write(makeMediaLink(inPoint.getAudio()));
730 inWriter.write('\n');
732 // write waypoint type if any
733 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
737 if (!type.equals(""))
739 inWriter.write("\t\t<type>");
740 inWriter.write(type);
741 inWriter.write("</type>\n");
744 inWriter.write("\t</wpt>\n");
749 * Export the specified trackpoint into the file
750 * @param inPoint trackpoint to export
751 * @param inWriter writer object
752 * @param inTimestamps true to export timestamps too
753 * @param inExportPhoto true to export photo link
754 * @param inExportAudio true to export audio link
756 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
757 boolean inExportPhoto, boolean inExportAudio)
760 inWriter.write("\t\t<trkpt lat=\"");
761 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
762 inWriter.write("\" lon=\"");
763 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
764 inWriter.write("\">");
766 if (inPoint.hasAltitude())
768 inWriter.write("<ele>");
769 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
770 inWriter.write("</ele>");
772 // timestamp if available (and selected)
773 if (inPoint.hasTimestamp() && inTimestamps)
775 inWriter.write("<time>");
776 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
777 inWriter.write("</time>");
780 if (inPoint.getPhoto() != null && inExportPhoto) {
781 inWriter.write(makeMediaLink(inPoint.getPhoto()));
783 if (inPoint.getAudio() != null && inExportAudio) {
784 inWriter.write(makeMediaLink(inPoint.getAudio()));
786 inWriter.write("</trkpt>\n");
791 * Make the xml for the media link(s)
792 * @param inPoint point to generate text for
793 * @return link tags, or null if no links
795 private static String makeMediaLink(DataPoint inPoint)
797 Photo photo = inPoint.getPhoto();
798 AudioClip audio = inPoint.getAudio();
799 if (photo == null && audio == null) {
802 String linkText = "";
804 linkText = makeMediaLink(photo);
807 linkText += makeMediaLink(audio);
813 * Make the media link for a single media item
814 * @param inMedia media item, either photo or audio
815 * @return link for this media
817 private static String makeMediaLink(MediaObject inMedia)
819 if (inMedia.getFile() != null)
821 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
822 if (inMedia.getUrl() != null)
824 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
825 // No link available, must have been loaded from zip file - no link possible