Changeset 17548 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2021-02-28T17:08:41+01:00 (4 years ago)
Author:
Don-vip
Message:

fix #20341 - Support metadata from more image formats (patch by Bjoeni)

Location:
trunk/src/org/openstreetmap/josm
Files:
1 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java

    r16643 r17548  
    2323import org.openstreetmap.josm.gui.io.importexport.GeoJSONImporter;
    2424import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
    25 import org.openstreetmap.josm.gui.io.importexport.JpgImporter;
     25import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
    2626import org.openstreetmap.josm.gui.io.importexport.NMEAImporter;
    2727import org.openstreetmap.josm.gui.io.importexport.NoteImporter;
     
    7070                RtkLibImporter.class,
    7171                NoteImporter.class,
    72                 JpgImporter.class,
     72                ImageImporter.class,
    7373                WMSLayerImporter.class,
    7474                AllFormatsImporter.class,
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java

    r17459 r17548  
    22package org.openstreetmap.josm.data.gpx;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import java.io.File;
    5 import java.io.IOException;
    67import java.util.Date;
    78import java.util.List;
     
    1819
    1920import com.drew.imaging.jpeg.JpegMetadataReader;
    20 import com.drew.lang.CompoundException;
     21import com.drew.imaging.png.PngMetadataReader;
     22import com.drew.imaging.tiff.TiffMetadataReader;
    2123import com.drew.metadata.Directory;
    2224import com.drew.metadata.Metadata;
     
    582584        }
    583585
     586        String fn = file.getName();
     587
    584588        try {
    585             metadata = JpegMetadataReader.readMetadata(file);
    586         } catch (CompoundException | IOException ex) {
    587             Logging.error(ex);
    588             setExifTime(null);
    589             setExifCoor(null);
    590             setPos(null);
    591             return;
     589            // try to parse metadata according to extension
     590            String ext = fn.substring(fn.lastIndexOf(".") + 1).toLowerCase();
     591            switch (ext) {
     592            case "jpg":
     593            case "jpeg":
     594                metadata = JpegMetadataReader.readMetadata(file);
     595                break;
     596            case "tif":
     597            case "tiff":
     598                metadata = TiffMetadataReader.readMetadata(file);
     599                break;
     600            case "png":
     601                metadata = PngMetadataReader.readMetadata(file);
     602                break;
     603            default:
     604                throw new NoMetadataReaderWarning(ext);
     605            }
     606        } catch (Exception topException) {
     607            //try other formats (e.g. JPEG file with .png extension)
     608            try {
     609                metadata = JpegMetadataReader.readMetadata(file);
     610            } catch (Exception ex1) {
     611                try {
     612                    metadata = TiffMetadataReader.readMetadata(file);
     613                } catch (Exception ex2) {
     614                    try {
     615                        metadata = PngMetadataReader.readMetadata(file);
     616                    } catch (Exception ex3) {
     617
     618                        Logging.warn(topException);
     619                        Logging.info(tr("Can''t parse metadata for file \"{0}\". Using last modified date as timestamp.", fn));
     620                        setExifTime(new Date(file.lastModified()));
     621                        setExifCoor(null);
     622                        setPos(null);
     623                        return;
     624                    }
     625                }
     626            }
    592627        }
    593628
    594629        // Changed to silently cope with no time info in exif. One case
    595630        // of person having time that couldn't be parsed, but valid GPS info
     631        Date time = null;
    596632        try {
    597             setExifTime(ExifReader.readTime(metadata));
     633            time = ExifReader.readTime(metadata);
    598634        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
    599635            Logging.warn(ex);
    600             setExifTime(null);
    601         }
     636        }
     637
     638        if (time == null) {
     639            Logging.info(tr("No EXIF time in file \"{0}\". Using last modified date as timestamp.", fn));
     640            time = new Date(file.lastModified()); //use lastModified time if no EXIF time present
     641        }
     642        setExifTime(time);
    602643
    603644        final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
     
    623664        }
    624665
    625         if (dirGps == null) {
     666        if (dirGps == null || dirGps.getTagCount() <= 1) {
    626667            setExifCoor(null);
    627668            setPos(null);
     
    658699    }
    659700
     701    private static class NoMetadataReaderWarning extends Exception {
     702        NoMetadataReaderWarning(String ext) {
     703            super("No metadata reader for format *." + ext);
     704        }
     705    }
     706
    660707    private static <T> void ifNotNull(T value, Consumer<T> setter) {
    661708        if (value != null) {
  • trunk/src/org/openstreetmap/josm/gui/io/importexport/JpgImporter.java

    r17534 r17548  
    22package org.openstreetmap.josm.gui.io.importexport;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
    6 import java.io.File;
    7 import java.io.IOException;
    8 import java.util.ArrayList;
    9 import java.util.Arrays;
    10 import java.util.EnumSet;
    11 import java.util.HashSet;
    12 import java.util.List;
    13 import java.util.Set;
    14 import java.util.regex.Matcher;
    15 import java.util.regex.Pattern;
    16 
    17 import org.openstreetmap.josm.actions.ExtensionFileFilter;
    18 import org.openstreetmap.josm.gui.layer.GpxLayer;
    19 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
    20 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    21 import org.openstreetmap.josm.io.CachedFile;
    22 import org.openstreetmap.josm.io.IllegalDataException;
    23 
    244/**
    25  * File importer allowing to import geotagged images (*.jpg files).
    26  *
    27  */
    28 public class JpgImporter extends FileImporter {
    29     /** Check if the filename starts with a borked path ({@link java.io.File#File} drops consecutive {@code /} characters). */
    30     private static final Pattern URL_START_BAD = Pattern.compile("^(https?:/)([^/].*)$");
    31     /** Check for the beginning of a "good" url */
    32     private static final Pattern URL_START_GOOD = Pattern.compile("^https?://.*$");
    33 
    34     private GpxLayer gpx;
     5*  File importer allowing to import geotagged images
     6*  @deprecated use {@link ImageImporter} instead
     7*/
     8@Deprecated
     9public class JpgImporter extends ImageImporter {
    3510
    3611    /**
    37      * The default file filter (only *.jpg files).
    38      */
    39     public static final ExtensionFileFilter FILE_FILTER = new ExtensionFileFilter(
    40             "jpg,jpeg", "jpg", tr("Image Files") + " (*.jpg)");
    41 
    42     /**
    43      * An alternate file filter that also includes folders.
    44      * @since 5438
    45      */
    46     public static final ExtensionFileFilter FILE_FILTER_WITH_FOLDERS = new ExtensionFileFilter(
    47             "jpg,jpeg", "jpg", tr("Image Files") + " (*.jpg, "+ tr("folder")+')');
    48 
    49     /**
    50      * Constructs a new {@code JpgImporter}.
    51      */
     12    * Constructs a new {@code JpgImporter}.
     13    */
    5214    public JpgImporter() {
    53         this(false);
     15        super(false);
    5416    }
    5517
    5618    /**
    57      * Constructs a new {@code JpgImporter} with folders selection, if wanted.
    58      * @param includeFolders If true, includes folders in the file filter
    59      * @since 5438
    60      */
     19    * Constructs a new {@code JpgImporter} with folders selection, if wanted.
     20    * @param includeFolders If true, includes folders in the file filter
     21    * @since 5438
     22    */
    6123    public JpgImporter(boolean includeFolders) {
    62         super(includeFolders ? FILE_FILTER_WITH_FOLDERS : FILE_FILTER);
    63     }
    64 
    65     /**
    66      * Constructs a new {@code JpgImporter} for the given GPX layer. Folders selection is allowed.
    67      * @param gpx The GPX layer
    68      */
    69     public JpgImporter(GpxLayer gpx) {
    70         this(true);
    71         this.gpx = gpx;
    72     }
    73 
    74     @Override
    75     public boolean acceptFile(File pathname) {
    76         return super.acceptFile(pathname) || pathname.isDirectory();
    77     }
    78 
    79     @Override
    80     public void importData(List<File> sel, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
    81         progressMonitor.beginTask(tr("Looking for image files"), 1);
    82         try {
    83             List<File> files = new ArrayList<>();
    84             Set<String> visitedDirs = new HashSet<>();
    85             addRecursiveFiles(this.options, files, visitedDirs, sel, progressMonitor.createSubTaskMonitor(1, true));
    86 
    87             if (progressMonitor.isCanceled())
    88                 return;
    89 
    90             if (files.isEmpty())
    91                 throw new IOException(tr("No image files found."));
    92 
    93             GeoImageLayer.create(files, gpx);
    94         } finally {
    95             progressMonitor.finishTask();
    96         }
    97     }
    98 
    99     static void addRecursiveFiles(List<File> files, Set<String> visitedDirs, List<File> sel, ProgressMonitor progressMonitor)
    100             throws IOException {
    101         addRecursiveFiles(EnumSet.noneOf(Options.class), files, visitedDirs, sel, progressMonitor);
    102     }
    103 
    104     static void addRecursiveFiles(Set<Options> options, List<File> files, Set<String> visitedDirs, List<File> sel,
    105             ProgressMonitor progressMonitor) throws IOException {
    106 
    107         if (progressMonitor.isCanceled())
    108             return;
    109 
    110         progressMonitor.beginTask(null, sel.size());
    111         try {
    112             for (File f : sel) {
    113                 if (f.isDirectory()) {
    114                     if (visitedDirs.add(f.getCanonicalPath())) { // Do not loop over symlinks
    115                         File[] dirFiles = f.listFiles(); // Can be null for some strange directories (like lost+found)
    116                         if (dirFiles != null) {
    117                             addRecursiveFiles(options, files, visitedDirs, Arrays.asList(dirFiles),
    118                                     progressMonitor.createSubTaskMonitor(1, true));
    119                         }
    120                     } else {
    121                         progressMonitor.worked(1);
    122                     }
    123                 } else {
    124                     /* Check if the path is a web path, and if so, ensure that it is "correct" */
    125                     final String path = f.getPath();
    126                     Matcher matcherBad = URL_START_BAD.matcher(path);
    127                     final String realPath;
    128                     if (matcherBad.matches()) {
    129                         realPath = matcherBad.replaceFirst(matcherBad.group(1) + "/" + matcherBad.group(2));
    130                     } else {
    131                         realPath = path;
    132                     }
    133                     if (URL_START_GOOD.matcher(realPath).matches() && FILE_FILTER.accept(f)
    134                             && options.contains(Options.ALLOW_WEB_RESOURCES)) {
    135                         try (CachedFile cachedFile = new CachedFile(realPath)) {
    136                             files.add(cachedFile.getFile());
    137                         }
    138                     } else if (FILE_FILTER.accept(f)) {
    139                         files.add(f);
    140                     }
    141                     progressMonitor.worked(1);
    142                 }
    143             }
    144         } finally {
    145             progressMonitor.finishTask();
    146         }
    147     }
    148 
    149     @Override
    150     public boolean isBatchImporter() {
    151         return true;
    152     }
    153 
    154     /**
    155      * Needs to be the last, to avoid problems.
    156      */
    157     @Override
    158     public double getPriority() {
    159         return -1000;
     24        super(includeFolders);
    16025    }
    16126}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

    r16438 r17548  
    7979import org.openstreetmap.josm.gui.MainApplication;
    8080import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
    81 import org.openstreetmap.josm.gui.io.importexport.JpgImporter;
     81import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
    8282import org.openstreetmap.josm.gui.io.importexport.NMEAImporter;
    8383import org.openstreetmap.josm.gui.io.importexport.RtkLibImporter;
     
    718718            openButton.addActionListener(ae -> {
    719719                AbstractFileChooser fc = DiskAccessAction.createAndOpenFileChooser(true, false, null,
    720                         JpgImporter.FILE_FILTER_WITH_FOLDERS, JFileChooser.FILES_ONLY, "geoimage.lastdirectory");
     720                        ImageImporter.FILE_FILTER_WITH_FOLDERS, JFileChooser.FILES_ONLY, "geoimage.lastdirectory");
    721721                if (fc == null)
    722722                    return;
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r17459 r17548  
    5656import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    5757import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
    58 import org.openstreetmap.josm.gui.io.importexport.JpgImporter;
     58import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
    5959import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    6060import org.openstreetmap.josm.gui.layer.GpxLayer;
     
    313313                    }
    314314
    315                     File[] children = f.listFiles(JpgImporter.FILE_FILTER_WITH_FOLDERS);
     315                    File[] children = f.listFiles(ImageImporter.FILE_FILTER_WITH_FOLDERS);
    316316                    if (children != null) {
    317317                        progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

    r16284 r17548  
    1414import java.awt.Rectangle;
    1515import java.awt.RenderingHints;
    16 import java.awt.Toolkit;
    1716import java.awt.event.MouseEvent;
    1817import java.awt.event.MouseListener;
     
    2524import java.awt.image.ImageObserver;
    2625import java.io.File;
    27 
     26import java.io.IOException;
     27
     28import javax.imageio.ImageIO;
    2829import javax.swing.JComponent;
    2930import javax.swing.SwingUtilities;
     
    244245            if (!(entry.getWidth() > 0 && entry.getHeight() > 0)) {
    245246                synchronized (entry) {
    246                     img.getWidth(this);
    247                     img.getHeight(this);
     247                    int width = img.getWidth(this);
     248                    int height = img.getHeight(this);
     249
     250                    if (!(entry.getWidth() > 0 && entry.getHeight() > 0) && width > 0 && height > 0) {
     251                        // dimensions not in metadata but already present in image, so observer won't be called
     252                        entry.setWidth(width);
     253                        entry.setHeight(height);
     254                        entry.notifyAll();
     255                    }
    248256
    249257                    long now = System.currentTimeMillis();
     
    280288        @Override
    281289        public void run() {
    282             Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
    283             if (!updateImageEntry(img))
    284                 return;
    285 
    286             int width = entry.getWidth();
    287             int height = entry.getHeight();
    288 
    289             if (mayFitMemory(((long) width)*height*4*2)) {
    290                 Logging.info(tr("Loading {0}", file.getPath()));
    291                 tracker.addImage(img, 1);
    292 
    293                 // Wait for the end of loading
    294                 while (!tracker.checkID(1, true)) {
     290            Image img;
     291            try {
     292                img = ImageIO.read(file);
     293
     294                if (!updateImageEntry(img))
     295                    return;
     296
     297                int width = entry.getWidth();
     298                int height = entry.getHeight();
     299
     300                if (mayFitMemory(((long) width)*height*4*2)) {
     301                    Logging.info(tr("Loading {0}", file.getPath()));
     302                    tracker.addImage(img, 1);
     303
     304                    // Wait for the end of loading
     305                    while (!tracker.checkID(1, true)) {
     306                        if (this.entry != ImageDisplay.this.entry) {
     307                            // The file has changed
     308                            tracker.removeImage(img);
     309                            return;
     310                        }
     311                        try {
     312                            Thread.sleep(5);
     313                        } catch (InterruptedException e) {
     314                            Logging.trace(e);
     315                            Logging.warn("InterruptedException in {0} while loading image {1}",
     316                                    getClass().getSimpleName(), file.getPath());
     317                            Thread.currentThread().interrupt();
     318                        }
     319                    }
     320                    if (tracker.isErrorID(1)) {
     321                        Logging.warn("Abort loading of {0} since tracker errored with 1", file);
     322                        // the tracker catches OutOfMemory conditions
     323                        tracker.removeImage(img);
     324                        img = null;
     325                    } else {
     326                        tracker.removeImage(img);
     327                    }
     328                } else {
     329                    Logging.warn("Abort loading of {0} since it might not fit into memory", file);
     330                    img = null;
     331                }
     332
     333                synchronized (ImageDisplay.this) {
    295334                    if (this.entry != ImageDisplay.this.entry) {
    296335                        // The file has changed
    297                         tracker.removeImage(img);
    298336                        return;
    299337                    }
    300                     try {
    301                         Thread.sleep(5);
    302                     } catch (InterruptedException e) {
    303                         Logging.trace(e);
    304                         Logging.warn("InterruptedException in {0} while loading image {1}",
    305                                 getClass().getSimpleName(), file.getPath());
    306                         Thread.currentThread().interrupt();
     338
     339                    if (img != null) {
     340                        boolean switchedDim = false;
     341                        if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) {
     342                            if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) {
     343                                width = img.getHeight(null);
     344                                height = img.getWidth(null);
     345                                switchedDim = true;
     346                            }
     347                            final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
     348                            final AffineTransform xform = ExifReader.getRestoreOrientationTransform(
     349                                    entry.getExifOrientation(),
     350                                    img.getWidth(null),
     351                                    img.getHeight(null));
     352                            final Graphics2D g = rot.createGraphics();
     353                            g.drawImage(img, xform, null);
     354                            g.dispose();
     355                            img = rot;
     356                        }
     357
     358                        ImageDisplay.this.image = img;
     359                        visibleRect = new VisRect(0, 0, width, height);
     360
     361                        Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}",
     362                                file.getPath(), width, height, width*height*4/1024/1024, switchedDim);
    307363                    }
    308                 }
    309                 if (tracker.isErrorID(1)) {
    310                     Logging.warn("Abort loading of {0} since tracker errored with 1", file);
    311                     // the tracker catches OutOfMemory conditions
    312                     tracker.removeImage(img);
    313                     img = null;
    314                 } else {
    315                     tracker.removeImage(img);
    316                 }
    317             } else {
    318                 Logging.warn("Abort loading of {0} since it might not fit into memory", file);
    319                 img = null;
    320             }
    321 
    322             synchronized (ImageDisplay.this) {
    323                 if (this.entry != ImageDisplay.this.entry) {
    324                     // The file has changed
    325                     return;
    326                 }
    327 
    328                 if (img != null) {
    329                     boolean switchedDim = false;
    330                     if (ExifReader.orientationNeedsCorrection(entry.getExifOrientation())) {
    331                         if (ExifReader.orientationSwitchesDimensions(entry.getExifOrientation())) {
    332                             width = img.getHeight(null);
    333                             height = img.getWidth(null);
    334                             switchedDim = true;
    335                         }
    336                         final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    337                         final AffineTransform xform = ExifReader.getRestoreOrientationTransform(
    338                                 entry.getExifOrientation(),
    339                                 img.getWidth(null),
    340                                 img.getHeight(null));
    341                         final Graphics2D g = rot.createGraphics();
    342                         g.drawImage(img, xform, null);
    343                         g.dispose();
    344                         img = rot;
    345                     }
    346 
    347                     ImageDisplay.this.image = img;
    348                     visibleRect = new VisRect(0, 0, width, height);
    349 
    350                     Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}",
    351                             file.getPath(), width, height, width*height*4/1024/1024, switchedDim);
    352                 }
    353 
    354                 selectedRect = null;
    355                 errorLoading = (img == null);
    356             }
    357             ImageDisplay.this.repaint();
     364
     365                    selectedRect = null;
     366                    errorLoading = (img == null);
     367                }
     368                ImageDisplay.this.repaint();
     369            } catch (IOException ex) {
     370                Logging.error(ex);
     371            }
    358372        }
    359373    }
  • trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportImagesAction.java

    r17333 r17548  
    1616import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    1717import org.openstreetmap.josm.gui.MainApplication;
    18 import org.openstreetmap.josm.gui.io.importexport.JpgImporter;
     18import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
    1919import org.openstreetmap.josm.gui.layer.GpxLayer;
    2020import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
     
    6969            return;
    7070        }
    71         JpgImporter importer = new JpgImporter(layer);
     71        ImageImporter importer = new ImageImporter(layer);
    7272        AbstractFileChooser fc = new FileChooserManager(true, "geoimage.lastdirectory", Config.getPref().get("lastDirectory")).
    7373                createFileChooser(true, null, importer.filter, JFileChooser.FILES_AND_DIRECTORIES).openFileChooser();
  • trunk/src/org/openstreetmap/josm/tools/ExifReader.java

    r15219 r17548  
    178178     */
    179179    public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException {
    180         if (dirGps != null) {
     180        if (dirGps != null && dirGps.getTagCount() > 1) {
    181181            double lat = readAxis(dirGps, GpsDirectory.TAG_LATITUDE, GpsDirectory.TAG_LATITUDE_REF, 'S');
    182182            double lon = readAxis(dirGps, GpsDirectory.TAG_LONGITUDE, GpsDirectory.TAG_LONGITUDE_REF, 'W');
Note: See TracChangeset for help on using the changeset viewer.