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 // Instantiate source file cachers in case we want to copy output
282 GpxCacherList gpxCachers = null;
283 if (_copySourceCheckbox.isSelected()) {
284 gpxCachers = new GpxCacherList(_trackInfo.getFileInfo());
286 OutputStreamWriter writer = null;
289 // normal writing to file - firstly specify UTF8 encoding if requested
290 if (_forceUtf8Radio != null && _forceUtf8Radio.isSelected())
291 writer = new OutputStreamWriter(new FileOutputStream(_exportFile), "UTF-8");
293 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
294 final boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
295 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getAudiopointsSelected(),
296 _pointTypeSelector.getJustSelection(), _timestampsCheckbox.isSelected()};
298 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
299 _descriptionField.getText(), saveFlags, gpxCachers);
303 // Store directory in config for later
304 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
305 // Add to recent file list
306 Config.getRecentFileList().addFile(new RecentFile(_exportFile, true));
308 UpdateMessageBroker.informSubscribers();
309 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
310 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
311 + " " + _exportFile.getAbsolutePath());
312 // export successful so need to close dialog and return
316 catch (IOException ioe)
318 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
320 if (writer != null) writer.close();
322 catch (IOException ioe2) {}
323 JOptionPane.showMessageDialog(_parentFrame,
324 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
325 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
327 // if not returned already, export failed so need to recall the file selection
333 * Export the information to the given writer
334 * @param inWriter writer object
335 * @param inInfo track info object
336 * @param inName name of track (optional)
337 * @param inDesc description of track (optional)
338 * @param inSaveFlags array of booleans to export tracks, waypoints, photos, audios, selection, timestamps
339 * @param inGpxCachers list of Gpx cachers containing input data
340 * @return number of points written
341 * @throws IOException if io errors occur on write
343 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
344 String inDesc, boolean[] inSaveFlags, GpxCacherList inGpxCachers) throws IOException
346 // Write or copy headers
347 inWriter.write(getXmlHeaderString(inWriter));
348 final String gpxHeader = getGpxHeaderString(inGpxCachers);
349 final boolean isVersion1_1 = (gpxHeader.toUpperCase().indexOf("GPX/1/1") > 0);
350 inWriter.write(gpxHeader);
352 String trackName = (inName != null && !inName.equals("")) ? inName : "GpsPruneTrack";
353 writeNameAndDescription(inWriter, inName, inDesc, isVersion1_1);
356 DataPoint point = null;
357 final boolean exportTrackpoints = inSaveFlags[0];
358 final boolean exportWaypoints = inSaveFlags[1];
359 final boolean exportPhotos = inSaveFlags[2];
360 final boolean exportAudios = inSaveFlags[3];
361 final boolean exportSelection = inSaveFlags[4];
362 final boolean exportTimestamps = inSaveFlags[5];
364 int selStart = -1, selEnd = -1;
365 if (exportSelection) {
366 selStart = inInfo.getSelection().getStart();
367 selEnd = inInfo.getSelection().getEnd();
369 // Loop over waypoints
370 final int numPoints = inInfo.getTrack().getNumPoints();
372 for (i=0; i<numPoints; i++)
374 point = inInfo.getTrack().getPoint(i);
375 if (!exportSelection || (i>=selStart && i<=selEnd))
377 // Make a wpt element for each waypoint
378 if (point.isWaypoint() && exportWaypoints)
380 String pointSource = (inGpxCachers == null? null : getPointSource(inGpxCachers, point));
381 if (pointSource != null)
383 // If timestamp checkbox is off, strip time
384 if (!exportTimestamps) {
385 pointSource = stripTime(pointSource);
387 inWriter.write(pointSource);
388 inWriter.write('\n');
391 exportWaypoint(point, inWriter, exportTimestamps, exportPhotos, exportAudios);
397 // Export both route points and then track points
398 if (exportTrackpoints || exportPhotos || exportAudios)
400 // Output all route points (if any)
401 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
402 exportAudios, exportTimestamps, true, inGpxCachers, "<rtept", "\t<rte><number>1</number>\n",
404 // Output all track points, if any
405 String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
406 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
407 exportAudios, exportTimestamps, false, inGpxCachers, "<trkpt", trackStart,
408 "\t</trkseg>\n\t<trkseg>\n", "\t</trkseg></trk>\n");
411 inWriter.write("</gpx>\n");
417 * Write the name and description according to the GPX version number
418 * @param inWriter writer object
419 * @param inName name, or null if none supplied
420 * @param inDesc description, or null if none supplied
421 * @param inIsVersion1_1 true if gpx version 1.1, false for version 1.0
423 private static void writeNameAndDescription(OutputStreamWriter inWriter,
424 String inName, String inDesc, boolean inIsVersion1_1) throws IOException
426 String desc = (inDesc != null && !inDesc.equals("")) ? inDesc : "Export from GpsPrune";
427 // Position of name and description fields needs to be different for GPX1.0 and GPX1.1
430 // GPX 1.1 has the name and description inside a metadata tag
431 inWriter.write("\t<metadata>\n");
433 if (inName != null && !inName.equals(""))
435 inWriter.write("\t\t<name>");
436 inWriter.write(inName);
437 inWriter.write("</name>\n");
439 inWriter.write("\t\t<desc>");
440 inWriter.write(desc);
441 inWriter.write("</desc>\n");
444 inWriter.write("\t</metadata>\n");
449 * Loop through the track outputting the relevant track points
450 * @param inWriter writer object for output
451 * @param inInfo track info object containing track
452 * @param inExportSelection true to just output current selection
453 * @param inExportTrackpoints true to output track points
454 * @param inExportPhotos true to output photo points
455 * @param inExportAudios true to output audio points
456 * @param inExportTimestamps true to include timestamps in export
457 * @param inOnlyCopies true to only export if source can be copied
458 * @param inCachers list of GpxCachers
459 * @param inPointTag tag to match for each point
460 * @param inStartTag start tag to output
461 * @param inSegmentTag tag to output between segments (or null)
462 * @param inEndTag end tag to output
464 private static int writeTrackPoints(OutputStreamWriter inWriter,
465 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
466 boolean inExportPhotos, boolean inExportAudios, boolean exportTimestamps,
467 boolean inOnlyCopies, GpxCacherList inCachers, String inPointTag,
468 String inStartTag, String inSegmentTag, String inEndTag)
471 // Note: far too many input parameters to this method but avoids duplication
472 // of output functionality for writing track points and route points
473 int numPoints = inInfo.getTrack().getNumPoints();
474 int selStart = inInfo.getSelection().getStart();
475 int selEnd = inInfo.getSelection().getEnd();
477 // Loop over track points
478 for (int i=0; i<numPoints; i++)
480 DataPoint point = inInfo.getTrack().getPoint(i);
481 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
483 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos)
484 || (point.getAudio()!=null && inExportAudios))
486 // get the source from the point (if any)
487 String pointSource = getPointSource(inCachers, point);
488 // Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
489 if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
490 if (pointSource != null || !inOnlyCopies)
492 // restart track segment if necessary
493 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
494 inWriter.write(inSegmentTag);
496 if (numSaved == 0) {inWriter.write(inStartTag);}
497 if (pointSource != null)
499 // If timestamps checkbox is off, strip the time
500 if (!exportTimestamps) {
501 pointSource = stripTime(pointSource);
503 inWriter.write(pointSource);
504 inWriter.write('\n');
507 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps, inExportPhotos, inExportAudios);}
514 if (numSaved > 0) {inWriter.write(inEndTag);}
520 * Get the point source for the specified point
521 * @param inCachers list of GPX cachers to ask for source
522 * @param inPoint point object
523 * @return xml source if available, or null otherwise
525 private static String getPointSource(GpxCacherList inCachers, DataPoint inPoint)
527 if (inCachers == null || inPoint == null) {return null;}
528 String source = inCachers.getSourceString(inPoint);
529 if (source == null || !inPoint.isModified()) {return source;}
530 // Point has been modified - maybe it's possible to modify the source
531 source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
532 source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
533 source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
534 source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
535 if (inPoint.isWaypoint())
537 source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());
538 source = replaceGpxTags(source, "<description>", "</description>",
539 XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION)));
541 // photo / audio links
542 if (source != null && (inPoint.hasMedia() || source.indexOf("</link>") > 0)) {
543 source = replaceMediaLinks(source, makeMediaLink(inPoint));
549 * Replace the given value into the given XML string
550 * @param inSource source XML for point
551 * @param inStartTag start tag for field
552 * @param inEndTag end tag for field
553 * @param inValue value to replace between start tag and end tag
554 * @return modified String, or null if not possible
556 private static String replaceGpxTags(String inSource, String inStartTag, String inEndTag, String inValue)
558 if (inSource == null) {return null;}
559 // Look for start and end tags within source
560 final int startPos = inSource.indexOf(inStartTag);
561 final int endPos = inSource.indexOf(inEndTag, startPos+inStartTag.length());
562 if (startPos > 0 && endPos > 0)
564 String origValue = inSource.substring(startPos + inStartTag.length(), endPos);
565 if (inValue != null && origValue.equals(inValue)) {
569 else if (inValue == null || inValue.equals("")) {
570 // Need to delete value
571 return inSource.substring(0, startPos) + inSource.substring(endPos + inEndTag.length());
574 // Need to replace value
575 return inSource.substring(0, startPos+inStartTag.length()) + inValue + inSource.substring(endPos);
578 // Value not found for this field in original source
579 if (inValue == null || inValue.equals("")) {return inSource;}
585 * Replace the media tags in the given XML string
586 * @param inSource source XML for point
587 * @param inValue value for the current point
588 * @return modified String, or null if not possible
590 private static String replaceMediaLinks(String inSource, String inValue)
592 if (inSource == null) {return null;}
593 // Note that this method is very similar to replaceGpxTags except there can be multiple link tags
594 // and the tags must have attributes. So either one heavily parameterized method or two.
595 // Look for start and end tags within source
596 final String STARTTEXT = "<link";
597 final String ENDTEXT = "</link>";
598 final int startPos = inSource.indexOf(STARTTEXT);
599 final int endPos = inSource.lastIndexOf(ENDTEXT);
600 if (startPos > 0 && endPos > 0)
602 String origValue = inSource.substring(startPos, endPos + ENDTEXT.length());
603 if (inValue != null && origValue.equals(inValue)) {
607 else if (inValue == null || inValue.equals("")) {
608 // Need to delete value
609 return inSource.substring(0, startPos) + inSource.substring(endPos + ENDTEXT.length());
612 // Need to replace value
613 return inSource.substring(0, startPos) + inValue + inSource.substring(endPos + ENDTEXT.length());
616 // Value not found for this field in original source
617 if (inValue == null || inValue.equals("")) {return inSource;}
623 * Get the header string for the xml document including encoding
624 * @param inWriter writer object
625 * @return header string defining encoding
627 private static String getXmlHeaderString(OutputStreamWriter inWriter)
629 return "<?xml version=\"1.0\" encoding=\"" + getEncoding(inWriter) + "\"?>\n";
634 * Get the default system encoding using a writer
635 * @param inWriter writer object
636 * @return string defining encoding
638 private static String getEncoding(OutputStreamWriter inWriter)
640 String encoding = inWriter.getEncoding();
642 encoding = Charset.forName(encoding).name();
644 catch (Exception e) {} // ignore failure to find encoding
650 * Use a temporary file to obtain the name of the default system encoding
651 * @return name of default system encoding, or null if write failed
653 private static String getSystemEncoding()
655 File tempFile = null;
656 String encoding = null;
659 tempFile = File.createTempFile("prune", null);
660 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile));
661 encoding = getEncoding(writer);
664 catch (IOException e) {} // value stays null
666 if (tempFile != null && tempFile.exists()) {
667 if (!tempFile.delete()) {
668 System.err.println("Cannot delete temp file: " + tempFile.getAbsolutePath());
671 // If writing failed (eg permissions) then just ask system for default
672 if (encoding == null) encoding = Charset.defaultCharset().name();
677 * Creates temp file if necessary to check system encoding
678 * @return true if system uses UTF-8 by default
680 private static boolean isSystemUtf8()
682 if (_systemEncoding == null) _systemEncoding = getSystemEncoding();
683 return (_systemEncoding != null && _systemEncoding.toUpperCase().equals("UTF-8"));
687 * Get the header string for the gpx tag
688 * @param inCachers cacher list to ask for headers, if available
689 * @return header string from cachers or as default
691 private static String getGpxHeaderString(GpxCacherList inCachers)
693 String gpxHeader = null;
694 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
695 if (gpxHeader == null || gpxHeader.length() < 5)
697 // Create default (1.0) header
698 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
699 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
700 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
701 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
703 return gpxHeader + "\n";
708 * Export the specified waypoint into the file
709 * @param inPoint waypoint to export
710 * @param inWriter writer object
711 * @param inTimestamps true to export timestamps too
712 * @param inPhoto true to export link to photo
713 * @param inAudio true to export link to audio
714 * @throws IOException on write failure
716 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
717 boolean inPhoto, boolean inAudio)
720 inWriter.write("\t<wpt lat=\"");
721 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
722 inWriter.write("\" lon=\"");
723 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
724 inWriter.write("\">\n");
725 // altitude if available
726 if (inPoint.hasAltitude())
728 inWriter.write("\t\t<ele>");
729 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
730 inWriter.write("</ele>\n");
732 // timestamp if available (point might have timestamp and then be turned into a waypoint)
733 if (inPoint.hasTimestamp() && inTimestamps)
735 inWriter.write("\t\t<time>");
736 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
737 inWriter.write("</time>\n");
739 // write waypoint name after elevation and time
740 inWriter.write("\t\t<name>");
741 inWriter.write(inPoint.getWaypointName().trim());
742 inWriter.write("</name>\n");
743 // description, if any
744 String desc = XmlUtils.fixCdata(inPoint.getFieldValue(Field.DESCRIPTION));
745 if (desc != null && !desc.equals(""))
747 inWriter.write("\t\t<description>");
748 inWriter.write(desc);
749 inWriter.write("</description>\n");
751 // Media links, if any
752 if (inPhoto && inPoint.getPhoto() != null)
754 inWriter.write("\t\t");
755 inWriter.write(makeMediaLink(inPoint.getPhoto()));
756 inWriter.write('\n');
758 if (inAudio && inPoint.getAudio() != null)
760 inWriter.write("\t\t");
761 inWriter.write(makeMediaLink(inPoint.getAudio()));
762 inWriter.write('\n');
764 // write waypoint type if any
765 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
769 if (!type.equals(""))
771 inWriter.write("\t\t<type>");
772 inWriter.write(type);
773 inWriter.write("</type>\n");
776 inWriter.write("\t</wpt>\n");
781 * Export the specified trackpoint into the file
782 * @param inPoint trackpoint to export
783 * @param inWriter writer object
784 * @param inTimestamps true to export timestamps too
785 * @param inExportPhoto true to export photo link
786 * @param inExportAudio true to export audio link
788 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps,
789 boolean inExportPhoto, boolean inExportAudio)
792 inWriter.write("\t\t<trkpt lat=\"");
793 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
794 inWriter.write("\" lon=\"");
795 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
796 inWriter.write("\">");
798 if (inPoint.hasAltitude())
800 inWriter.write("<ele>");
801 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
802 inWriter.write("</ele>");
804 // timestamp if available (and selected)
805 if (inPoint.hasTimestamp() && inTimestamps)
807 inWriter.write("<time>");
808 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
809 inWriter.write("</time>");
812 if (inPoint.getPhoto() != null && inExportPhoto) {
813 inWriter.write(makeMediaLink(inPoint.getPhoto()));
815 if (inPoint.getAudio() != null && inExportAudio) {
816 inWriter.write(makeMediaLink(inPoint.getAudio()));
818 inWriter.write("</trkpt>\n");
823 * Make the xml for the media link(s)
824 * @param inPoint point to generate text for
825 * @return link tags, or null if no links
827 private static String makeMediaLink(DataPoint inPoint)
829 Photo photo = inPoint.getPhoto();
830 AudioClip audio = inPoint.getAudio();
831 if (photo == null && audio == null) {
834 String linkText = "";
836 linkText = makeMediaLink(photo);
839 linkText += makeMediaLink(audio);
845 * Make the media link for a single media item
846 * @param inMedia media item, either photo or audio
847 * @return link for this media
849 private static String makeMediaLink(MediaObject inMedia)
851 if (inMedia.getFile() != null)
853 return "<link href=\"" + inMedia.getFile().getAbsolutePath() + "\"><text>" + inMedia.getName() + "</text></link>";
854 if (inMedia.getUrl() != null)
856 return "<link href=\"" + inMedia.getUrl() + "\"><text>" + inMedia.getName() + "</text></link>";
857 // No link available, must have been loaded from zip file - no link possible
863 * Strip the time from a GPX point source string
864 * @param inPointSource point source to copy
865 * @return point source with timestamp removed
867 private static String stripTime(String inPointSource)
869 return inPointSource.replaceAll("<time>.*?</time>", "");