Ticket #12255: 12255-v3.patch
File 12255-v3.patch, 24.7 KB (added by , 10 years ago) |
---|
-
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java index c5f440b..f0564cc 100644
a b 21 21 import java.beans.PropertyChangeListener; 22 22 import java.io.File; 23 23 import java.io.IOException; 24 import java.text.ParseException;25 24 import java.util.ArrayList; 26 25 import java.util.Arrays; 27 import java.util.Calendar;28 26 import java.util.Collection; 29 27 import java.util.Collections; 30 import java.util.GregorianCalendar;31 28 import java.util.HashSet; 32 29 import java.util.LinkedHashSet; 33 30 import java.util.LinkedList; 34 31 import java.util.List; 35 32 import java.util.Set; 36 import java.util.TimeZone;37 33 import java.util.concurrent.ExecutorService; 38 34 import java.util.concurrent.Executors; 39 35 … … 49 45 import org.openstreetmap.josm.actions.mapmode.MapMode; 50 46 import org.openstreetmap.josm.actions.mapmode.SelectAction; 51 47 import org.openstreetmap.josm.data.Bounds; 52 import org.openstreetmap.josm.data.coor.LatLon;53 48 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 54 49 import org.openstreetmap.josm.gui.ExtendedDialog; 55 50 import org.openstreetmap.josm.gui.MapFrame; … … 67 62 import org.openstreetmap.josm.gui.layer.Layer; 68 63 import org.openstreetmap.josm.gui.util.GuiHelper; 69 64 import org.openstreetmap.josm.io.JpgImporter; 70 import org.openstreetmap.josm.tools.ExifReader;71 65 import org.openstreetmap.josm.tools.ImageProvider; 72 66 import org.openstreetmap.josm.tools.Utils; 73 67 74 import com.drew.imaging.jpeg.JpegMetadataReader;75 import com.drew.lang.CompoundException;76 import com.drew.metadata.Directory;77 import com.drew.metadata.Metadata;78 import com.drew.metadata.MetadataException;79 import com.drew.metadata.exif.ExifIFD0Directory;80 import com.drew.metadata.exif.GpsDirectory;81 82 68 /** 83 69 * Layer displaying geottaged pictures. 84 70 */ … … protected void realRun() throws IOException { 157 143 progressMonitor.subTask(tr("Reading {0}...", f.getName())); 158 144 progressMonitor.worked(1); 159 145 160 ImageEntry e = new ImageEntry(); 161 162 // Changed to silently cope with no time info in exif. One case 163 // of person having time that couldn't be parsed, but valid GPS info 164 165 try { 166 e.setExifTime(ExifReader.readTime(f)); 167 } catch (ParseException ex) { 168 e.setExifTime(null); 169 } 170 e.setFile(f); 171 extractExif(e); 146 ImageEntry e = new ImageEntry(f); 147 e.extractExif(); 172 148 data.add(e); 173 149 } 174 150 layer = new GeoImageLayer(data, gpxLayer); … … public void paint(Graphics2D g, MapView mv, Bounds bounds) { 498 474 continue; 499 475 } 500 476 Point p = mv.getPoint(e.getPos()); 501 if (e. thumbnail != null) {502 Dimension d = scaledDimension(e. thumbnail);477 if (e.hasThumbnail()) { 478 Dimension d = scaledDimension(e.getThumbnail()); 503 479 Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); 504 480 if (clip.intersects(target)) { 505 tempG.drawImage(e. thumbnail, target.x, target.y, target.width, target.height, null);481 tempG.drawImage(e.getThumbnail(), target.x, target.y, target.width, target.height, null); 506 482 } 507 483 } else { // thumbnail not loaded yet 508 484 icon.paintIcon(mv, tempG, … … public void paint(Graphics2D g, MapView mv, Bounds bounds) { 534 510 535 511 int imgWidth = 100; 536 512 int imgHeight = 100; 537 if (useThumbs && e. thumbnail != null) {538 Dimension d = scaledDimension(e. thumbnail);513 if (useThumbs && e.hasThumbnail()) { 514 Dimension d = scaledDimension(e.getThumbnail()); 539 515 imgWidth = d.width; 540 516 imgHeight = d.height; 541 517 } else { … … public void paint(Graphics2D g, MapView mv, Bounds bounds) { 573 549 g.drawPolyline(xar, yar, 3); 574 550 } 575 551 576 if (useThumbs && e. thumbnail != null) {552 if (useThumbs && e.hasThumbnail()) { 577 553 g.setColor(new Color(128, 0, 0, 122)); 578 554 g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight); 579 555 } else { … … public void visitBoundingBox(BoundingXYVisitor v) { 593 569 } 594 570 } 595 571 596 /**597 * Extract GPS metadata from image EXIF598 *599 * If successful, fills in the LatLon and EastNorth attributes of passed in image600 * @param e image entry601 */602 private static void extractExif(ImageEntry e) {603 604 Metadata metadata;605 Directory dirExif;606 GpsDirectory dirGps;607 608 try {609 metadata = JpegMetadataReader.readMetadata(e.getFile());610 dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);611 dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);612 } catch (CompoundException | IOException p) {613 e.setExifCoor(null);614 e.setPos(null);615 return;616 }617 618 try {619 if (dirExif != null) {620 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);621 e.setExifOrientation(orientation);622 }623 } catch (MetadataException ex) {624 Main.debug(ex.getMessage());625 }626 627 if (dirGps == null) {628 e.setExifCoor(null);629 e.setPos(null);630 return;631 }632 633 try {634 double speed = dirGps.getDouble(GpsDirectory.TAG_SPEED);635 String speedRef = dirGps.getString(GpsDirectory.TAG_SPEED_REF);636 if ("M".equalsIgnoreCase(speedRef)) {637 // miles per hour638 speed *= 1.609344;639 } else if ("N".equalsIgnoreCase(speedRef)) {640 // knots == nautical miles per hour641 speed *= 1.852;642 }643 // default is K (km/h)644 e.setSpeed(speed);645 } catch (Exception ex) {646 Main.debug(ex.getMessage());647 }648 649 try {650 double ele = dirGps.getDouble(GpsDirectory.TAG_ALTITUDE);651 int d = dirGps.getInt(GpsDirectory.TAG_ALTITUDE_REF);652 if (d == 1) {653 ele *= -1;654 }655 e.setElevation(ele);656 } catch (MetadataException ex) {657 Main.debug(ex.getMessage());658 }659 660 try {661 LatLon latlon = ExifReader.readLatLon(dirGps);662 e.setExifCoor(latlon);663 e.setPos(e.getExifCoor());664 665 } catch (Exception ex) { // (other exceptions, e.g. #5271)666 Main.error("Error reading EXIF from file: "+ex);667 e.setExifCoor(null);668 e.setPos(null);669 }670 671 try {672 Double direction = ExifReader.readDirection(dirGps);673 if (direction != null) {674 e.setExifImgDir(direction.doubleValue());675 }676 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)677 Main.debug(ex.getMessage());678 }679 680 // Time and date. We can have these cases:681 // 1) GPS_TIME_STAMP not set -> date/time will be null682 // 2) GPS_DATE_STAMP not set -> use EXIF date or set to default683 // 3) GPS_TIME_STAMP and GPS_DATE_STAMP are set684 int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_TIME_STAMP);685 if (timeStampComps != null) {686 int gpsHour = timeStampComps[0];687 int gpsMin = timeStampComps[1];688 int gpsSec = timeStampComps[2];689 Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));690 691 // We have the time. Next step is to check if the GPS date stamp is set.692 // dirGps.getString() always succeeds, but the return value might be null.693 String dateStampStr = dirGps.getString(GpsDirectory.TAG_DATE_STAMP);694 if (dateStampStr != null && dateStampStr.matches("^\\d+:\\d+:\\d+$")) {695 String[] dateStampComps = dateStampStr.split(":");696 cal.set(Calendar.YEAR, Integer.parseInt(dateStampComps[0]));697 cal.set(Calendar.MONTH, Integer.parseInt(dateStampComps[1]) - 1);698 cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStampComps[2]));699 } else {700 // No GPS date stamp in EXIF data. Copy it from EXIF time.701 // Date is not set if EXIF time is not available.702 if (e.hasExifTime()) {703 // Time not set yet, so we can copy everything, not just date.704 cal.setTime(e.getExifTime());705 }706 }707 708 cal.set(Calendar.HOUR_OF_DAY, gpsHour);709 cal.set(Calendar.MINUTE, gpsMin);710 cal.set(Calendar.SECOND, gpsSec);711 712 e.setExifGpsTime(cal.getTime());713 }714 }715 716 572 public void showNextPhoto() { 717 573 if (data != null && !data.isEmpty()) { 718 574 currentPhoto++; … … public ImageEntry getPhotoUnderMouse(MouseEvent evt) { 861 717 } 862 718 Point p = Main.map.mapView.getPoint(img.getPos()); 863 719 Rectangle r; 864 if (useThumbs && img. thumbnail != null) {865 Dimension d = scaledDimension(img. thumbnail);720 if (useThumbs && img.hasThumbnail()) { 721 Dimension d = scaledDimension(img.getThumbnail()); 866 722 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); 867 723 } else { 868 724 r = new Rectangle(p.x - icon.getIconWidth() / 2, … … public void mouseReleased(MouseEvent ev) { 971 827 } 972 828 Point p = Main.map.mapView.getPoint(e.getPos()); 973 829 Rectangle r; 974 if (useThumbs && e. thumbnail != null) {975 Dimension d = scaledDimension(e. thumbnail);830 if (useThumbs && e.hasThumbnail()) { 831 Dimension d = scaledDimension(e.getThumbnail()); 976 832 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); 977 833 } else { 978 834 r = new Rectangle(p.x - icon.getIconWidth() / 2, -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java index 29b4f22..2684288 100644
a b 3 3 4 4 import java.awt.Image; 5 5 import java.io.File; 6 import java.io.IOException; 7 import java.text.ParseException; 8 import java.util.Calendar; 6 9 import java.util.Date; 10 import java.util.GregorianCalendar; 11 import java.util.TimeZone; 7 12 13 import com.drew.imaging.jpeg.JpegMetadataReader; 14 import com.drew.lang.CompoundException; 15 import com.drew.metadata.Directory; 16 import com.drew.metadata.Metadata; 17 import com.drew.metadata.MetadataException; 18 import com.drew.metadata.exif.ExifIFD0Directory; 19 import com.drew.metadata.exif.GpsDirectory; 20 import org.openstreetmap.josm.Main; 21 import org.openstreetmap.josm.data.SystemOfMeasurement; 8 22 import org.openstreetmap.josm.data.coor.CachedLatLon; 9 23 import org.openstreetmap.josm.data.coor.LatLon; 24 import org.openstreetmap.josm.tools.ExifReader; 10 25 11 26 /** 12 27 * Stores info about each image … … 25 40 private boolean isNewGpsData; 26 41 /** Temporary source of GPS time if not correlated with GPX track. */ 27 42 private Date exifGpsTime; 28 Image thumbnail;43 private Image thumbnail; 29 44 30 45 /** 31 46 * The following values are computed from the correlation with the gpx track … … 43 58 * When the correlation dialog is open, we like to show the image position 44 59 * for the current time offset on the map in real time. 45 60 * On the other hand, when the user aborts this operation, the old values 46 * should be restored. We have a temp rary copy, that overrides61 * should be restored. We have a temporary copy, that overrides 47 62 * the normal values if it is not null. (This may be not the most elegant 48 63 * solution for this, but it works.) 49 64 */ 50 65 ImageEntry tmp; 51 66 52 67 /** 68 * Constructs a new {@code ImageEntry}. 69 */ 70 public ImageEntry() {} 71 72 /** 73 * Constructs a new {@code ImageEntry}. 74 * @param file Path to image file on disk 75 */ 76 public ImageEntry(File file) { 77 setFile(file); 78 } 79 80 /** 53 81 * Returns the cached temporary position value. 54 82 * @return the cached temporary position value 55 83 */ … … public boolean hasGpsTime() { 98 126 return (tmp != null && tmp.gpsTime != null) || gpsTime != null; 99 127 } 100 128 129 public Double getExifImgDir() { 130 if (tmp != null) 131 return tmp.exifImgDir; 132 return exifImgDir; 133 } 134 101 135 /** 102 136 * Returns associated file. 103 137 * @return associated file … … public LatLon getExifCoor() { 159 193 return exifCoor; 160 194 } 161 195 162 public Double getExifImgDir() {163 return exifImgDir;164 }165 166 196 public boolean hasThumbnail() { 167 197 return thumbnail != null; 168 198 } 169 199 200 public Image getThumbnail() { 201 return thumbnail; 202 } 203 204 public void setThumbnail(Image thumbnail) { 205 this.thumbnail = thumbnail; 206 } 207 170 208 /** 171 209 * Sets the position. 172 210 * @param pos cached position … … public void setPos(CachedLatLon pos) { 180 218 * @param pos position (will be cached) 181 219 */ 182 220 public void setPos(LatLon pos) { 183 setPos( new CachedLatLon(pos));221 setPos(pos != null ? new CachedLatLon(pos) : null); 184 222 } 185 223 186 224 /** … … public void setExifCoor(LatLon exifCoor) { 240 278 this.exifCoor = exifCoor; 241 279 } 242 280 243 public void setExifImgDir( double exifDir) {281 public void setExifImgDir(Double exifDir) { 244 282 this.exifImgDir = exifDir; 245 283 } 246 284 … … public void cleanTmp() { 277 315 } 278 316 279 317 /** 280 * Copy the values from the temporary variable to the main instance. 318 * Get temporary variable that is used for real time parameter 319 * adjustments. The temporary variable is created if it does not exist 320 * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable is not 321 * needed anymore. 322 */ 323 public ImageEntry getTmp() { 324 if (tmp == null) { 325 cleanTmp(); 326 } 327 return tmp; 328 } 329 330 /** 331 * Copy the values from the temporary variable to the main instance. The 332 * temporary variable is deleted. 333 * @see #discardTmp() 281 334 */ 282 335 public void applyTmp() { 283 336 if (tmp != null) { … … public void applyTmp() { 285 338 speed = tmp.speed; 286 339 elevation = tmp.elevation; 287 340 gpsTime = tmp.gpsTime; 341 exifImgDir = tmp.exifImgDir; 288 342 tmp = null; 289 343 } 290 344 } 291 345 292 346 /** 347 * Delete the temporary variable. Temporary modifications are lost. 348 * @see #applyTmp() 349 */ 350 public void discardTmp() { 351 tmp = null; 352 } 353 354 /** 293 355 * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif 294 356 * @return {@code true} if it has been tagged 295 357 */ … … public void unflagNewGpsData() { 335 397 public boolean hasNewGpsData() { 336 398 return isNewGpsData; 337 399 } 400 401 /** 402 * Extract GPS metadata from image EXIF. Has no effect if the image file is not set 403 * 404 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes 405 */ 406 public void extractExif() { 407 408 Metadata metadata; 409 Directory dirExif; 410 GpsDirectory dirGps; 411 412 if (file == null) { 413 return; 414 } 415 416 try { 417 metadata = JpegMetadataReader.readMetadata(file); 418 dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 419 dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 420 } catch (CompoundException | IOException p) { 421 setExifCoor(null); 422 setPos(null); 423 return; 424 } 425 426 try { 427 if (dirExif != null) { 428 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION); 429 setExifOrientation(orientation); 430 } 431 } catch (MetadataException ex) { 432 Main.debug(ex.getMessage()); 433 } 434 435 if (dirGps == null) { 436 setExifCoor(null); 437 setPos(null); 438 return; 439 } 440 441 try { 442 double speed = dirGps.getDouble(GpsDirectory.TAG_SPEED); 443 String speedRef = dirGps.getString(GpsDirectory.TAG_SPEED_REF); 444 if ("M".equalsIgnoreCase(speedRef)) { 445 // miles per hour 446 speed *= SystemOfMeasurement.IMPERIAL.bValue / 1000; 447 } else if ("N".equalsIgnoreCase(speedRef)) { 448 // knots == nautical miles per hour 449 speed *= SystemOfMeasurement.NAUTICAL_MILE.bValue / 1000; 450 } 451 // default is K (km/h) 452 setSpeed(speed); 453 } catch (Exception ex) { 454 Main.debug(ex.getMessage()); 455 } 456 457 try { 458 double ele = dirGps.getDouble(GpsDirectory.TAG_ALTITUDE); 459 int d = dirGps.getInt(GpsDirectory.TAG_ALTITUDE_REF); 460 if (d == 1) { 461 ele *= -1; 462 } 463 setElevation(ele); 464 } catch (MetadataException ex) { 465 Main.debug(ex.getMessage()); 466 } 467 468 try { 469 LatLon latlon = ExifReader.readLatLon(dirGps); 470 setExifCoor(latlon); 471 setPos(getExifCoor()); 472 473 } catch (Exception ex) { // (other exceptions, e.g. #5271) 474 Main.error("Error reading EXIF from file: " + ex); 475 setExifCoor(null); 476 setPos(null); 477 } 478 479 try { 480 Double direction = ExifReader.readDirection(dirGps); 481 if (direction != null) { 482 setExifImgDir(direction); 483 } 484 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271) 485 Main.debug(ex.getMessage()); 486 } 487 488 // Changed to silently cope with no time info in exif. One case 489 // of person having time that couldn't be parsed, but valid GPS info 490 try { 491 setExifTime(ExifReader.readTime(file)); 492 } catch (ParseException ex) { 493 setExifTime(null); 494 } 495 496 // Time and date. We can have these cases: 497 // 1) GPS_TIME_STAMP not set -> date/time will be null 498 // 2) GPS_DATE_STAMP not set -> use EXIF date or set to default 499 // 3) GPS_TIME_STAMP and GPS_DATE_STAMP are set 500 int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_TIME_STAMP); 501 if (timeStampComps != null) { 502 int gpsHour = timeStampComps[0]; 503 int gpsMin = timeStampComps[1]; 504 int gpsSec = timeStampComps[2]; 505 Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 506 507 // We have the time. Next step is to check if the GPS date stamp is set. 508 // dirGps.getString() always succeeds, but the return value might be null. 509 String dateStampStr = dirGps.getString(GpsDirectory.TAG_DATE_STAMP); 510 if (dateStampStr != null && dateStampStr.matches("^\\d+:\\d+:\\d+$")) { 511 String[] dateStampComps = dateStampStr.split(":"); 512 cal.set(Calendar.YEAR, Integer.parseInt(dateStampComps[0])); 513 cal.set(Calendar.MONTH, Integer.parseInt(dateStampComps[1]) - 1); 514 cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStampComps[2])); 515 } else { 516 // No GPS date stamp in EXIF data. Copy it from EXIF time. 517 // Date is not set if EXIF time is not available. 518 if (hasExifTime()) { 519 // Time not set yet, so we can copy everything, not just date. 520 cal.setTime(getExifTime()); 521 } 522 } 523 524 cal.set(Calendar.HOUR_OF_DAY, gpsHour); 525 cal.set(Calendar.MINUTE, gpsMin); 526 cal.set(Calendar.SECOND, gpsSec); 527 528 setExifGpsTime(cal.getTime()); 529 } 530 } 338 531 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java index 8ff6a51..6666eb2 100644
a b public static ImageViewerDialog getInstance() { 69 69 private JButton btnNext; 70 70 private JButton btnPrevious; 71 71 private JButton btnCollapse; 72 private JToggleButton tbCentre; 72 73 73 74 private ImageViewerDialog() { 74 75 super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged", … … protected void build() { 149 150 "geoimage:last", tr("Geoimage: {0}", tr("Show last Image")), KeyEvent.VK_END, Shortcut.DIRECT) 150 151 ); 151 152 152 JToggleButtontbCentre = new JToggleButton(new ImageAction(COMMAND_CENTERVIEW,153 tbCentre = new JToggleButton(new ImageAction(COMMAND_CENTERVIEW, 153 154 ImageProvider.get("dialogs", "centreview"), tr("Center view"))); 154 155 tbCentre.setPreferredSize(buttonDim); 155 156 … … public void actionPerformed(ActionEvent e) { 224 225 } else if (COMMAND_LAST.equals(action) && currentLayer != null) { 225 226 currentLayer.showLastPhoto(); 226 227 } else if (COMMAND_CENTERVIEW.equals(action)) { 227 centerView = ((JToggleButton) e.getSource()).isSelected(); 228 final JToggleButton button = (JToggleButton) e.getSource(); 229 centerView = button.isEnabled() && button.isSelected(); 228 230 if (centerView && currentEntry != null && currentEntry.getPos() != null) { 229 231 Main.map.mapView.zoomTo(currentEntry.getPos()); 230 232 } … … public static void setNextEnabled(boolean value) { 275 277 getInstance().btnNext.setEnabled(value); 276 278 } 277 279 280 /** 281 * Enables (or disables) the "Center view" button. 282 * @param value {@code true} to enable the button, {@code false} otherwise 283 * @return the old enabled value. Can be used to restore the original enable state 284 */ 285 public static synchronized boolean setCentreEnabled(boolean value) { 286 final ImageViewerDialog instance = getInstance(); 287 final boolean wasEnabled = instance.tbCentre.isEnabled(); 288 instance.tbCentre.setEnabled(value); 289 instance.tbCentre.getAction().actionPerformed(new ActionEvent(instance.tbCentre, 0, null)); 290 return wasEnabled; 291 } 292 278 293 private transient GeoImageLayer currentLayer; 279 294 private transient ImageEntry currentEntry; 280 295 … … public void displayImage(GeoImageLayer layer, ImageEntry entry) { 303 318 setTitle(tr("Geotagged Images") + (entry.getFile() != null ? " - " + entry.getFile().getName() : "")); 304 319 StringBuilder osd = new StringBuilder(entry.getFile() != null ? entry.getFile().getName() : ""); 305 320 if (entry.getElevation() != null) { 306 osd.append(tr("\nAltitude: {0} m", entry.getElevation().longValue()));321 osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation()))); 307 322 } 308 323 if (entry.getSpeed() != null) { 309 324 osd.append(tr("\nSpeed: {0} km/h", Math.round(entry.getSpeed()))); -
src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java index 9460d76..47706a3 100644
a b public void run() { 54 54 if (stop) return; 55 55 56 56 // Do not load thumbnails that were loaded before. 57 if (entry. thumbnail == null) {58 entry. thumbnail = loadThumb(entry);57 if (entry.hasThumbnail()) { 58 entry.setThumbnail(loadThumb(entry)); 59 59 60 60 if (Main.isDisplayingMapView()) { 61 61 layer.updateOffscreenBuffer = true;