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 final String gpxHeader = getGpxHeaderString(gpxCachers);
347 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
348 inWriter.write(gpxHeader);
350 String trackName = (inName != null && !inName.equals("")) ? inName : "GpsPruneTrack";
351 writeNameAndDescription(inWriter, inName, inDesc, isVersion1_1);
354 DataPoint point = null;
355 final boolean exportTrackpoints = inSaveFlags[0];
356 final boolean exportWaypoints = inSaveFlags[1];
357 final boolean exportPhotos = inSaveFlags[2];
358 final boolean exportAudios = inSaveFlags[3];
359 final boolean exportSelection = inSaveFlags[4];
360 final boolean exportTimestamps = inSaveFlags[5];
362 int selStart = -1, selEnd = -1;
363 if (exportSelection) {
364 selStart = inInfo.getSelection().getStart();
365 selEnd = inInfo.getSelection().getEnd();
367 // Loop over waypoints
368 final int numPoints = inInfo.getTrack().getNumPoints();
370 for (i=0; i<numPoints; i++)
372 point = inInfo.getTrack().getPoint(i);
373 if (!exportSelection || (i>=selStart && i<=selEnd))
375 // Make a wpt element for each waypoint
376 if (point.isWaypoint() && exportWaypoints)
378 String pointSource = (inUseCopy?getPointSource(gpxCachers, point):null);
379 if (pointSource != null) {
380 inWriter.write(pointSource);
381 inWriter.write('\n');
384 exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
390 // Export both route points and then track points
391 if (exportTrackpoints || exportPhotos || exportAudios)
393 // Output all route points (if any)
394 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
395 exportAudios, exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n",
397 // Output all track points, if any
398 String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
399 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
400 exportAudios, exportTimestamps, false, gpxCachers, "<trkpt", trackStart,
401 "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
404 inWriter.write("</gpx>\n");
410 * Write the name and description according to the GPX version number
411 * @param inWriter writer object
412 * @param inName name, or null if none supplied
413 * @param inDesc description, or null if none supplied
414 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
416 private static void writeNameAndDescription(OutputStreamWriter inWriter,
417 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
419 String desc = (inDesc != null && !inDesc.equals("")) ? inDesc : "Export from GpsPrune";
420 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
423 // GPX 1.1 has the name and description inside a metadata tag
424 inWriter.write("\t<metadata>\n");
426 if (inName != null && !inName.equals(""))
428 inWriter.write("\t\t<name>");
429 inWriter.write(inName);
430 inWriter.write("</name>\n");
432 inWriter.write("\t\t<desc>");
433 inWriter.write(desc);
434 inWriter.write("</desc>\n");
437 inWriter.write("\t</metadata>\n");
442 * Loop through the track outputting the relevant track points
443 * @param inWriter writer object for output
444 * @param inInfo track info object containing track
445 * @param inExportSelection true to just output current selection
446 * @param inExportTrackpoints true to output track points
447 * @param inExportPhotos true to output photo points
448 * @param inExportAudios true to output audio points
449 * @param inExportTimestamps true to include timestamps in export
450 * @param inOnlyCopies true to only export if source can be copied
451 * @param inCachers list of GpxCachers
452 * @param inPointTag tag to match for each point
453 * @param inStartTag start tag to output
454 * @param inSegmentTag tag to output between segments (or null)
455 * @param inEndTag end tag to output
457 private static int writeTrackPoints(OutputStreamWriter inWriter,
458 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
459 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
460 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
461 String inStartTag, String inSegmentTag, String inEndTag)
464 // Note: far too many input parameters to this method but avoids duplication
465 // of output functionality for writing track points and route points
466 int numPoints = inInfo.getTrack().getNumPoints();
467 int selStart = inInfo.getSelection().getStart();
468 int selEnd = inInfo.getSelection().getEnd();
470 // Loop over track points
471 for (int i=0; i<numPoints; i++)
473 DataPoint point = inInfo.getTrack().getPoint(i);
474 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
476 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
477 || (point.getAudio()!=null && inExportAudios))
479 // get the source from the point (if any)
480 String pointSource = getPointSource(inCachers, point);
481 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
482 if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
483 if (pointSource != null || !inOnlyCopies)
485 // restart track segment if necessary
486 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
487 inWriter.write(inSegmentTag);
489 if (numSaved == 0) {inWriter.write(inStartTag);}
490 if (pointSource != null) {
491 inWriter.write(pointSource);
492 inWriter.write('\n');
495 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
502 if (numSaved > 0) {inWriter.write(inEndTag);}
508 * Get the point source for the specified point
509 * @param inCachers list of GPX cachers to ask for source
510 * @param inPoint point object
511 * @return xml source if available, or null otherwise
513 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
515 if (inCachers == null || inPoint == null) {return null;}
516 String source = inCachers.getSourceString(inPoint);
517 if (source == null || !inPoint.isModified()) {return source;}
518 // Point has been modified - maybe it's possible to modify the source
519 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
520 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
521 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
522 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
523 if (inPoint.isWaypoint())
525 source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());
526 source = replaceGpxTags(source, "<description>", "</description>",
527 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
529 // photo / audio links
530 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
531 source = replaceMediaLinks(source, makeMediaLink(inPoint));
537 * Replace the given value into the given XML string
538 * @param inSource source XML for point
539 * @param inStartTag start tag for field
540 * @param inEndTag end tag for field
541 * @param inValue value to replace between start tag and end tag
542 * @return modified String, or null if not possible
544 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
546 if (inSource == null) {return null;}
547 // Look for start and end tags within source
548 final int startPos = inSource.indexOf(inStartTag);
549 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
550 if (startPos > 0 && endPos > 0)
552 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
553 if (inValue != null && origValue.equals(inValue)) {
557 else if (inValue == null || inValue.equals("")) {
558 // Need to delete value
559 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
562 // Need to replace value
563 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
566 // Value not found for this field in original source
567 if (inValue == null || inValue.equals("")) {return inSource;}
573 * Replace the media tags in the given XML string
574 * @param inSource source XML for point
575 * @param inValue value for the current point
576 * @return modified String, or null if not possible
578 private static String replaceMediaLinks(String inSource, String inValue)
580 if (inSource == null) {return null;}
581 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
582 // and the tags must have attributes. So either one heavily parameterized method or two.
583 // Look for start and end tags within source
584 final String STARTTEXT = "<link";
585 final String ENDTEXT = "</link>";
586 final int startPos = inSource.indexOf(STARTTEXT);
587 final int endPos = inSource.lastIndexOf(ENDTEXT);
588 if (startPos > 0 && endPos > 0)
590 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
591 if (inValue != null && origValue.equals(inValue)) {
595 else if (inValue == null || inValue.equals("")) {
596 // Need to delete value
597 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
600 // Need to replace value
601 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
604 // Value not found for this field in original source
605 if (inValue == null || inValue.equals("")) {return inSource;}
611 * Get the header string for the xml document including encoding
612 * @param inWriter writer object
613 * @return header string defining encoding
615 private static String getXmlHeaderString(OutputStreamWriter inWriter)
617 return "<?xml version=\"1.0\" encoding=\"" + getEncoding(inWriter) + "\"?>\n";
622 * Get the default system encoding using a writer
623 * @param inWriter writer object
624 * @return string defining encoding
626 private static String getEncoding(OutputStreamWriter inWriter)
628 String encoding = inWriter.getEncoding();
630 encoding = Charset.forName(encoding).name();
632 catch (Exception e) {} // ignore failure to find encoding
633 // Hack to fix bugs with Mac OSX (which reports MacRoman but is actually UTF-8)
634 if (encoding == null || encoding.toLowerCase().startsWith("macroman")) {
642 * Use a temporary file to obtain the name of the default system encoding
643 * @return name of default system encoding, or null if write failed
645 private static String getSystemEncoding()
647 File tempFile = null;
648 String encoding = null;
651 tempFile = File.createTempFile("prune", null);
652 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile));
653 encoding = getEncoding(writer);
656 catch (IOException e) {} // value stays null
658 if (tempFile != null && tempFile.exists()) {
659 if (!tempFile.delete()) {
660 System.err.println("Cannot delete temp file: " + tempFile.getAbsolutePath());
663 // If writing failed (eg permissions) then just ask system for default
664 if (encoding == null) encoding = Charset.defaultCharset().name();
669 * Creates temp file if necessary to check system encoding
670 * @return true if system uses UTF-8 by default
672 private static boolean isSystemUtf8()
674 if (_systemEncoding == null) _systemEncoding = getSystemEncoding();
675 return (_systemEncoding != null && _systemEncoding.toUpperCase().equals("UTF-8"));
679 * Get the header string for the gpx tag
680 * @param inCachers cacher list to ask for headers, if available
681 * @return header string from cachers or as default
683 private static String getGpxHeaderString(GpxCacherList inCachers)
685 String gpxHeader = null;
686 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
687 if (gpxHeader == null || gpxHeader.length() < 5)
689 // Create default (1.0) header
690 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
691 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
692 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
693 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
695 return gpxHeader + "\n";
700 * Export the specified waypoint into the file
701 * @param inPoint waypoint to export
702 * @param inWriter writer object
703 * @param inTimestamps true to export timestamps too
704 * @param inPhoto true to export link to photo
705 * @param inAudio true to export link to audio
706 * @throws IOException on write failure
708 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
709 boolean inPhoto, boolean inAudio)
712 inWriter.write("\t<wpt lat=\"");
713 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
714 inWriter.write("\" lon=\"");
715 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
716 inWriter.write("\">\n");
717 // altitude if available
718 if (inPoint.hasAltitude())
720 inWriter.write("\t\t<ele>");
721 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
722 inWriter.write("</ele>\n");
724 // timestamp if available (point might have timestamp and then be turned into a waypoint)
725 if (inPoint.hasTimestamp() && inTimestamps)
727 inWriter.write("\t\t<time>");
728 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
729 inWriter.write("</time>\n");
731 // write waypoint name after elevation and time
732 inWriter.write("\t\t<name>");
733 inWriter.write(inPoint.getWaypointName().trim());
734 inWriter.write("</name>\n");
735 // description, if any
736 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
737 if (desc != null && !desc.equals(""))
739 inWriter.write("\t\t<description>");
740 inWriter.write(desc);
741 inWriter.write("</description>\n");
743 // Media links, if any
744 if (inPhoto && inPoint.getPhoto() != null)
746 inWriter.write("\t\t");
747 inWriter.write(makeMediaLink(inPoint.getPhoto()));
748 inWriter.write('\n');
750 if (inAudio && inPoint.getAudio() != null)
752 inWriter.write("\t\t");
753 inWriter.write(makeMediaLink(inPoint.getAudio()));
754 inWriter.write('\n');
756 // write waypoint type if any
757 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
761 if (!type.equals(""))
763 inWriter.write("\t\t<type>");
764 inWriter.write(type);
765 inWriter.write("</type>\n");
768 inWriter.write("\t</wpt>\n");
773 * Export the specified trackpoint into the file
774 * @param inPoint trackpoint to export
775 * @param inWriter writer object
776 * @param inTimestamps true to export timestamps too
777 * @param inExportPhoto true to export photo link
778 * @param inExportAudio true to export audio link
780 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
781 boolean inExportPhoto, boolean inExportAudio)
784 inWriter.write("\t\t<trkpt lat=\"");
785 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
786 inWriter.write("\" lon=\"");
787 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
788 inWriter.write("\">");
790 if (inPoint.hasAltitude())
792 inWriter.write("<ele>");
793 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
794 inWriter.write("</ele>");
796 // timestamp if available (and selected)
797 if (inPoint.hasTimestamp() && inTimestamps)
799 inWriter.write("<time>");
800 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
801 inWriter.write("</time>");
804 if (inPoint.getPhoto() != null && inExportPhoto) {
805 inWriter.write(makeMediaLink(inPoint.getPhoto()));
807 if (inPoint.getAudio() != null && inExportAudio) {
808 inWriter.write(makeMediaLink(inPoint.getAudio()));
810 inWriter.write("</trkpt>\n");
815 * Make the xml for the media link(s)
816 * @param inPoint point to generate text for
817 * @return link tags, or null if no links
819 private static String makeMediaLink(DataPoint inPoint)
821 Photo photo = inPoint.getPhoto();
822 AudioClip audio = inPoint.getAudio();
823 if (photo == null && audio == null) {
826 String linkText = "";
828 linkText = makeMediaLink(photo);
831 linkText += makeMediaLink(audio);
837 * Make the media link for a single media item
838 * @param inMedia media item, either photo or audio
839 * @return link for this media
841 private static String makeMediaLink(MediaObject inMedia)
843 if (inMedia.getFile() != null)
845 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
846 if (inMedia.getUrl() != null)
848 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
849 // No link available, must have been loaded from zip file - no link possible