]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/GpxExporter.java
019cc438bdbf96003595e7c7ebe7d84654839d11
[GpsPrune.git] / tim / prune / save / GpxExporter.java
1 package tim.prune.save;
2
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;
9 import java.io.File;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.OutputStreamWriter;
13 import java.io.Writer;
14
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;
27
28 import tim.prune.App;
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;
41
42 /**
43  * Class to export track information
44  * into a specified Gpx file
45  */
46 public class GpxExporter extends GenericFunction implements Runnable
47 {
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;
56
57         /** this program name */
58         private static final String GPX_CREATOR = "Prune v" + GpsPruner.VERSION_NUMBER + " activityworkshop.net";
59
60
61         /**
62          * Constructor
63          * @param inApp app object
64          */
65         public GpxExporter(App inApp)
66         {
67                 super(inApp);
68                 _trackInfo = inApp.getTrackInfo();
69         }
70
71         /** Get name key */
72         public String getNameKey() {
73                 return "function.exportgpx";
74         }
75
76         /**
77          * Show the dialog to select options and export file
78          */
79         public void begin()
80         {
81                 // Make dialog window
82                 if (_dialog == null)
83                 {
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());
88                         _dialog.pack();
89                 }
90                 _pointTypeSelector.init(_app.getTrackInfo());
91                 _dialog.setVisible(true);
92         }
93
94
95         /**
96          * Create dialog components
97          * @return Panel containing all gui elements in dialog
98          */
99         private Component makeDialogComponents()
100         {
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);
129
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)
136                         {
137                                 startExport();
138                         }
139                 };
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)
146                         {
147                                 _dialog.dispose();
148                         }
149                 });
150                 buttonPanel.add(cancelButton);
151                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
152                 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
153                 return dialogPanel;
154         }
155
156
157         /**
158          * Start the export process based on the input parameters
159          */
160         private void startExport()
161         {
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);
166                         return;
167                 }
168                 // Choose output file
169                 File saveFile = chooseGpxFile(_parentFrame);
170                 if (saveFile != null)
171                 {
172                         // New file or overwrite confirmed, so initiate export in separate thread
173                         _exportFile = saveFile;
174                         new Thread(this).start();
175                 }
176         }
177
178         /**
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
182          */
183         public static File chooseGpxFile(JFrame inParentFrame)
184         {
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));}
193
194                 // Allow choose again if an existing file is selected
195                 boolean chooseAgain = false;
196                 do
197                 {
198                         chooseAgain = false;
199                         if (fileChooser.showSaveDialog(inParentFrame) == JFileChooser.APPROVE_OPTION)
200                         {
201                                 // OK pressed and file chosen
202                                 File file = fileChooser.getSelectedFile();
203                                 // Check file extension
204                                 if (!file.getName().toLowerCase().endsWith(".gpx"))
205                                 {
206                                         file = new File(file.getAbsolutePath() + ".gpx");
207                                 }
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)
215                                 {
216                                         // new file or overwrite confirmed
217                                         saveFile = file;
218                                 }
219                                 else
220                                 {
221                                         // file exists and overwrite cancelled - select again
222                                         chooseAgain = true;
223                                 }
224                         }
225                 } while (chooseAgain);
226                 return saveFile;
227         }
228
229         /**
230          * Run method for controlling separate thread for exporting
231          */
232         public void run()
233         {
234                 OutputStreamWriter writer = null;
235                 try
236                 {
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()};
242                         // write file
243                         final int numPoints = exportData(writer, _trackInfo, _nameField.getText(),
244                                 _descriptionField.getText(), saveFlags, _copySourceCheckbox.isSelected());
245
246                         // close file
247                         writer.close();
248                         // Store directory in config for later
249                         Config.setConfigString(Config.KEY_TRACK_DIR, _exportFile.getParentFile().getAbsolutePath());
250                         // Show confirmation
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
255                         _dialog.dispose();
256                         return;
257                 }
258                 catch (IOException ioe)
259                 {
260                         // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
261                         try {
262                                 if (writer != null) writer.close();
263                         }
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);
268                 }
269                 // if not returned already, export failed so need to recall the file selection
270                 startExport();
271         }
272
273
274         /**
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
284          */
285         public static int exportData(OutputStreamWriter inWriter, TrackInfo inInfo, String inName,
286                 String inDesc, boolean[] inSaveFlags, boolean inUseCopy) throws IOException
287         {
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));
293                 // Name field
294                 String trackName = "PruneTrack";
295                 if (inName != null && !inName.equals(""))
296                 {
297                         trackName = inName;
298                         inWriter.write("\t<name>");
299                         inWriter.write(trackName);
300                         inWriter.write("</name>\n");
301                 }
302                 // Description field
303                 inWriter.write("\t<desc>");
304                 if (inDesc != null && !inDesc.equals("")) {
305                         inWriter.write(inDesc);
306                 }
307                 else {
308                         inWriter.write("Export from Prune");
309                 }
310                 inWriter.write("</desc>\n");
311
312                 int i = 0;
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];
320                 // Examine selection
321                 int selStart = -1, selEnd = -1;
322                 if (exportSelection) {
323                         selStart = inInfo.getSelection().getStart();
324                         selEnd = inInfo.getSelection().getEnd();
325                 }
326                 // Loop over waypoints
327                 final int numPoints = inInfo.getTrack().getNumPoints();
328                 int numSaved = 0;
329                 for (i=0; i<numPoints; i++)
330                 {
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()) {
335                                         if (exportWaypoints)
336                                         {
337                                                 String pointSource = (inUseCopy?gpxCachers.getSourceString(point):null);
338                                                 if (pointSource != null) {
339                                                         inWriter.write(pointSource);
340                                                         inWriter.write('\n');
341                                                 }
342                                                 else {
343                                                         exportWaypoint(point, inWriter, exportTimestamps);
344                                                 }
345                                                 numSaved++;
346                                         }
347                                 }
348                                 else {
349                                         hasTrackpoints = true;
350                                 }
351                         }
352                 }
353                 // Export both route points and then track points
354                 if (hasTrackpoints && (exportTrackpoints || exportPhotos))
355                 {
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");
364                 }
365
366                 inWriter.write("</gpx>\n");
367                 return numSaved;
368         }
369
370         /**
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
384          */
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)
390         throws IOException
391         {
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();
397                 int numSaved = 0;
398                 // Loop over track points
399                 for (int i=0; i<numPoints; i++)
400                 {
401                         DataPoint point = inInfo.getTrack().getPoint(i);
402                         if ((!inExportSelection || (i>=selStart && i<=selEnd)) && !point.isWaypoint())
403                         {
404                                 if ((point.getPhoto()==null && inExportTrackpoints) || (point.getPhoto()!=null && inExportPhotos))
405                                 {
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);
410                                         if (writePoint)
411                                         {
412                                                 // restart track segment if necessary
413                                                 if ((numSaved > 0) && point.getSegmentStart() && (inSegmentTag != null)) {
414                                                         inWriter.write(inSegmentTag);
415                                                 }
416                                                 if (numSaved == 0) {inWriter.write(inStartTag);}
417                                                 if (pointSource != null) {
418                                                         inWriter.write(pointSource);
419                                                         inWriter.write('\n');
420                                                 }
421                                                 else {
422                                                         if (!inOnlyCopies) {exportTrackpoint(point, inWriter, exportTimestamps);}
423                                                 }
424                                                 numSaved++;
425                                         }
426                                 }
427                         }
428                 }
429                 if (numSaved > 0) {inWriter.write(inEndTag);}
430                 return numSaved;
431         }
432
433         /**
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
437          */
438         private static String getHeaderString(GpxCacherList inCachers)
439         {
440                 String gpxHeader = null;
441                 if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
442                 if (gpxHeader == null || gpxHeader.length() < 5)
443                 {
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";
449                 }
450                 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + gpxHeader + "\n";
451         }
452
453         /**
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
459          */
460         private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
461                 throws IOException
462         {
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())
470                 {
471                         inWriter.write("\t\t<ele>");
472                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
473                         inWriter.write("</ele>\n");
474                 }
475                 // timestamp if available (point might have timestamp and then be turned into a waypoint)
476                 if (inPoint.hasTimestamp() && inTimestamps)
477                 {
478                         inWriter.write("\t\t<time>");
479                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
480                         inWriter.write("</time>\n");
481                 }
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);
488                 if (type != null)
489                 {
490                         type = type.trim();
491                         if (!type.equals(""))
492                         {
493                                 inWriter.write("\t\t<type>");
494                                 inWriter.write(type);
495                                 inWriter.write("</type>\n");
496                         }
497                 }
498                 inWriter.write("\t</wpt>\n");
499         }
500
501
502         /**
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
507          */
508         private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
509                 throws IOException
510         {
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("\">");
516                 // altitude
517                 if (inPoint.hasAltitude())
518                 {
519                         inWriter.write("<ele>");
520                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
521                         inWriter.write("</ele>");
522                 }
523                 // timestamp if available (and selected)
524                 if (inPoint.hasTimestamp() && inTimestamps)
525                 {
526                         inWriter.write("<time>");
527                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
528                         inWriter.write("</time>");
529                 }
530                 inWriter.write("</trkpt>\n");
531         }
532 }