X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fload%2FJpegLoader.java;h=feec754f22f7049dc7bcef4b26d71cba534d2ed2;hb=140e9d165f85c3d4f0435a311e091209313faa2a;hp=6fe52a18a96e48be328a83f5bb0320cd4c463fce;hpb=23959e65a6a0d581e657b07186d18b7a1ac5afeb;p=GpsPrune.git diff --git a/tim/prune/load/JpegLoader.java b/tim/prune/load/JpegLoader.java index 6fe52a1..feec754 100644 --- a/tim/prune/load/JpegLoader.java +++ b/tim/prune/load/JpegLoader.java @@ -3,7 +3,7 @@ package tim.prune.load; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; -import java.util.ArrayList; +import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.BoxLayout; @@ -13,21 +13,22 @@ import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import tim.prune.App; import tim.prune.I18nManager; +import tim.prune.config.Config; import tim.prune.data.Altitude; import tim.prune.data.DataPoint; +import tim.prune.data.Field; +import tim.prune.data.LatLonRectangle; import tim.prune.data.Latitude; import tim.prune.data.Longitude; import tim.prune.data.Photo; -import tim.prune.drew.jpeg.ExifReader; -import tim.prune.drew.jpeg.JpegData; -import tim.prune.drew.jpeg.JpegException; -import tim.prune.drew.jpeg.Rational; +import tim.prune.data.Timestamp; +import tim.prune.jpeg.ExifGateway; +import tim.prune.jpeg.JpegData; /** * Class to manage the loading of Jpegs and dealing with the GPS data from them @@ -37,12 +38,16 @@ public class JpegLoader implements Runnable private App _app = null; private JFrame _parentFrame = null; private JFileChooser _fileChooser = null; + private GenericFileFilter _fileFilter = null; private JCheckBox _subdirCheckbox = null; + private JCheckBox _noExifCheckbox = null; + private JCheckBox _outsideAreaCheckbox = null; private JDialog _progressDialog = null; private JProgressBar _progressBar = null; private int[] _fileCounts = null; private boolean _cancelled = false; - private ArrayList _photos = null; + private LatLonRectangle _trackRectangle = null; + private TreeSet _photos = null; /** @@ -54,36 +59,66 @@ public class JpegLoader implements Runnable { _app = inApp; _parentFrame = inParentFrame; + String[] fileTypes = {"jpg", "jpe", "jpeg"}; + _fileFilter = new GenericFileFilter("filetype.jpeg", fileTypes); } + /** - * Select an input file and open the GUI frame - * to select load options + * Open the GUI to select options and start the load + * @param inRectangle track rectangle */ - public void openFile() + public void openDialog(LatLonRectangle inRectangle) { + // Create file chooser if necessary if (_fileChooser == null) { _fileChooser = new JFileChooser(); _fileChooser.setMultiSelectionEnabled(true); _fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + _fileChooser.setFileFilter(_fileFilter); + _fileChooser.setDialogTitle(I18nManager.getText("menu.file.addphotos")); _subdirCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.subdirectories")); _subdirCheckbox.setSelected(true); - _fileChooser.setAccessory(_subdirCheckbox); + _noExifCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegswithoutcoords")); + _noExifCheckbox.setSelected(true); + _outsideAreaCheckbox = new JCheckBox(I18nManager.getText("dialog.jpegload.loadjpegsoutsidearea")); + _outsideAreaCheckbox.setSelected(true); + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(_subdirCheckbox); + panel.add(_noExifCheckbox); + panel.add(_outsideAreaCheckbox); + _fileChooser.setAccessory(panel); + // start from directory in config if already set by other operations + String configDir = Config.getConfigString(Config.KEY_PHOTO_DIR); + if (configDir == null) {configDir = Config.getConfigString(Config.KEY_TRACK_DIR);} + if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} } + // enable/disable track checkbox + _trackRectangle = inRectangle; + _outsideAreaCheckbox.setEnabled(_trackRectangle != null && !_trackRectangle.isEmpty()); + // Show file dialog to choose file / directory(ies) if (_fileChooser.showOpenDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { // Bring up dialog before starting - showDialog(); + if (_progressDialog == null) { + createProgressDialog(); + } + // reset dialog and show it + _progressBar.setValue(0); + _progressBar.setString(""); + _progressDialog.setVisible(true); + // start thread for processing new Thread(this).start(); } } /** - * Show the main dialog + * Create the dialog to show the progress */ - private void showDialog() + private void createProgressDialog() { _progressDialog = new JDialog(_parentFrame, I18nManager.getText("dialog.jpegload.progress.title")); _progressDialog.setLocationRelativeTo(_parentFrame); @@ -106,7 +141,6 @@ public class JpegLoader implements Runnable panel.add(cancelButton); _progressDialog.getContentPane().add(panel); _progressDialog.pack(); - _progressDialog.show(); } @@ -117,41 +151,46 @@ public class JpegLoader implements Runnable { // Initialise arrays, errors, summaries _fileCounts = new int[4]; // files, jpegs, exifs, gps - _photos = new ArrayList(); - // Loop over selected files/directories + _photos = new TreeSet(new PhotoSorter()); File[] files = _fileChooser.getSelectedFiles(); + // Loop recursively over selected files/directories to count files int numFiles = countFileList(files, true, _subdirCheckbox.isSelected()); - // if (false) System.out.println("Found " + numFiles + " files"); + // Set up the progress bar for this number of files _progressBar.setMaximum(numFiles); _progressBar.setValue(0); _cancelled = false; + + // Process the files recursively and build lists of photos processFileList(files, true, _subdirCheckbox.isSelected()); - _progressDialog.hide(); - if (_cancelled) return; - // System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1] + ", " + _fileCounts[2] + ", " + _fileCounts[3]); + _progressDialog.setVisible(false); + _progressDialog.dispose(); // Sometimes dialog doesn't disappear without this dispose + if (_cancelled) {return;} + + //System.out.println("Finished - counts are: " + _fileCounts[0] + ", " + _fileCounts[1] + // + ", " + _fileCounts[2] + ", " + _fileCounts[3]); if (_fileCounts[0] == 0) { - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nofilesfound"), - I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE); + // No files found at all + _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.nofilesfound"); } else if (_fileCounts[1] == 0) { - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nojpegsfound"), - I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE); + // No jpegs found + _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.nojpegsfound"); } - else if (_fileCounts[2] == 0) + else if (!_noExifCheckbox.isSelected() && _fileCounts[2] == 0) { - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.noexiffound"), - I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE); + // Need coordinates but no exif found + _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.noexiffound"); } - else if (_fileCounts[3] == 0) + else if (!_noExifCheckbox.isSelected() && _fileCounts[3] == 0) { - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.jpegload.nogpsfound"), - I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.ERROR_MESSAGE); + // Need coordinates but no gps information found + _app.showErrorMessage("error.jpegload.dialogtitle", "error.jpegload.nogpsfound"); } else { - // Load information into dialog for confirmation + // Found some photos to load - pass information back to app _app.informPhotosLoaded(_photos); } } @@ -168,7 +207,7 @@ public class JpegLoader implements Runnable if (inFiles != null) { // Loop over elements in array - for (int i=0; i 0) -// System.out.println("Number of errors was: " + jpegData.getNumErrors() + ": " + jpegData.getErrors().get(0)); if (jpegData.getExifDataPresent()) - _fileCounts[2]++; // exif found - if (jpegData.isValid()) + {_fileCounts[2]++;} // exif found + if (jpegData.isGpsValid()) { -// if (false && jpegData.getTimestamp() != null) -// System.out.println("Timestamp is " + jpegData.getTimestamp()[0].toString() + ":" + jpegData.getTimestamp()[1].toString() + ":" + jpegData.getTimestamp()[2].toString()); -// if (false && jpegData.getDatestamp() != null) -// System.out.println("Datestamp is " + jpegData.getDatestamp()[0].toString() + ":" + jpegData.getDatestamp()[1].toString() + ":" + jpegData.getDatestamp()[2].toString()); - // Make DataPoint and Photo + timestamp = createTimestamp(jpegData.getGpsDatestamp(), jpegData.getGpsTimestamp()); + // Make DataPoint and attach to Photo DataPoint point = createDataPoint(jpegData); - Photo photo = new Photo(inFile); point.setPhoto(photo); + point.setSegmentStart(true); photo.setDataPoint(point); - _photos.add(photo); -// System.out.println("Made photo: " + photo.getFile().getAbsolutePath() + " with the datapoint: " -// + point.getLatitude().output(Latitude.FORMAT_DEG_MIN_SEC) + ", " -// + point.getLongitude().output(Longitude.FORMAT_DEG_MIN_SEC) + ", " -// + point.getAltitude().getValue(Altitude.FORMAT_METRES)); + photo.setOriginalStatus(Photo.Status.TAGGED); _fileCounts[3]++; } + // Use exif timestamp if gps timestamp not available + if (timestamp == null && jpegData.getOriginalTimestamp() != null) { + timestamp = createTimestamp(jpegData.getOriginalTimestamp()); + } + if (timestamp == null && jpegData.getDigitizedTimestamp() != null) { + timestamp = createTimestamp(jpegData.getDigitizedTimestamp()); + } + photo.setExifThumbnail(jpegData.getThumbnailImage()); + // Also extract orientation tag for setting rotation state of photo + photo.setRotation(jpegData.getRequiredRotation()); } - catch (JpegException jpe) { // don't list errors, just count them + // Use file timestamp if exif timestamp isn't available + if (timestamp == null) { + timestamp = new Timestamp(inFile.lastModified()); + } + // Apply timestamp to photo and its point (if any) + photo.setTimestamp(timestamp); + if (photo.getDataPoint() != null) { + photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.FORMAT_ISO_8601), false); + } + // Check the criteria for adding the photo - check whether the photo has coordinates and if so if they're within the rectangle + if ( (photo.getDataPoint() != null || _noExifCheckbox.isSelected()) + && (photo.getDataPoint() == null || !_outsideAreaCheckbox.isEnabled() + || _outsideAreaCheckbox.isSelected() || _trackRectangle.containsPoint(photo.getDataPoint()))) + { + _photos.add(photo); } - } - - - /** - * Process the given directory, by looping over its contents - * and recursively through its subdirectories - * @param inDirectory directory to read - * @param inDescend true to descend subdirectories - */ - private void processDirectory(File inDirectory, boolean inDescend) - { - File[] files = inDirectory.listFiles(); - processFileList(files, false, inDescend); } @@ -269,6 +318,11 @@ public class JpegLoader implements Runnable File file = inFiles[i]; if (file.exists() && file.canRead()) { + // Store first directory in config for later + if (i == 0 && inFirstDir) { + File workingDir = file.isDirectory()?file:file.getParentFile(); + Config.setConfigString(Config.KEY_PHOTO_DIR, workingDir.getAbsolutePath()); + } // Check whether it's a file or a directory if (file.isFile()) { @@ -295,30 +349,71 @@ public class JpegLoader implements Runnable // Create model objects from jpeg data double latval = getCoordinateDoubleValue(inData.getLatitude(), inData.getLatitudeRef() == 'N' || inData.getLatitudeRef() == 'n'); - Latitude latitude = new Latitude(latval, Latitude.FORMAT_NONE); + Latitude latitude = new Latitude(latval, Latitude.FORMAT_DEG_MIN_SEC); double lonval = getCoordinateDoubleValue(inData.getLongitude(), inData.getLongitudeRef() == 'E' || inData.getLongitudeRef() == 'e'); - Longitude longitude = new Longitude(lonval, Longitude.FORMAT_NONE); - Altitude altitude = new Altitude(inData.getAltitude().intValue(), Altitude.FORMAT_METRES); + Longitude longitude = new Longitude(lonval, Longitude.FORMAT_DEG_MIN_SEC); + Altitude altitude = null; + if (inData.hasAltitude()) { + altitude = new Altitude(inData.getAltitude(), Altitude.Format.METRES); + } return new DataPoint(latitude, longitude, altitude); } /** - * Convert an array of 3 Rational numbers into a double coordinate value - * @param inRationals array of three Rational objects + * Convert an array of 3 doubles (deg-min-sec) into a double coordinate value + * @param inValues array of three doubles for deg-min-sec * @param isPositive true for positive hemisphere, for positive double value * @return double value of coordinate, either positive or negative */ - private static double getCoordinateDoubleValue(Rational[] inRationals, boolean isPositive) + private static double getCoordinateDoubleValue(double[] inValues, boolean isPositive) { - if (inRationals == null || inRationals.length != 3) return 0.0; - double value = inRationals[0].doubleValue() // degrees - + inRationals[1].doubleValue() / 60.0 // minutes - + inRationals[2].doubleValue() / 60.0 / 60.0; // seconds + if (inValues == null || inValues.length != 3) return 0.0; + double value = inValues[0] // degrees + + inValues[1] / 60.0 // minutes + + inValues[2] / 60.0 / 60.0; // seconds // make sure it's the correct sign value = Math.abs(value); if (!isPositive) value = -value; return value; } + + + /** + * Use the given int values to create a timestamp + * @param inDate ints describing date + * @param inTime ints describing time + * @return Timestamp object corresponding to inputs + */ + private static Timestamp createTimestamp(int[] inDate, int[] inTime) + { + if (inDate == null || inTime == null || inDate.length != 3 || inTime.length != 3) { + return null; + } + return new Timestamp(inDate[0], inDate[1], inDate[2], + inTime[0], inTime[1], inTime[2]); + } + + + /** + * Use the given String value to create a timestamp + * @param inStamp timestamp from exif + * @return Timestamp object corresponding to input + */ + private static Timestamp createTimestamp(String inStamp) + { + Timestamp stamp = null; + try + { + stamp = new Timestamp(Integer.parseInt(inStamp.substring(0, 4)), + Integer.parseInt(inStamp.substring(5, 7)), + Integer.parseInt(inStamp.substring(8, 10)), + Integer.parseInt(inStamp.substring(11, 13)), + Integer.parseInt(inStamp.substring(14, 16)), + Integer.parseInt(inStamp.substring(17))); + } + catch (NumberFormatException nfe) {} + return stamp; + } }