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.JButton;
19 import javax.swing.JCheckBox;
20 import javax.swing.JDialog;
21 import javax.swing.JFileChooser;
22 import javax.swing.JFrame;
23 import javax.swing.JLabel;
24 import javax.swing.JOptionPane;
25 import javax.swing.JPanel;
26 import javax.swing.JTextField;
29 import tim.prune.GenericFunction;
30 import tim.prune.GpsPruner;
31 import tim.prune.I18nManager;
32 import tim.prune.UpdateMessageBroker;
33 import tim.prune.config.Config;
34 import tim.prune.data.Altitude;
35 import tim.prune.data.Coordinate;
36 import tim.prune.data.DataPoint;
37 import tim.prune.data.Field;
38 import tim.prune.data.Timestamp;
39 import tim.prune.data.TrackInfo;
40 import tim.prune.load.GenericFileFilter;
43 * Class to export track information
44 * into a specified Gpx file
46 public class GpxExporter extends GenericFunction implements Runnable
48 private TrackInfo _trackInfo = null;
49 private JDialog _dialog = null;
50 private JTextField _nameField = null;
51 private JTextField _descriptionField = null;
52 private PointTypeSelector _pointTypeSelector = null;
53 private JCheckBox _timestampsCheckbox = null;
54 private JCheckBox _copySourceCheckbox = null;
55 private File _exportFile = null;
57 /** this program name */
58 private static final String GPX_CREATOR = "Prune v" + GpsPruner.VERSION_NUMBER + " activityworkshop.net";
63 * @param inApp app object
65 public GpxExporter(App inApp)
68 _trackInfo = inApp.getTrackInfo();
72 public String getNameKey() {
73 return "function.exportgpx";
77 * Show the dialog to select options and export file
84 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
85 _dialog.setLocationRelativeTo(_parentFrame);
86 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
87 _dialog.getContentPane().add(makeDialogComponents());
90 _pointTypeSelector.init(_app.getTrackInfo());
91 _dialog.setVisible(true);
96 * Create dialog components
97 * @return Panel containing all gui elements in dialog
99 private Component makeDialogComponents()
101 JPanel dialogPanel = new JPanel();
102 dialogPanel.setLayout(new BorderLayout());
103 JPanel mainPanel = new JPanel();
104 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
105 // Make a central panel with the text boxes
106 JPanel descPanel = new JPanel();
107 descPanel.setLayout(new GridLayout(2, 2));
108 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
109 _nameField = new JTextField(10);
110 descPanel.add(_nameField);
111 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
112 _descriptionField = new JTextField(10);
113 descPanel.add(_descriptionField);
114 mainPanel.add(descPanel);
115 mainPanel.add(Box.createVerticalStrut(5));
116 // point type selection (track points, waypoints, photo points)
117 _pointTypeSelector = new PointTypeSelector();
118 mainPanel.add(_pointTypeSelector);
119 // checkboxes for timestamps and copying
120 JPanel checkPanel = new JPanel();
121 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
122 _timestampsCheckbox.setSelected(true);
123 checkPanel.add(_timestampsCheckbox);
124 _copySourceCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.copysource"));
125 _copySourceCheckbox.setSelected(true);
126 checkPanel.add(_copySourceCheckbox);
127 mainPanel.add(checkPanel);
128 dialogPanel.add(mainPanel, BorderLayout.CENTER);
130 // button panel at bottom
131 JPanel buttonPanel = new JPanel();
132 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
133 JButton okButton = new JButton(I18nManager.getText("button.ok"));
134 ActionListener okListener = new ActionListener() {
135 public void actionPerformed(ActionEvent e)
140 okButton.addActionListener(okListener);
141 _descriptionField.addActionListener(okListener);
142 buttonPanel.add(okButton);
143 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
144 cancelButton.addActionListener(new ActionListener() {
145 public void actionPerformed(ActionEvent e)
150 buttonPanel.add(cancelButton);
151 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
152 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
158 * Start the export process based on the input parameters
160 private void startExport()
162 // OK pressed, so check selections
163 if (!_pointTypeSelector.getAnythingSelected()) {
164 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
165 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
168 // Choose output file
169 File saveFile = chooseGpxFile(_parentFrame);
170 if (saveFile != null)
172 // New file or overwrite confirmed, so initiate export in separate thread
173 _exportFile = saveFile;
174 new Thread(this).start();
179 * Select a GPX file to save to
180 * @param inParentFrame parent frame for file chooser dialog
181 * @return selected File, or null if selection cancelled
183 public static File chooseGpxFile(JFrame inParentFrame)
185 File saveFile = null;
186 JFileChooser fileChooser = new JFileChooser();
187 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
188 fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
189 fileChooser.setAcceptAllFileFilterUsed(false);
190 // start from directory in config which should be set
191 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
192 if (configDir != null) {fileChooser.setCurrentDirectory(new File(configDir));}
194 // Allow choose again if an existing file is selected
195 boolean chooseAgain = false;
199 if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
201 // OK pressed and file chosen
202 File file = fileChooser.getSelectedFile();
203 // Check file extension
204 if (!file.getName().toLowerCase().endsWith(".gpx"))
206 file = new File(file.getAbsolutePath() + ".gpx");
208 // Check if file exists and if necessary prompt for overwrite
209 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
210 if (!file.exists() || JOptionPane.showOptionDialog(inParentFrame,
211 I18nManager.getText("dialog.save.overwrite.text"),
212 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
213 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
214 == JOptionPane.YES_OPTION)
216 // new file or overwrite confirmed
221 // file exists and overwrite cancelled - select again
225 } while (chooseAgain);
230 * Run method for controlling separate thread for exporting
234 OutputStreamWriter writer = null;
237 // normal writing to file
238 writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
239 boolean[] saveFlags = {_pointTypeSelector.getTrackpointsSelected(), _pointTypeSelector.getWaypointsSelected(),
240 _pointTypeSelector.getPhotopointsSelected(), _pointTypeSelector.getJustSelection(),
241 _timestampsCheckbox.isSelected()};
243 final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
244 _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
248 // Store directory in config for later
249 Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
251 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
252 + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
253 + " " + _exportFile.getAbsolutePath());
254 // export successful so need to close dialog and return
258 catch (IOException ioe)
260 // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
262 if (writer != null) writer.close();
264 catch (IOException ioe2) {}
265 JOptionPane.showMessageDialog(_parentFrame,
266 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
267 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
269 // if not returned already, export failed so need to recall the file selection
275 * Export the information to the given writer
276 * @param inWriter writer object
277 * @param inInfo track info object
278 * @param inName name of track (optional)
279 * @param inDesc description of track (optional)
280 * @param inSaveFlags array of booleans to export tracks, waypoints, photos, timestamps
281 * @param inUseCopy true to copy source if available
282 * @return number of points written
283 * @throws IOException if io errors occur on write
285 public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
286 String inDesc, boolean[] inSaveFlags, boolean inUseCopy) throws IOException
288 // Instantiate source file cachers in case we want to copy output
289 GpxCacherList gpxCachers = null;
290 if (inUseCopy) gpxCachers = new GpxCacherList(inInfo.getFileInfo());
291 // Write or copy header
292 inWriter.write(getHeaderString(gpxCachers));
294 String trackName = "PruneTrack";
295 if (inName != null && !inName.equals(""))
298 inWriter.write("\t<name>");
299 inWriter.write(trackName);
300 inWriter.write("</name>\n");
303 inWriter.write("\t<desc>");
304 if (inDesc != null && !inDesc.equals("")) {
305 inWriter.write(inDesc);
308 inWriter.write("Export from Prune");
310 inWriter.write("</desc>\n");
313 DataPoint point = null;
314 boolean hasTrackpoints = false;
315 final boolean exportTrackpoints = inSaveFlags[0];
316 final boolean exportWaypoints = inSaveFlags[1];
317 final boolean exportPhotos = inSaveFlags[2];
318 final boolean exportSelection = inSaveFlags[3];
319 final boolean exportTimestamps = inSaveFlags[4];
321 int selStart = -1, selEnd = -1;
322 if (exportSelection) {
323 selStart = inInfo.getSelection().getStart();
324 selEnd = inInfo.getSelection().getEnd();
326 // Loop over waypoints
327 final int numPoints = inInfo.getTrack().getNumPoints();
329 for (i=0; i<numPoints; i++)
331 point = inInfo.getTrack().getPoint(i);
332 if (!exportSelection || (i>=selStart && i<=selEnd)) {
333 // Make a wpt element for each waypoint
334 if (point.isWaypoint()) {
337 String pointSource = (inUseCopy?gpxCachers.getSourceString(point):null);
338 if (pointSource != null) {
339 inWriter.write(pointSource);
340 inWriter.write('\n');
343 exportWaypoint(point, inWriter, exportTimestamps);
349 hasTrackpoints = true;
353 // Export both route points and then track points
354 if (hasTrackpoints && (exportTrackpoints || exportPhotos))
356 // Output all route points (if any)
357 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
358 exportTimestamps, true, gpxCachers, "<rtept", "\t<rte><number>1</number>\n", null, "\t</rte>\n");
359 // Output all track points, if any
360 String trackStart = "\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n";
361 numSaved += writeTrackPoints(inWriter, inInfo, exportSelection, exportTrackpoints, exportPhotos,
362 exportTimestamps, false, gpxCachers, "<trkpt", trackStart, "\t</trkseg>\n\t<trkseg>\n",
363 "\t</trkseg></trk>\n");
366 inWriter.write("</gpx>\n");
371 * Loop through the track outputting the relevant track points
372 * @param inWriter writer object for output
373 * @param inInfo track info object containing track
374 * @param inExportSelection true to just output current selection
375 * @param inExportTrackpoints true to output track points
376 * @param inExportPhotos true to output photo points
377 * @param exportTimestamps true to include timestamps in export
378 * @param inOnlyCopies true to only export if source can be copied
379 * @param inCachers list of GpxCachers
380 * @param inPointTag tag to match for each point
381 * @param inStartTag start tag to output
382 * @param inSegmentTag tag to output between segments (or null)
383 * @param inEndTag end tag to output
385 private static int writeTrackPoints(OutputStreamWriter inWriter,
386 TrackInfo inInfo, boolean inExportSelection, boolean inExportTrackpoints,
387 boolean inExportPhotos, boolean exportTimestamps, boolean inOnlyCopies,
388 GpxCacherList inCachers, String inPointTag, String inStartTag,
389 String inSegmentTag, String inEndTag)
392 // Note: far too many input parameters to this method but avoids duplication
393 // of output functionality for writing track points and route points
394 int numPoints = inInfo.getTrack().getNumPoints();
395 int selStart = inInfo.getSelection().getStart();
396 int selEnd = inInfo.getSelection().getEnd();
398 // Loop over track points
399 for (int i=0; i<numPoints; i++)
401 DataPoint point = inInfo.getTrack().getPoint(i);
402 if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
404 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos))
406 // get the source from the point (if any)
407 String pointSource = (inCachers!=null?inCachers.getSourceString(point):null);
408 boolean writePoint = (pointSource != null && pointSource.toLowerCase().startsWith(inPointTag))
409 || (pointSource == null && !inOnlyCopies);
412 // restart track segment if necessary
413 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
414 inWriter.write(inSegmentTag);
416 if (numSaved == 0) {inWriter.write(inStartTag);}
417 if (pointSource != null) {
418 inWriter.write(pointSource);
419 inWriter.write('\n');
422 if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps);}
429 if (numSaved > 0) {inWriter.write(inEndTag);}
434 * Get the header string for the gpx
435 * @param inCachers cacher list to ask for headers, if available
436 * @return header string from cachers or as default
438 private static String getHeaderString(GpxCacherList inCachers)
440 String gpxHeader = null;
441 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
442 if (gpxHeader == null || gpxHeader.length() < 5)
444 // Create default (1.0) header
445 gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
446 + "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
447 + " xmlns=\"http://www.topografix.com/GPX/1/0\""
448 + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n";
450 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + gpxHeader + "\n";
454 * Export the specified waypoint into the file
455 * @param inPoint waypoint to export
456 * @param inWriter writer object
457 * @param inTimestamps true to export timestamps too
458 * @throws IOException on write failure
460 private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
463 inWriter.write("\t<wpt lat=\"");
464 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
465 inWriter.write("\" lon=\"");
466 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
467 inWriter.write("\">\n");
468 // altitude if available
469 if (inPoint.hasAltitude())
471 inWriter.write("\t\t<ele>");
472 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
473 inWriter.write("</ele>\n");
475 // timestamp if available (point might have timestamp and then be turned into a waypoint)
476 if (inPoint.hasTimestamp() && inTimestamps)
478 inWriter.write("\t\t<time>");
479 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
480 inWriter.write("</time>\n");
482 // write waypoint name after elevation and time
483 inWriter.write("\t\t<name>");
484 inWriter.write(inPoint.getWaypointName().trim());
485 inWriter.write("</name>\n");
486 // write waypoint type if any
487 String type = inPoint.getFieldValue(Field.WAYPT_TYPE);
491 if (!type.equals(""))
493 inWriter.write("\t\t<type>");
494 inWriter.write(type);
495 inWriter.write("</type>\n");
498 inWriter.write("\t</wpt>\n");
503 * Export the specified trackpoint into the file
504 * @param inPoint trackpoint to export
505 * @param inWriter writer object
506 * @param inTimestamps true to export timestamps too
508 private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
511 inWriter.write("\t\t<trkpt lat=\"");
512 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
513 inWriter.write("\" lon=\"");
514 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
515 inWriter.write("\">");
517 if (inPoint.hasAltitude())
519 inWriter.write("<ele>");
520 inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
521 inWriter.write("</ele>");
523 // timestamp if available (and selected)
524 if (inPoint.hasTimestamp() && inTimestamps)
526 inWriter.write("<time>");
527 inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
528 inWriter.write("</time>");
530 inWriter.write("</trkpt>\n");