]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/load/JpegLoader.java
Version 2, March 2007
[GpsPrune.git] / tim / prune / load / JpegLoader.java
1 package tim.prune.load;
2
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.io.File;
6 import java.util.ArrayList;
7
8 import javax.swing.BorderFactory;
9 import javax.swing.BoxLayout;
10 import javax.swing.JButton;
11 import javax.swing.JCheckBox;
12 import javax.swing.JDialog;
13 import javax.swing.JFileChooser;
14 import javax.swing.JFrame;
15 import javax.swing.JLabel;
16 import javax.swing.JOptionPane;
17 import javax.swing.JPanel;
18 import javax.swing.JProgressBar;
19
20 import tim.prune.App;
21 import tim.prune.I18nManager;
22 import tim.prune.data.Altitude;
23 import tim.prune.data.DataPoint;
24 import tim.prune.data.Latitude;
25 import tim.prune.data.Longitude;
26 import tim.prune.data.Photo;
27 import tim.prune.drew.jpeg.ExifReader;
28 import tim.prune.drew.jpeg.JpegData;
29 import tim.prune.drew.jpeg.JpegException;
30 import tim.prune.drew.jpeg.Rational;
31
32 /**
33  * Class to manage the loading of Jpegs and dealing with the GPS data from them
34  */
35 public class JpegLoader implements Runnable
36 {
37         private App _app = null;
38         private JFrame _parentFrame = null;
39         private JFileChooser _fileChooser = null;
40         private JCheckBox _subdirCheckbox = null;
41         private JDialog _progressDialog   = null;
42         private JProgressBar _progressBar = null;
43         private int[] _fileCounts = null;
44         private boolean _cancelled = false;
45         private ArrayList _photos = null;
46
47
48         /**
49          * Constructor
50          * @param inApp Application object to inform of photo load
51          * @param inParentFrame parent frame to reference for dialogs
52          */
53         public JpegLoader(App inApp, JFrame inParentFrame)
54         {
55                 _app = inApp;
56                 _parentFrame = inParentFrame;
57         }
58
59         /**
60          * Select an input file and open the GUI frame
61          * to select load options
62          */
63         public void openFile()
64         {
65                 if (_fileChooser == null)
66                 {
67                         _fileChooser = new JFileChooser();
68                         _fileChooser.setMultiSelectionEnabled(true);
69                         _fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
70                         _subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories"));
71                         _subdirCheckbox.setSelected(true);
72                         _fileChooser.setAccessory(_subdirCheckbox);
73                 }
74                 if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
75                 {
76                         // Bring up dialog before starting
77                         showDialog();
78                         new Thread(this).start();
79                 }
80         }
81
82
83         /**
84          * Show the main dialog
85          */
86         private void showDialog()
87         {
88                 _progressDialog = new JDialog(_parentFrame, I18nManager.getText("dialog.jpegload.progress.title"));
89                 _progressDialog.setLocationRelativeTo(_parentFrame);
90                 _progressBar = new JProgressBar(0, 100);
91                 _progressBar.setValue(0);
92                 _progressBar.setStringPainted(true);
93                 _progressBar.setString("");
94                 JPanel panel = new JPanel();
95                 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
96                 panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
97                 panel.add(new JLabel(I18nManager.getText("dialog.jpegload.progress")));
98                 panel.add(_progressBar);
99                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
100                 cancelButton.addActionListener(new ActionListener() {
101                         public void actionPerformed(ActionEvent e)
102                         {
103                                 _cancelled = true;
104                         }
105                 });
106                 panel.add(cancelButton);
107                 _progressDialog.getContentPane().add(panel);
108                 _progressDialog.pack();
109                 _progressDialog.show();
110         }
111
112
113         /**
114          * Run method for performing tasks in separate thread
115          */
116         public void run()
117         {
118                 // Initialise arrays, errors, summaries
119                 _fileCounts = new int[4]; // files, jpegs, exifs, gps
120                 _photos = new ArrayList();
121                 // Loop over selected files/directories
122                 File[] files = _fileChooser.getSelectedFiles();
123                 int numFiles = countFileList(files, true, _subdirCheckbox.isSelected());
124                 // if (false) System.out.println("Found " + numFiles + " files");
125                 _progressBar.setMaximum(numFiles);
126                 _progressBar.setValue(0);
127                 _cancelled = false;
128                 processFileList(files, true, _subdirCheckbox.isSelected());
129                 _progressDialog.hide();
130                 if (_cancelled) return;
131                 // System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1] + ", " + _fileCounts[2] + ", " + _fileCounts[3]);
132                 if (_fileCounts[0] == 0)
133                 {
134                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nofilesfound"),
135                                 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
136                 }
137                 else if (_fileCounts[1] == 0)
138                 {
139                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nojpegsfound"),
140                                 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
141                 }
142                 else if (_fileCounts[2] == 0)
143                 {
144                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.noexiffound"),
145                                 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
146                 }
147                 else if (_fileCounts[3] == 0)
148                 {
149                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nogpsfound"),
150                                 I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE);
151                 }
152                 else
153                 {
154                         // Load information into dialog for confirmation
155                         _app.informPhotosLoaded(_photos);
156                 }
157         }
158
159
160         /**
161          * Process a list of files and/or directories
162          * @param inFiles array of file/directories
163          * @param inFirstDir true if first directory
164          * @param inDescend true to descend to subdirectories
165          */
166         private void processFileList(File[] inFiles, boolean inFirstDir, boolean inDescend)
167         {
168                 if (inFiles != null)
169                 {
170                         // Loop over elements in array
171                         for (int i=0; i<inFiles.length; i++)
172                         {
173                                 File file = inFiles[i];
174                                 if (file.exists() && file.canRead())
175                                 {
176                                         // Check whether it's a file or a directory
177                                         if (file.isFile())
178                                         {
179                                                 processFile(file);
180                                         }
181                                         else if (file.isDirectory() && (inFirstDir || inDescend))
182                                         {
183                                                 // Always process first directory,
184                                                 // only process subdirectories if checkbox selected
185                                                 processDirectory(file, inDescend);
186                                         }
187                                 }
188                                 else
189                                 {
190                                         // file doesn't exist or isn't readable - record error
191                                 }
192                                 // check for cancel
193                                 if (_cancelled) break;
194                         }
195                 }
196         }
197
198
199         /**
200          * Process the given file, by attempting to extract its tags
201          * @param inFile file object to read
202          */
203         private void processFile(File inFile)
204         {
205                 _fileCounts[0]++; // file found
206                 _progressBar.setValue(_fileCounts[0]);
207                 _progressBar.setString("" + _fileCounts[0] + " / " + _progressBar.getMaximum());
208                 _progressBar.repaint();
209                 try
210                 {
211                         JpegData jpegData = new ExifReader(inFile).extract();
212                         _fileCounts[1]++; // jpeg found (no exception thrown)
213 //                      if (jpegData.getNumErrors() > 0)
214 //                              System.out.println("Number of errors was: " + jpegData.getNumErrors() + ": " + jpegData.getErrors().get(0));
215                         if (jpegData.getExifDataPresent())
216                                 _fileCounts[2]++; // exif found
217                         if (jpegData.isValid())
218                         {
219 //                              if (false && jpegData.getTimestamp() != null)
220 //                                      System.out.println("Timestamp is " + jpegData.getTimestamp()[0].toString() + ":" + jpegData.getTimestamp()[1].toString() + ":" + jpegData.getTimestamp()[2].toString());
221 //                              if (false && jpegData.getDatestamp() != null)
222 //                                      System.out.println("Datestamp is " + jpegData.getDatestamp()[0].toString() + ":" + jpegData.getDatestamp()[1].toString() + ":" + jpegData.getDatestamp()[2].toString());
223                                 // Make DataPoint and Photo
224                                 DataPoint point = createDataPoint(jpegData);
225                                 Photo photo = new Photo(inFile);
226                                 point.setPhoto(photo);
227                                 photo.setDataPoint(point);
228                                 _photos.add(photo);
229 //                              System.out.println("Made photo: " + photo.getFile().getAbsolutePath() + " with the datapoint: "
230 //                                      + point.getLatitude().output(Latitude.FORMAT_DEG_MIN_SEC) + ", "
231 //                                      + point.getLongitude().output(Longitude.FORMAT_DEG_MIN_SEC) + ", "
232 //                                      + point.getAltitude().getValue(Altitude.FORMAT_METRES));
233                                 _fileCounts[3]++;
234                         }
235                 }
236                 catch (JpegException jpe) { // don't list errors, just count them
237                 }
238         }
239
240
241         /**
242          * Process the given directory, by looping over its contents
243          * and recursively through its subdirectories
244          * @param inDirectory directory to read
245          * @param inDescend true to descend subdirectories
246          */
247         private void processDirectory(File inDirectory, boolean inDescend)
248         {
249                 File[] files = inDirectory.listFiles();
250                 processFileList(files, false, inDescend);
251         }
252
253
254         /**
255          * Recursively count the selected Files so we can draw a progress bar
256          * @param inFiles file list
257          * @param inFirstDir true if first directory
258          * @param inDescend true to descend to subdirectories
259          * @return count of the files selected
260          */
261         private int countFileList(File[] inFiles, boolean inFirstDir, boolean inDescend)
262         {
263                 int fileCount = 0;
264                 if (inFiles != null)
265                 {
266                         // Loop over elements in array
267                         for (int i=0; i<inFiles.length; i++)
268                         {
269                                 File file = inFiles[i];
270                                 if (file.exists() && file.canRead())
271                                 {
272                                         // Check whether it's a file or a directory
273                                         if (file.isFile())
274                                         {
275                                                 fileCount++;
276                                         }
277                                         else if (file.isDirectory() && (inFirstDir || inDescend))
278                                         {
279                                                 fileCount += countFileList(file.listFiles(), false, inDescend);
280                                         }
281                                 }
282                         }
283                 }
284                 return fileCount;
285         }
286
287
288         /**
289          * Create a DataPoint object from the given jpeg data
290          * @param inData Jpeg data including coordinates
291          * @return DataPoint object for Track
292          */
293         private static DataPoint createDataPoint(JpegData inData)
294         {
295                 // Create model objects from jpeg data
296                 double latval = getCoordinateDoubleValue(inData.getLatitude(),
297                         inData.getLatitudeRef() == 'N' || inData.getLatitudeRef() == 'n');
298                 Latitude latitude = new Latitude(latval, Latitude.FORMAT_NONE);
299                 double lonval = getCoordinateDoubleValue(inData.getLongitude(),
300                         inData.getLongitudeRef() == 'E' || inData.getLongitudeRef() == 'e');
301                 Longitude longitude = new Longitude(lonval, Longitude.FORMAT_NONE);
302                 Altitude altitude = new Altitude(inData.getAltitude().intValue(), Altitude.FORMAT_METRES);
303                 return new DataPoint(latitude, longitude, altitude);
304         }
305
306
307         /**
308          * Convert an array of 3 Rational numbers into a double coordinate value
309          * @param inRationals array of three Rational objects
310          * @param isPositive true for positive hemisphere, for positive double value
311          * @return double value of coordinate, either positive or negative
312          */
313         private static double getCoordinateDoubleValue(Rational[] inRationals, boolean isPositive)
314         {
315                 if (inRationals == null || inRationals.length != 3) return 0.0;
316                 double value = inRationals[0].doubleValue()        // degrees
317                         + inRationals[1].doubleValue() / 60.0          // minutes
318                         + inRationals[2].doubleValue() / 60.0 / 60.0;  // seconds
319                 // make sure it's the correct sign
320                 value = Math.abs(value);
321                 if (!isPositive) value = -value;
322                 return value;
323         }
324 }