]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/GpxExporter.java
Version 7, February 2009
[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.BoxLayout;
17 import javax.swing.JButton;
18 import javax.swing.JCheckBox;
19 import javax.swing.JDialog;
20 import javax.swing.JFileChooser;
21 import javax.swing.JLabel;
22 import javax.swing.JOptionPane;
23 import javax.swing.JPanel;
24 import javax.swing.JTextField;
25
26 import tim.prune.App;
27 import tim.prune.Config;
28 import tim.prune.GenericFunction;
29 import tim.prune.GpsPruner;
30 import tim.prune.I18nManager;
31 import tim.prune.UpdateMessageBroker;
32 import tim.prune.data.Altitude;
33 import tim.prune.data.Coordinate;
34 import tim.prune.data.DataPoint;
35 import tim.prune.data.Timestamp;
36 import tim.prune.data.Track;
37 import tim.prune.load.GenericFileFilter;
38
39 /**
40  * Class to export track information
41  * into a specified Gpx file
42  */
43 public class GpxExporter extends GenericFunction implements Runnable
44 {
45         private Track _track = null;
46         private JDialog _dialog = null;
47         private JTextField _nameField = null;
48         private JTextField _descriptionField = null;
49         private JCheckBox _timestampsCheckbox = null;
50         private JFileChooser _fileChooser = null;
51         private File _exportFile = null;
52
53         /** version number of Gpx */
54         private static final String GPX_VERSION_NUMBER = "1.0";
55         /** this program name */
56         private static final String GPX_CREATOR = "Prune v" + GpsPruner.VERSION_NUMBER + " activityworkshop.net";
57
58
59         /**
60          * Constructor
61          * @param inApp app object
62          */
63         public GpxExporter(App inApp)
64         {
65                 super(inApp);
66                 _track = inApp.getTrackInfo().getTrack();
67         }
68
69         /** Get name key */
70         public String getNameKey() {
71                 return "function.exportgpx";
72         }
73
74         /**
75          * Show the dialog to select options and export file
76          */
77         public void begin()
78         {
79                 // Make dialog window
80                 if (_dialog == null)
81                 {
82                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
83                         _dialog.setLocationRelativeTo(_parentFrame);
84                         _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
85                         _dialog.getContentPane().add(makeDialogComponents());
86                         _dialog.pack();
87                 }
88                 _dialog.setVisible(true);
89         }
90
91
92         /**
93          * Create dialog components
94          * @return Panel containing all gui elements in dialog
95          */
96         private Component makeDialogComponents()
97         {
98                 JPanel dialogPanel = new JPanel();
99                 dialogPanel.setLayout(new BorderLayout());
100                 JPanel mainPanel = new JPanel();
101                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
102                 // Make a central panel with the text boxes
103                 JPanel descPanel = new JPanel();
104                 descPanel.setLayout(new GridLayout(2, 2));
105                 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.name")));
106                 _nameField = new JTextField(10);
107                 descPanel.add(_nameField);
108                 descPanel.add(new JLabel(I18nManager.getText("dialog.exportgpx.desc")));
109                 _descriptionField = new JTextField(10);
110                 descPanel.add(_descriptionField);
111                 mainPanel.add(descPanel);
112                 // checkbox for timestamps
113                 _timestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportgpx.includetimestamps"));
114                 _timestampsCheckbox.setSelected(true);
115                 mainPanel.add(_timestampsCheckbox);
116                 dialogPanel.add(mainPanel, BorderLayout.CENTER);
117
118                 // button panel at bottom
119                 JPanel buttonPanel = new JPanel();
120                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
121                 JButton okButton = new JButton(I18nManager.getText("button.ok"));
122                 ActionListener okListener = new ActionListener() {
123                         public void actionPerformed(ActionEvent e)
124                         {
125                                 startExport();
126                         }
127                 };
128                 okButton.addActionListener(okListener);
129                 _descriptionField.addActionListener(okListener);
130                 buttonPanel.add(okButton);
131                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
132                 cancelButton.addActionListener(new ActionListener() {
133                         public void actionPerformed(ActionEvent e)
134                         {
135                                 _dialog.dispose();
136                         }
137                 });
138                 buttonPanel.add(cancelButton);
139                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
140                 dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
141                 return dialogPanel;
142         }
143
144
145         /**
146          * Start the export process based on the input parameters
147          */
148         private void startExport()
149         {
150                 // OK pressed, so choose output file
151                 if (_fileChooser == null)
152                 {
153                         _fileChooser = new JFileChooser();
154                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
155                         _fileChooser.setFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
156                         _fileChooser.setAcceptAllFileFilterUsed(false);
157                         // start from directory in config which should be set
158                         File configDir = Config.getWorkingDirectory();
159                         if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
160                 }
161                 // Allow choose again if an existing file is selected
162                 boolean chooseAgain = false;
163                 do
164                 {
165                         chooseAgain = false;
166                         if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
167                         {
168                                 // OK pressed and file chosen
169                                 File file = _fileChooser.getSelectedFile();
170                                 // Check file extension
171                                 if (!file.getName().toLowerCase().endsWith(".gpx"))
172                                 {
173                                         file = new File(file.getAbsolutePath() + ".gpx");
174                                 }
175                                 // Check if file exists and if necessary prompt for overwrite
176                                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
177                                 if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
178                                                 I18nManager.getText("dialog.save.overwrite.text"),
179                                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
180                                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
181                                         == JOptionPane.YES_OPTION)
182                                 {
183                                         // New file or overwrite confirmed, so initiate export in separate thread
184                                         _exportFile = file;
185                                         new Thread(this).start();
186                                 }
187                                 else
188                                 {
189                                         chooseAgain = true;
190                                 }
191                         }
192                 } while (chooseAgain);
193         }
194
195
196         /**
197          * Run method for controlling separate thread for exporting
198          */
199         public void run()
200         {
201                 OutputStreamWriter writer = null;
202                 try
203                 {
204                         // normal writing to file
205                         writer = new OutputStreamWriter(new FileOutputStream(_exportFile));
206                         // write file
207                         int numPoints = exportData(writer, _track, _nameField.getText(),
208                                 _descriptionField.getText(), _timestampsCheckbox.isSelected());
209
210                         // close file
211                         writer.close();
212                         // Store directory in config for later
213                         Config.setWorkingDirectory(_exportFile.getParentFile());
214                         // Show confirmation
215                         UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
216                                  + " " + numPoints + " " + I18nManager.getText("confirm.save.ok2")
217                                  + " " + _exportFile.getAbsolutePath());
218                         // export successful so need to close dialog and return
219                         _dialog.dispose();
220                         return;
221                 }
222                 catch (IOException ioe)
223                 {
224                         // System.out.println("Exception: " + ioe.getClass().getName() + " - " + ioe.getMessage());
225                         try {
226                                 if (writer != null) writer.close();
227                         }
228                         catch (IOException ioe2) {}
229                         JOptionPane.showMessageDialog(_parentFrame,
230                                 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
231                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
232                 }
233                 // if not returned already, export failed so need to recall the file selection
234                 startExport();
235         }
236
237
238         /**
239          * Export the information to the given writer
240          * @param inWriter writer object
241          * @param inTrack track object containing data
242          * @param inName name of track (optional)
243          * @param inDesc description of track (optional)
244          * @param inTimestamps true to export timestamps
245          * @return number of points written
246          * @throws IOException if io errors occur on write
247          */
248         public static int exportData(OutputStreamWriter inWriter, Track inTrack, String inName,
249                 String inDesc, boolean inTimestamps) throws IOException
250         {
251                 inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"");
252                 inWriter.write(GPX_VERSION_NUMBER);
253                 inWriter.write("\" creator=\"");
254                 inWriter.write(GPX_CREATOR);
255                 inWriter.write("\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
256                         + " xmlns=\"http://www.topografix.com/GPX/1/0\""
257                         + " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
258                 // Name field
259                 String trackName = "PruneTrack";
260                 if (inName != null && !inName.equals(""))
261                 {
262                         trackName = inName;
263                         inWriter.write("\t<name>");
264                         inWriter.write(trackName);
265                         inWriter.write("</name>\n");
266                 }
267                 // Description field
268                 inWriter.write("\t<desc>");
269                 if (inDesc != null && !inDesc.equals("")) {
270                         inWriter.write(inDesc);
271                 }
272                 else
273                 {
274                         inWriter.write("Export from Prune");
275                 }
276                 inWriter.write("</desc>\n");
277
278                 int i = 0;
279                 DataPoint point = null;
280                 boolean hasTrackpoints = false;
281                 // Loop over waypoints
282                 int numPoints = inTrack.getNumPoints();
283                 for (i=0; i<numPoints; i++)
284                 {
285                         point = inTrack.getPoint(i);
286                         // Make a wpt element for each waypoint
287                         if (point.isWaypoint())
288                         {
289                                 exportWaypoint(point, inWriter, inTimestamps);
290                         }
291                         else
292                         {
293                                 hasTrackpoints = true;
294                         }
295                 }
296                 // Output the track, if there is one
297                 if (hasTrackpoints)
298                 {
299                         boolean firstPoint = true;
300                         inWriter.write("\t<trk><name>" + trackName + "</name><number>1</number><trkseg>\n");
301                         // Loop over track points
302                         for (i=0; i<numPoints; i++)
303                         {
304                                 point = inTrack.getPoint(i);
305                                 // restart track segment if necessary
306                                 if (point.getSegmentStart() && !firstPoint) {
307                                         inWriter.write("\t</trkseg>\n\t<trkseg>\n");
308                                 }
309                                 if (!point.isWaypoint()) {
310                                         // export the track point
311                                         exportTrackpoint(point, inWriter, inTimestamps);
312                                         firstPoint = false;
313                                 }
314                         }
315                         inWriter.write("\t</trkseg></trk>\n");
316                 }
317                 inWriter.write("</gpx>\n");
318                 return numPoints;
319         }
320
321
322         /**
323          * Export the specified waypoint into the file
324          * @param inPoint waypoint to export
325          * @param inWriter writer object
326          * @param inTimestamps true to export timestamps too
327          * @throws IOException on write failure
328          */
329         private static void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
330                 throws IOException
331         {
332                 inWriter.write("\t<wpt lat=\"");
333                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
334                 inWriter.write("\" lon=\"");
335                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
336                 inWriter.write("\">\n");
337                 // altitude if available
338                 if (inPoint.hasAltitude())
339                 {
340                         inWriter.write("\t\t<ele>");
341                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
342                         inWriter.write("</ele>\n");
343                 }
344                 // write waypoint name after elevation
345                 inWriter.write("\t\t<name>");
346                 inWriter.write(inPoint.getWaypointName().trim());
347                 inWriter.write("</name>\n");
348                 // timestamp if available (point might have timestamp and then be turned into a waypoint)
349                 if (inPoint.hasTimestamp() && inTimestamps)
350                 {
351                         inWriter.write("\t\t<time>");
352                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
353                         inWriter.write("</time>\n");
354                 }
355                 // TODO: Include waypt type in Gpx
356                 inWriter.write("\t</wpt>\n");
357         }
358
359
360         /**
361          * Export the specified trackpoint into the file
362          * @param inPoint trackpoint to export
363          * @param inWriter writer object
364          * @param inTimestamps true to export timestamps too
365          */
366         private static void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inTimestamps)
367                 throws IOException
368         {
369                 inWriter.write("\t\t<trkpt lat=\"");
370                 inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
371                 inWriter.write("\" lon=\"");
372                 inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
373                 inWriter.write("\">");
374                 // altitude
375                 if (inPoint.hasAltitude())
376                 {
377                         inWriter.write("<ele>");
378                         inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
379                         inWriter.write("</ele>");
380                 }
381                 // timestamp if available (and selected)
382                 if (inPoint.hasTimestamp() && inTimestamps)
383                 {
384                         inWriter.write("<time>");
385                         inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
386                         inWriter.write("</time>");
387                 }
388                 inWriter.write("</trkpt>\n");
389         }
390 }