Ticket #22337: 22337.patch
| File 22337.patch, 59.6 KB (added by , 4 years ago) |
|---|
-
src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java b/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.gpx; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 4 import java.awt.Dimension; 7 5 import java.awt.image.BufferedImage; 8 6 import java.io.File; 9 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.net.URI; 10 import java.nio.file.Files; 10 11 import java.time.Instant; 11 12 import java.util.Date; 12 13 import java.util.List; 13 import java.util.Locale;14 import java.util.Map;15 14 import java.util.Objects; 16 import java.util.function.Consumer;17 import java.util.stream.Stream;18 15 19 16 import javax.imageio.IIOParam; 20 17 21 18 import org.openstreetmap.josm.data.IQuadBucketType; 22 19 import org.openstreetmap.josm.data.coor.CachedLatLon; 20 import org.openstreetmap.josm.data.coor.ILatLon; 23 21 import org.openstreetmap.josm.data.coor.LatLon; 24 22 import org.openstreetmap.josm.data.imagery.street_level.Projections; 25 23 import org.openstreetmap.josm.data.osm.BBox; 26 import org.openstreetmap.josm.tools.ExifReader; 27 import org.openstreetmap.josm.tools.JosmRuntimeException; 28 import org.openstreetmap.josm.tools.Logging; 29 30 import com.drew.imaging.jpeg.JpegMetadataReader; 31 import com.drew.imaging.jpeg.JpegProcessingException; 32 import com.drew.imaging.png.PngMetadataReader; 33 import com.drew.imaging.png.PngProcessingException; 34 import com.drew.imaging.tiff.TiffMetadataReader; 35 import com.drew.imaging.tiff.TiffProcessingException; 36 import com.drew.metadata.Directory; 37 import com.drew.metadata.Metadata; 38 import com.drew.metadata.MetadataException; 39 import com.drew.metadata.exif.ExifIFD0Directory; 40 import com.drew.metadata.exif.GpsDirectory; 41 import com.drew.metadata.iptc.IptcDirectory; 42 import com.drew.metadata.jpeg.JpegDirectory; 43 import com.drew.metadata.xmp.XmpDirectory; 24 import org.openstreetmap.josm.gui.layer.geoimage.ImageMetadata; 44 25 45 26 /** 46 27 * Stores info about each image 47 28 * @since 14205 (extracted from gui.layer.geoimage.ImageEntry) 48 29 */ 49 public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType {30 public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType, ImageMetadata { 50 31 private File file; 51 32 private Integer exifOrientation; 52 33 private LatLon exifCoor; … … 127 108 setFile(file); 128 109 } 129 110 111 @Override 112 public URI getImageURI() { 113 return this.getFile().toURI(); 114 } 115 130 116 /** 131 117 * Returns width of the image this GpxImageEntry represents. 132 118 * @return width of the image this GpxImageEntry represents 133 119 * @since 13220 134 120 */ 121 @Override 135 122 public int getWidth() { 136 123 return width; 137 124 } … … 141 128 * @return height of the image this GpxImageEntry represents 142 129 * @since 13220 143 130 */ 131 @Override 144 132 public int getHeight() { 145 133 return height; 146 134 } … … 150 138 * is returned if that copy exists. 151 139 * @return the position value 152 140 */ 141 @Override 153 142 public CachedLatLon getPos() { 154 143 if (tmp != null) 155 144 return tmp.pos; … … 161 150 * returned if that copy exists. 162 151 * @return the speed value 163 152 */ 153 @Override 164 154 public Double getSpeed() { 165 155 if (tmp != null) 166 156 return tmp.speed; … … 172 162 * copy is returned if that copy exists. 173 163 * @return the elevation value 174 164 */ 165 @Override 175 166 public Double getElevation() { 176 167 if (tmp != null) 177 168 return tmp.elevation; … … 196 187 * is returned if that copy exists. 197 188 * @return the GPS time value 198 189 */ 190 @Override 199 191 public Instant getGpsInstant() { 200 192 return tmp != null ? tmp.gpsTime : gpsTime; 201 193 } … … 205 197 * @return {@code true} if this entry has a GPS time 206 198 * @since 6450 207 199 */ 200 @Override 208 201 public boolean hasGpsTime() { 209 202 return (tmp != null && tmp.gpsTime != null) || gpsTime != null; 210 203 } … … 229 222 * Returns EXIF orientation 230 223 * @return EXIF orientation 231 224 */ 225 @Override 232 226 public Integer getExifOrientation() { 233 227 return exifOrientation != null ? exifOrientation : 1; 234 228 } … … 238 232 * @return EXIF time 239 233 * @since 17715 240 234 */ 235 @Override 241 236 public Instant getExifInstant() { 242 237 return exifTime; 243 238 } … … 247 242 * @return {@code true} if this entry has a EXIF time 248 243 * @since 6450 249 244 */ 245 @Override 250 246 public boolean hasExifTime() { 251 247 return exifTime != null; 252 248 } … … 267 263 * @return the EXIF GPS time 268 264 * @since 17715 269 265 */ 266 @Override 270 267 public Instant getExifGpsInstant() { 271 268 return exifGpsTime; 272 269 } … … 276 273 * @return {@code true} if this entry has a EXIF GPS time 277 274 * @since 6450 278 275 */ 276 @Override 279 277 public boolean hasExifGpsTime() { 280 278 return exifGpsTime != null; 281 279 } … … 286 284 return Date.from(date); 287 285 } 288 286 287 @Override 289 288 public LatLon getExifCoor() { 290 289 return exifCoor; 291 290 } 292 291 292 @Override 293 293 public Double getExifImgDir() { 294 294 if (tmp != null) 295 295 return tmp.exifImgDir; 296 296 return exifImgDir; 297 297 } 298 298 299 @Override 300 public Instant getLastModified() { 301 return Instant.ofEpochMilli(this.getFile().lastModified()); 302 } 303 299 304 /** 300 305 * Sets the width of this GpxImageEntry. 301 306 * @param width set the width of this GpxImageEntry 302 307 * @since 13220 303 308 */ 309 @Override 304 310 public void setWidth(int width) { 305 311 this.width = width; 306 312 } … … 310 316 * @param height set the height of this GpxImageEntry 311 317 * @since 13220 312 318 */ 319 @Override 313 320 public void setHeight(int height) { 314 321 this.height = height; 315 322 } … … 330 337 setPos(pos != null ? new CachedLatLon(pos) : null); 331 338 } 332 339 340 @Override 341 public void setPos(ILatLon pos) { 342 if (pos instanceof CachedLatLon) { 343 this.setPos((CachedLatLon) pos); 344 } else if (pos instanceof LatLon) { 345 this.setPos((LatLon) pos); 346 } else if (pos != null) { 347 this.setPos(new LatLon(pos)); 348 } else { 349 this.setPos(null); 350 } 351 } 352 333 353 /** 334 354 * Sets the speed. 335 355 * @param speed speed 336 356 */ 357 @Override 337 358 public void setSpeed(Double speed) { 338 359 this.speed = speed; 339 360 } … … 342 363 * Sets the elevation. 343 364 * @param elevation elevation 344 365 */ 366 @Override 345 367 public void setElevation(Double elevation) { 346 368 this.elevation = elevation; 347 369 } … … 358 380 * Sets EXIF orientation. 359 381 * @param exifOrientation EXIF orientation 360 382 */ 383 @Override 361 384 public void setExifOrientation(Integer exifOrientation) { 362 385 this.exifOrientation = exifOrientation; 363 386 } … … 367 390 * @param exifTime EXIF time 368 391 * @since 17715 369 392 */ 393 @Override 370 394 public void setExifTime(Instant exifTime) { 371 395 this.exifTime = exifTime; 372 396 } … … 376 400 * @param exifGpsTime the EXIF GPS time 377 401 * @since 17715 378 402 */ 403 @Override 379 404 public void setExifGpsTime(Instant exifGpsTime) { 380 405 this.exifGpsTime = exifGpsTime; 381 406 } … … 385 410 * @param gpsTime the GPS time 386 411 * @since 17715 387 412 */ 413 @Override 388 414 public void setGpsTime(Instant gpsTime) { 389 415 this.gpsTime = gpsTime; 390 416 } … … 393 419 this.exifCoor = exifCoor; 394 420 } 395 421 422 @Override 423 public void setExifCoor(ILatLon exifCoor) { 424 if (exifCoor instanceof LatLon) { 425 this.exifCoor = (LatLon) exifCoor; 426 } else if (exifCoor != null) { 427 this.exifCoor = new LatLon(exifCoor); 428 } else { 429 this.exifCoor = null; 430 } 431 } 432 433 @Override 396 434 public void setExifImgDir(Double exifDir) { 397 435 this.exifImgDir = exifDir; 398 436 } … … 402 440 * @param iptcCaption the IPTC caption 403 441 * @since 15219 404 442 */ 443 @Override 405 444 public void setIptcCaption(String iptcCaption) { 406 445 this.iptcCaption = iptcCaption; 407 446 } … … 411 450 * @param iptcHeadline the IPTC headline 412 451 * @since 15219 413 452 */ 453 @Override 414 454 public void setIptcHeadline(String iptcHeadline) { 415 455 this.iptcHeadline = iptcHeadline; 416 456 } … … 420 460 * @param iptcKeywords the IPTC keywords 421 461 * @since 15219 422 462 */ 463 @Override 423 464 public void setIptcKeywords(List<String> iptcKeywords) { 424 465 this.iptcKeywords = iptcKeywords; 425 466 } … … 429 470 * @param iptcObjectName the IPTC object name 430 471 * @since 15219 431 472 */ 473 @Override 432 474 public void setIptcObjectName(String iptcObjectName) { 433 475 this.iptcObjectName = iptcObjectName; 434 476 } … … 438 480 * @return the IPTC caption 439 481 * @since 15219 440 482 */ 483 @Override 441 484 public String getIptcCaption() { 442 485 return iptcCaption; 443 486 } … … 447 490 * @return the IPTC headline 448 491 * @since 15219 449 492 */ 493 @Override 450 494 public String getIptcHeadline() { 451 495 return iptcHeadline; 452 496 } … … 456 500 * @return the IPTC keywords 457 501 * @since 15219 458 502 */ 503 @Override 459 504 public List<String> getIptcKeywords() { 460 505 return iptcKeywords; 461 506 } … … 465 510 * @return the IPTC object name 466 511 * @since 15219 467 512 */ 513 @Override 468 514 public String getIptcObjectName() { 469 515 return iptcObjectName; 470 516 } … … 642 688 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes 643 689 * @since 9270 644 690 */ 691 @Override 645 692 public void extractExif() { 646 647 Metadata metadata; 648 649 if (file == null) { 650 return; 651 } 652 653 String fn = file.getName(); 654 655 try { 656 // try to parse metadata according to extension 657 String ext = fn.substring(fn.lastIndexOf('.') + 1).toLowerCase(Locale.US); 658 switch (ext) { 659 case "jpg": 660 case "jpeg": 661 metadata = JpegMetadataReader.readMetadata(file); 662 break; 663 case "tif": 664 case "tiff": 665 metadata = TiffMetadataReader.readMetadata(file); 666 break; 667 case "png": 668 metadata = PngMetadataReader.readMetadata(file); 669 break; 670 default: 671 throw new NoMetadataReaderWarning(ext); 672 } 673 } catch (JpegProcessingException | TiffProcessingException | PngProcessingException | IOException 674 | NoMetadataReaderWarning topException) { 675 //try other formats (e.g. JPEG file with .png extension) 676 try { 677 metadata = JpegMetadataReader.readMetadata(file); 678 } catch (JpegProcessingException | IOException ex1) { 679 try { 680 metadata = TiffMetadataReader.readMetadata(file); 681 } catch (TiffProcessingException | IOException ex2) { 682 try { 683 metadata = PngMetadataReader.readMetadata(file); 684 } catch (PngProcessingException | IOException ex3) { 685 Logging.warn(topException); 686 Logging.info(tr("Can''t parse metadata for file \"{0}\". Using last modified date as timestamp.", fn)); 687 setExifTime(Instant.ofEpochMilli(file.lastModified())); 688 setExifCoor(null); 689 setPos(null); 690 return; 691 } 692 } 693 } 694 } 695 696 IptcDirectory dirIptc = metadata.getFirstDirectoryOfType(IptcDirectory.class); 697 if (dirIptc != null) { 698 ifNotNull(ExifReader.readCaption(dirIptc), this::setIptcCaption); 699 ifNotNull(ExifReader.readHeadline(dirIptc), this::setIptcHeadline); 700 ifNotNull(ExifReader.readKeywords(dirIptc), this::setIptcKeywords); 701 ifNotNull(ExifReader.readObjectName(dirIptc), this::setIptcObjectName); 702 } 703 704 for (XmpDirectory xmpDirectory : metadata.getDirectoriesOfType(XmpDirectory.class)) { 705 Map<String, String> properties = xmpDirectory.getXmpProperties(); 706 final String projectionType = "GPano:ProjectionType"; 707 if (properties.containsKey(projectionType)) { 708 Stream.of(Projections.values()).filter(p -> p.name().equalsIgnoreCase(properties.get(projectionType))) 709 .findFirst().ifPresent(projection -> this.cameraProjection = projection); 710 break; 711 } 712 } 713 714 // Changed to silently cope with no time info in exif. One case 715 // of person having time that couldn't be parsed, but valid GPS info 716 Instant time = null; 717 try { 718 time = ExifReader.readInstant(metadata); 719 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) { 720 Logging.warn(ex); 721 } 722 723 if (time == null) { 724 Logging.info(tr("No EXIF time in file \"{0}\". Using last modified date as timestamp.", fn)); 725 time = Instant.ofEpochMilli(file.lastModified()); //use lastModified time if no EXIF time present 726 } 727 setExifTime(time); 728 729 final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class); 730 final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 731 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 732 733 try { 734 if (dirExif != null && dirExif.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { 735 setExifOrientation(dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION)); 736 } 737 } catch (MetadataException ex) { 738 Logging.debug(ex); 739 } 740 741 try { 742 if (dir != null && dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH) && dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) { 743 // there are cases where these do not match width and height stored in dirExif 744 setWidth(dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH)); 745 setHeight(dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT)); 746 } 747 } catch (MetadataException ex) { 748 Logging.debug(ex); 749 } 750 751 if (dirGps == null || dirGps.getTagCount() <= 1) { 752 setExifCoor(null); 753 setPos(null); 754 return; 755 } 756 757 ifNotNull(ExifReader.readSpeed(dirGps), this::setSpeed); 758 ifNotNull(ExifReader.readElevation(dirGps), this::setElevation); 759 760 try { 761 setExifCoor(ExifReader.readLatLon(dirGps)); 762 setPos(getExifCoor()); 763 } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271) 764 Logging.error("Error reading EXIF from file: " + ex); 765 setExifCoor(null); 766 setPos(null); 767 } 768 769 try { 770 ifNotNull(ExifReader.readDirection(dirGps), this::setExifImgDir); 771 } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271) 772 Logging.debug(ex); 773 } 693 ImageMetadata.super.extractExif(); 694 } 774 695 775 ifNotNull(dirGps.getGpsDate(), d -> setExifGpsTime(d.toInstant())); 696 @Override 697 public InputStream getInputStream() throws IOException { 698 return Files.newInputStream(this.getFile().toPath()); 776 699 } 777 700 778 701 /** … … 786 709 throw new UnsupportedOperationException("read not implemented for " + this.getClass().getSimpleName()); 787 710 } 788 711 789 private static class NoMetadataReaderWarning extends Exception {790 NoMetadataReaderWarning(String ext) {791 super("No metadata reader for format *." + ext);792 }793 }794 795 private static <T> void ifNotNull(T value, Consumer<T> setter) {796 if (value != null) {797 setter.accept(value);798 }799 }800 801 712 /** 802 713 * Get the projection type for this entry 803 714 * @return The projection type 804 715 * @since 18246 805 716 */ 717 @Override 806 718 public Projections getProjectionType() { 807 719 return this.cameraProjection; 808 720 } 809 721 722 @Override 723 public void setProjectionType(Projections newProjection) { 724 this.cameraProjection = newProjection; 725 } 726 810 727 /** 811 728 * Returns a {@link WayPoint} representation of this GPX image entry. 812 729 * @return a {@code WayPoint} representation of this GPX image entry (containing position, instant and elevation) -
src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java b/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.imagery.street_level; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 4 6 import java.awt.Dimension; 5 7 import java.awt.image.BufferedImage; 6 8 import java.io.File; … … 12 14 import javax.imageio.IIOParam; 13 15 14 16 import org.openstreetmap.josm.data.coor.ILatLon; 17 import org.openstreetmap.josm.gui.layer.geoimage.ImageMetadata; 18 import org.openstreetmap.josm.gui.layer.geoimage.ImageUtils; 15 19 import org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog; 20 import org.openstreetmap.josm.tools.ExifReader; 21 import org.openstreetmap.josm.tools.ImageProvider; 22 import org.openstreetmap.josm.tools.Logging; 16 23 17 24 /** 18 25 * An interface for image entries that will be shown in {@link org.openstreetmap.josm.gui.layer.geoimage.ImageDisplay} … … 137 144 * @return the read image, or {@code null} 138 145 * @throws IOException if any I/O error occurs 139 146 */ 140 BufferedImage read(Dimension target) throws IOException; 147 default BufferedImage read(Dimension target) throws IOException { 148 URI imageUrl = getImageURI(); 149 Logging.info(tr("Loading {0}", imageUrl)); 150 BufferedImage image = ImageProvider.read(imageUrl.toURL(), false, false, 151 r -> target == null ? r.getDefaultReadParam() : ImageUtils.withSubsampling(r, target)); 152 if (image == null) { 153 Logging.warn("Unable to load {0}", imageUrl); 154 return null; 155 } 156 if (this instanceof ImageMetadata) { 157 ImageMetadata data = (ImageMetadata) this; 158 Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}", 159 imageUrl, image.getWidth(), image.getHeight(), image.getWidth() * image.getHeight() * 4 / 1024 / 1024, 160 ExifReader.orientationSwitchesDimensions(data.getExifOrientation())); 161 return ImageUtils.applyExifRotation(image, data.getExifOrientation()); 162 } 163 return image; 164 } 141 165 142 166 /** 143 167 * Sets the width of this ImageEntry. -
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
a b 704 704 } 705 705 }; 706 706 MainApplication.getLayerManager().addActiveLayerChangeListener(activeLayerChangeListener); 707 708 MapFrame map = MainApplication.getMap();709 if (map.getToggleDialog(ImageViewerDialog.class) == null) {710 ImageViewerDialog.createInstance();711 map.addToggleDialog(ImageViewerDialog.getInstance());712 }713 707 } 714 708 715 709 @Override -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 4 import java.awt.Dimension; 7 import java.awt.Graphics2D;8 5 import java.awt.Image; 9 import java.awt.geom.AffineTransform;10 6 import java.awt.image.BufferedImage; 11 7 import java.io.File; 12 8 import java.io.IOException; 13 import java.io.UncheckedIOException;14 9 import java.net.MalformedURLException; 15 10 import java.net.URL; 16 11 import java.util.Collections; 17 12 import java.util.Objects; 13 18 14 import javax.imageio.IIOParam; 19 import javax.imageio.ImageReadParam;20 import javax.imageio.ImageReader;21 15 22 16 import org.openstreetmap.josm.data.ImageData; 23 17 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 24 18 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 25 import org.openstreetmap.josm.tools.ExifReader;26 import org.openstreetmap.josm.tools.ImageProvider;27 import org.openstreetmap.josm.tools.Logging;28 19 import org.openstreetmap.josm.tools.Utils; 29 20 30 21 /** … … 193 184 */ 194 185 @Override 195 186 public BufferedImage read(Dimension target) throws IOException { 196 URL imageUrl = getImageUrl(); 197 Logging.info(tr("Loading {0}", imageUrl)); 198 BufferedImage image = ImageProvider.read(imageUrl, false, false, 199 r -> target == null ? r.getDefaultReadParam() : withSubsampling(r, target)); 200 if (image == null) { 201 Logging.warn("Unable to load {0}", imageUrl); 202 return null; 203 } 204 Logging.debug("Loaded {0} with dimensions {1}x{2} memoryTaken={3}m exifOrientationSwitchedDimension={4}", 205 imageUrl, image.getWidth(), image.getHeight(), image.getWidth() * image.getHeight() * 4 / 1024 / 1024, 206 ExifReader.orientationSwitchesDimensions(getExifOrientation())); 207 return applyExifRotation(image); 187 return IImageEntry.super.read(target); 208 188 } 209 189 210 190 protected URL getImageUrl() throws MalformedURLException { 211 191 return getFile().toURI().toURL(); 212 192 } 213 214 private ImageReadParam withSubsampling(ImageReader reader, Dimension target) {215 try {216 ImageReadParam param = reader.getDefaultReadParam();217 Dimension source = new Dimension(reader.getWidth(0), reader.getHeight(0));218 if (source.getWidth() > target.getWidth() || source.getHeight() > target.getHeight()) {219 int subsampling = (int) Math.floor(Math.max(220 source.getWidth() / target.getWidth(),221 source.getHeight() / target.getHeight()));222 param.setSourceSubsampling(subsampling, subsampling, 0, 0);223 }224 return param;225 } catch (IOException e) {226 throw new UncheckedIOException(e);227 }228 }229 230 private BufferedImage applyExifRotation(BufferedImage img) {231 Integer exifOrientation = getExifOrientation();232 if (!ExifReader.orientationNeedsCorrection(exifOrientation)) {233 return img;234 }235 boolean switchesDimensions = ExifReader.orientationSwitchesDimensions(exifOrientation);236 int width = switchesDimensions ? img.getHeight() : img.getWidth();237 int height = switchesDimensions ? img.getWidth() : img.getHeight();238 BufferedImage rotated = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);239 AffineTransform transform = ExifReader.getRestoreOrientationTransform(exifOrientation, img.getWidth(), img.getHeight());240 Graphics2D g = rotated.createGraphics();241 g.drawImage(img, transform, null);242 g.dispose();243 return rotated;244 }245 193 } -
new file src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageMetadata.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 4 import java.io.BufferedInputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.UncheckedIOException; 8 import java.net.URI; 9 import java.time.Instant; 10 import java.util.List; 11 12 import org.openstreetmap.josm.data.coor.ILatLon; 13 import org.openstreetmap.josm.data.imagery.street_level.Projections; 14 15 /** 16 * An interface for images with metadata 17 * @author Taylor Smock 18 * @since xxx 19 */ 20 public interface ImageMetadata { 21 /** 22 * Get the image location 23 * @return The image location 24 */ 25 URI getImageURI(); 26 27 /** 28 * Returns width of the image this ImageMetadata represents. 29 * @return width of the image this ImageMetadata represents 30 */ 31 int getWidth(); 32 33 /** 34 * Returns height of the image this ImageMetadata represents. 35 * @return height of the image this ImageMetadata represents 36 * @since 13220 37 */ 38 int getHeight(); 39 40 /** 41 * Returns the position value. The position value from the temporary copy 42 * is returned if that copy exists. 43 * @return the position value 44 */ 45 ILatLon getPos(); 46 47 /** 48 * Returns the speed value. The speed value from the temporary copy is 49 * returned if that copy exists. 50 * @return the speed value 51 */ 52 Double getSpeed(); 53 54 /** 55 * Returns the elevation value. The elevation value from the temporary 56 * copy is returned if that copy exists. 57 * @return the elevation value 58 */ 59 Double getElevation(); 60 61 /** 62 * Returns the GPS time value. The GPS time value from the temporary copy 63 * is returned if that copy exists. 64 * @return the GPS time value 65 */ 66 Instant getGpsInstant(); 67 68 /** 69 * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy. 70 * @return {@code true} if this entry has a GPS time 71 * @since 6450 72 */ 73 boolean hasGpsTime(); 74 75 /** 76 * Returns a display name for this entry 77 * @return a display name for this entry 78 */ 79 String getDisplayName(); 80 81 /** 82 * Returns EXIF orientation 83 * @return EXIF orientation 84 */ 85 default Integer getExifOrientation() { 86 return 1; 87 } 88 89 /** 90 * Returns EXIF time 91 * @return EXIF time 92 * @since 17715 93 */ 94 Instant getExifInstant(); 95 96 /** 97 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy. 98 * @return {@code true} if this entry has a EXIF time 99 * @since 6450 100 */ 101 boolean hasExifTime(); 102 103 /** 104 * Returns the EXIF GPS time. 105 * @return the EXIF GPS time 106 * @since 17715 107 */ 108 Instant getExifGpsInstant(); 109 110 /** 111 * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy. 112 * @return {@code true} if this entry has a EXIF GPS time 113 * @since 6450 114 */ 115 boolean hasExifGpsTime(); 116 117 /** 118 * Get the exif coordinates 119 * @return The location of the image 120 */ 121 ILatLon getExifCoor(); 122 123 /** 124 * Get the exif direction 125 * @return The image direction 126 */ 127 Double getExifImgDir(); 128 129 /** 130 * Get the last time the source was modified 131 * @return The last time the source was modified 132 */ 133 Instant getLastModified(); 134 135 /** 136 * Sets the width of this ImageMetadata. 137 * @param width set the width of this ImageMetadata 138 * @since 13220 139 */ 140 void setWidth(int width); 141 142 /** 143 * Sets the height of this ImageMetadata. 144 * @param height set the height of this ImageMetadata 145 * @since 13220 146 */ 147 void setHeight(int height); 148 149 /** 150 * Sets the position. 151 * @param pos position (will be cached) 152 */ 153 void setPos(ILatLon pos); 154 155 /** 156 * Sets the speed. 157 * @param speed speed 158 */ 159 void setSpeed(Double speed); 160 161 /** 162 * Sets the elevation. 163 * @param elevation elevation 164 */ 165 void setElevation(Double elevation); 166 167 /** 168 * Sets EXIF orientation. 169 * @param exifOrientation EXIF orientation 170 */ 171 void setExifOrientation(Integer exifOrientation); 172 173 /** 174 * Sets EXIF time. 175 * @param exifTime EXIF time 176 * @since 17715 177 */ 178 void setExifTime(Instant exifTime); 179 180 /** 181 * Sets the EXIF GPS time. 182 * @param exifGpsTime the EXIF GPS time 183 * @since 17715 184 */ 185 void setExifGpsTime(Instant exifGpsTime); 186 187 /** 188 * Sets the GPS time. 189 * @param gpsTime the GPS time 190 * @since 17715 191 */ 192 void setGpsTime(Instant gpsTime); 193 194 /** 195 * Set the exif coordinates 196 * @param exifCoor The exif coordinates 197 */ 198 void setExifCoor(ILatLon exifCoor); 199 200 /** 201 * Set the exif direction 202 * @param exifDir The direction 203 */ 204 void setExifImgDir(Double exifDir); 205 206 /** 207 * Sets the IPTC caption. 208 * @param iptcCaption the IPTC caption 209 * @since 15219 210 */ 211 void setIptcCaption(String iptcCaption); 212 213 /** 214 * Sets the IPTC headline. 215 * @param iptcHeadline the IPTC headline 216 * @since 15219 217 */ 218 void setIptcHeadline(String iptcHeadline); 219 220 /** 221 * Sets the IPTC keywords. 222 * @param iptcKeywords the IPTC keywords 223 * @since 15219 224 */ 225 void setIptcKeywords(List<String> iptcKeywords); 226 227 /** 228 * Sets the IPTC object name. 229 * @param iptcObjectName the IPTC object name 230 * @since 15219 231 */ 232 void setIptcObjectName(String iptcObjectName); 233 234 /** 235 * Returns the IPTC caption. 236 * @return the IPTC caption 237 * @since 15219 238 */ 239 String getIptcCaption(); 240 241 /** 242 * Returns the IPTC headline. 243 * @return the IPTC headline 244 * @since 15219 245 */ 246 String getIptcHeadline(); 247 248 /** 249 * Returns the IPTC keywords. 250 * @return the IPTC keywords 251 * @since 15219 252 */ 253 List<String> getIptcKeywords(); 254 255 /** 256 * Returns the IPTC object name. 257 * @return the IPTC object name 258 * @since 15219 259 */ 260 String getIptcObjectName(); 261 262 /** 263 * Extract GPS metadata from image EXIF. Has no effect if the image file is not set 264 * 265 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes 266 * @since 9270 267 */ 268 default void extractExif() { 269 try (InputStream original = getInputStream(); 270 BufferedInputStream bufferedInputStream = new BufferedInputStream(original)) { 271 ImageUtils.applyExif(this, bufferedInputStream); 272 } catch (IOException e) { 273 throw new UncheckedIOException(e); 274 } 275 } 276 277 InputStream getInputStream() throws IOException; 278 279 /** 280 * Get the projection type for this entry 281 * @return The projection type 282 * @since 18246 (extracted in xxx) 283 */ 284 Projections getProjectionType(); 285 286 /** 287 * Set the new projection type 288 * @param newProjection The new type 289 */ 290 void setProjectionType(Projections newProjection); 291 } -
new file src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageUtils.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.Dimension; 7 import java.awt.Graphics2D; 8 import java.awt.geom.AffineTransform; 9 import java.awt.image.BufferedImage; 10 import java.io.IOException; 11 import java.io.InputStream; 12 import java.io.UncheckedIOException; 13 import java.net.URI; 14 import java.time.Instant; 15 import java.util.Locale; 16 import java.util.Map; 17 import java.util.function.Consumer; 18 import java.util.stream.Stream; 19 20 import javax.imageio.ImageReadParam; 21 import javax.imageio.ImageReader; 22 23 import com.drew.imaging.jpeg.JpegMetadataReader; 24 import com.drew.imaging.jpeg.JpegProcessingException; 25 import com.drew.imaging.png.PngMetadataReader; 26 import com.drew.imaging.png.PngProcessingException; 27 import com.drew.imaging.tiff.TiffMetadataReader; 28 import com.drew.imaging.tiff.TiffProcessingException; 29 import com.drew.metadata.Directory; 30 import com.drew.metadata.Metadata; 31 import com.drew.metadata.MetadataException; 32 import com.drew.metadata.exif.ExifIFD0Directory; 33 import com.drew.metadata.exif.GpsDirectory; 34 import com.drew.metadata.iptc.IptcDirectory; 35 import com.drew.metadata.jpeg.JpegDirectory; 36 import com.drew.metadata.xmp.XmpDirectory; 37 import org.openstreetmap.josm.data.imagery.street_level.Projections; 38 import org.openstreetmap.josm.tools.ExifReader; 39 import org.openstreetmap.josm.tools.JosmRuntimeException; 40 import org.openstreetmap.josm.tools.Logging; 41 42 /** 43 * Image utilities 44 * @since xxx 45 */ 46 public final class ImageUtils { 47 private ImageUtils() { 48 // Hide constructor 49 } 50 51 /** 52 * Rotate an image, if needed 53 * @param img The image to rotate 54 * @param exifOrientation The exif orientation 55 * @return The rotated image or the original 56 */ 57 public static BufferedImage applyExifRotation(BufferedImage img, Integer exifOrientation) { 58 if (exifOrientation == null || !ExifReader.orientationNeedsCorrection(exifOrientation)) { 59 return img; 60 } 61 boolean switchesDimensions = ExifReader.orientationSwitchesDimensions(exifOrientation); 62 int width = switchesDimensions ? img.getHeight() : img.getWidth(); 63 int height = switchesDimensions ? img.getWidth() : img.getHeight(); 64 BufferedImage rotated = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 65 AffineTransform transform = ExifReader.getRestoreOrientationTransform(exifOrientation, img.getWidth(), img.getHeight()); 66 Graphics2D g = rotated.createGraphics(); 67 g.drawImage(img, transform, null); 68 g.dispose(); 69 return rotated; 70 } 71 72 /** 73 * Common subsampling method 74 * @param reader The image reader 75 * @param target The target area 76 * @return The sampling parameters 77 */ 78 public static ImageReadParam withSubsampling(ImageReader reader, Dimension target) { 79 try { 80 ImageReadParam param = reader.getDefaultReadParam(); 81 Dimension source = new Dimension(reader.getWidth(0), reader.getHeight(0)); 82 if (source.getWidth() > target.getWidth() || source.getHeight() > target.getHeight()) { 83 int subsampling = (int) Math.floor(Math.max( 84 source.getWidth() / target.getWidth(), 85 source.getHeight() / target.getHeight())); 86 param.setSourceSubsampling(subsampling, subsampling, 0, 0); 87 } 88 return param; 89 } catch (IOException e) { 90 throw new UncheckedIOException(e); 91 } 92 } 93 94 /** 95 * Apply exif information from an {@link InputStream} 96 * @param image The image to apply information to 97 * @param inputStream The input stream to read 98 */ 99 public static void applyExif(ImageMetadata image, InputStream inputStream) { 100 Metadata metadata; 101 102 if (image == null || inputStream == null) { 103 return; 104 } 105 106 metadata = getMetadata(image.getImageURI(), inputStream); 107 if (metadata == null) { 108 image.setExifTime(image.getLastModified()); 109 image.setExifCoor(null); 110 image.setPos(null); 111 return; 112 } 113 final String fn = image.getImageURI().toString(); 114 115 IptcDirectory dirIptc = metadata.getFirstDirectoryOfType(IptcDirectory.class); 116 if (dirIptc != null) { 117 ifNotNull(ExifReader.readCaption(dirIptc), image::setIptcCaption); 118 ifNotNull(ExifReader.readHeadline(dirIptc), image::setIptcHeadline); 119 ifNotNull(ExifReader.readKeywords(dirIptc), image::setIptcKeywords); 120 ifNotNull(ExifReader.readObjectName(dirIptc), image::setIptcObjectName); 121 } 122 123 for (XmpDirectory xmpDirectory : metadata.getDirectoriesOfType(XmpDirectory.class)) { 124 Map<String, String> properties = xmpDirectory.getXmpProperties(); 125 final String projectionType = "GPano:ProjectionType"; 126 if (properties.containsKey(projectionType)) { 127 Stream.of(Projections.values()).filter(p -> p.name().equalsIgnoreCase(properties.get(projectionType))) 128 .findFirst().ifPresent(image::setProjectionType); 129 break; 130 } 131 } 132 133 // Changed to silently cope with no time info in exif. One case 134 // of person having time that couldn't be parsed, but valid GPS info 135 Instant time = null; 136 try { 137 time = ExifReader.readInstant(metadata); 138 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) { 139 Logging.warn(ex); 140 } 141 142 if (time == null) { 143 Logging.info(tr("No EXIF time in file \"{0}\". Using last modified date as timestamp.", fn)); 144 time = image.getLastModified(); //use lastModified time if no EXIF time present 145 } 146 image.setExifTime(time); 147 148 final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class); 149 final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 150 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class); 151 152 try { 153 if (dirExif != null && dirExif.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { 154 image.setExifOrientation(dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION)); 155 } 156 } catch (MetadataException ex) { 157 Logging.debug(ex); 158 } 159 160 try { 161 if (dir != null && dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH) && dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) { 162 // there are cases where these do not match width and height stored in dirExif 163 image.setWidth(dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH)); 164 image.setHeight(dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT)); 165 } 166 } catch (MetadataException ex) { 167 Logging.debug(ex); 168 } 169 170 if (dirGps == null || dirGps.getTagCount() <= 1) { 171 image.setExifCoor(null); 172 image.setPos(null); 173 return; 174 } 175 176 ifNotNull(ExifReader.readSpeed(dirGps), image::setSpeed); 177 ifNotNull(ExifReader.readElevation(dirGps), image::setElevation); 178 179 try { 180 image.setExifCoor(ExifReader.readLatLon(dirGps)); 181 image.setPos(image.getExifCoor()); 182 } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271) 183 Logging.error("Error reading EXIF from file: " + ex); 184 image.setExifCoor(null); 185 image.setPos(null); 186 } 187 188 try { 189 ifNotNull(ExifReader.readDirection(dirGps), image::setExifImgDir); 190 } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271) 191 Logging.debug(ex); 192 } 193 194 ifNotNull(dirGps.getGpsDate(), d -> image.setExifGpsTime(d.toInstant())); 195 } 196 197 private static Metadata getMetadata(URI uri, InputStream inputStream) { 198 inputStream.mark(32); 199 final Exception topException; 200 final String fn = uri.toString(); 201 try { 202 // try to parse metadata according to extension 203 String ext = fn.substring(fn.lastIndexOf('.') + 1).toLowerCase(Locale.US); 204 switch (ext) { 205 case "jpg": 206 case "jpeg": 207 return JpegMetadataReader.readMetadata(inputStream); 208 case "tif": 209 case "tiff": 210 return TiffMetadataReader.readMetadata(inputStream); 211 case "png": 212 return PngMetadataReader.readMetadata(inputStream); 213 default: 214 throw new NoMetadataReaderWarning(ext); 215 } 216 } catch (JpegProcessingException | TiffProcessingException | PngProcessingException | IOException 217 | NoMetadataReaderWarning exception) { 218 //try other formats (e.g. JPEG file with .png extension) 219 topException = exception; 220 } 221 try { 222 return JpegMetadataReader.readMetadata(inputStream); 223 } catch (JpegProcessingException | IOException ex1) { 224 Logging.trace(ex1); 225 } 226 try { 227 return TiffMetadataReader.readMetadata(inputStream); 228 } catch (TiffProcessingException | IOException ex2) { 229 Logging.trace(ex2); 230 } 231 232 try { 233 return PngMetadataReader.readMetadata(inputStream); 234 } catch (PngProcessingException | IOException ex3) { 235 Logging.trace(ex3); 236 } 237 Logging.warn(topException); 238 Logging.info(tr("Can''t parse metadata for file \"{0}\". Using last modified date as timestamp.", fn)); 239 return null; 240 } 241 242 private static class NoMetadataReaderWarning extends Exception { 243 NoMetadataReaderWarning(String ext) { 244 super("No metadata reader for format *." + ext); 245 } 246 } 247 248 private static <T> void ifNotNull(T value, Consumer<T> setter) { 249 if (value != null) { 250 setter.accept(value); 251 } 252 } 253 } -
src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
a b 43 43 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 44 44 import org.openstreetmap.josm.gui.ExtendedDialog; 45 45 import org.openstreetmap.josm.gui.MainApplication; 46 import org.openstreetmap.josm.gui.MapFrame; 46 47 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 47 48 import org.openstreetmap.josm.gui.dialogs.DialogsPanel; 48 49 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; … … 105 106 * @return the unique instance 106 107 */ 107 108 public static ImageViewerDialog getInstance() { 108 if (dialog == null) 109 throw new AssertionError("a new instance needs to be created first"); 109 MapFrame map = MainApplication.getMap(); 110 synchronized (ImageViewerDialog.class) { 111 if (dialog == null) 112 createInstance(); 113 if (map != null && map.getToggleDialog(ImageViewerDialog.class) == null) { 114 map.addToggleDialog(dialog); 115 } 116 } 110 117 return dialog; 111 118 } 112 119 -
new file src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 4 import java.io.File; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.UncheckedIOException; 8 import java.net.MalformedURLException; 9 import java.net.URI; 10 import java.nio.file.Files; 11 import java.nio.file.Paths; 12 import java.time.Instant; 13 import java.util.List; 14 import java.util.Objects; 15 import java.util.function.Supplier; 16 17 import org.openstreetmap.josm.data.coor.ILatLon; 18 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 19 import org.openstreetmap.josm.data.imagery.street_level.Projections; 20 import org.openstreetmap.josm.tools.HttpClient; 21 import org.openstreetmap.josm.tools.JosmRuntimeException; 22 23 /** 24 * A remote image entry 25 * @since xxx 26 */ 27 public class RemoteEntry implements IImageEntry<RemoteEntry>, ImageMetadata { 28 private final URI uri; 29 private final Supplier<RemoteEntry> firstImage; 30 private final Supplier<RemoteEntry> nextImage; 31 private final Supplier<RemoteEntry> previousImage; 32 private final Supplier<RemoteEntry> lastImage; 33 private int width; 34 private int height; 35 private ILatLon pos; 36 private Integer exifOrientation; 37 private Double elevation; 38 private Double speed; 39 private Double exifImgDir; 40 private ILatLon exifCoor; 41 private Instant exifTime; 42 private Instant exifGpsTime; 43 private Instant gpsTime; 44 private String iptcObjectName; 45 private List<String> iptcKeywords; 46 private String iptcHeadline; 47 private String iptcCaption; 48 private Projections projection; 49 private String title; 50 51 /** 52 * Create a new remote entry 53 * @param uri The URI to use 54 * @param firstImage first image supplier 55 * @param nextImage next image supplier 56 * @param lastImage last image supplier 57 * @param previousImage previous image supplier 58 */ 59 public RemoteEntry(URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage, 60 Supplier<RemoteEntry> nextImage, Supplier<RemoteEntry> lastImage) { 61 Objects.requireNonNull(uri); 62 Objects.requireNonNull(firstImage); 63 Objects.requireNonNull(previousImage); 64 Objects.requireNonNull(nextImage); 65 Objects.requireNonNull(lastImage); 66 this.uri = uri; 67 this.firstImage = firstImage; 68 this.previousImage = previousImage; 69 this.nextImage = nextImage; 70 this.lastImage = lastImage; 71 } 72 73 @Override 74 public RemoteEntry getNextImage() { 75 return this.nextImage.get(); 76 } 77 78 @Override 79 public RemoteEntry getPreviousImage() { 80 return this.previousImage.get(); 81 } 82 83 @Override 84 public RemoteEntry getFirstImage() { 85 return this.firstImage.get(); 86 } 87 88 @Override 89 public RemoteEntry getLastImage() { 90 return this.lastImage.get(); 91 } 92 93 @Override 94 public String getDisplayName() { 95 return this.title == null ? this.getImageURI().toString() : this.title; 96 } 97 98 @Override 99 public void setWidth(int width) { 100 this.width = width; 101 } 102 103 @Override 104 public void setHeight(int height) { 105 this.height = height; 106 } 107 108 @Override 109 public void setPos(ILatLon pos) { 110 this.pos = pos; 111 } 112 113 @Override 114 public void setSpeed(Double speed) { 115 this.speed = speed; 116 } 117 118 @Override 119 public void setElevation(Double elevation) { 120 this.elevation = elevation; 121 } 122 123 @Override 124 public void setExifOrientation(Integer exifOrientation) { 125 this.exifOrientation = exifOrientation; 126 } 127 128 @Override 129 public void setExifTime(Instant exifTime) { 130 this.exifTime = exifTime; 131 } 132 133 @Override 134 public void setExifGpsTime(Instant exifGpsTime) { 135 this.exifGpsTime = exifGpsTime; 136 } 137 138 @Override 139 public void setGpsTime(Instant gpsTime) { 140 this.gpsTime = gpsTime; 141 } 142 143 @Override 144 public void setExifCoor(ILatLon exifCoor) { 145 this.exifCoor = exifCoor; 146 } 147 148 @Override 149 public void setExifImgDir(Double exifDir) { 150 this.exifImgDir = exifDir; 151 } 152 153 @Override 154 public void setIptcCaption(String iptcCaption) { 155 this.iptcCaption = iptcCaption; 156 } 157 158 @Override 159 public void setIptcHeadline(String iptcHeadline) { 160 this.iptcHeadline = iptcHeadline; 161 } 162 163 @Override 164 public void setIptcKeywords(List<String> iptcKeywords) { 165 this.iptcKeywords = iptcKeywords; 166 } 167 168 @Override 169 public void setIptcObjectName(String iptcObjectName) { 170 this.iptcObjectName = iptcObjectName; 171 } 172 173 @Override 174 public Integer getExifOrientation() { 175 return this.exifOrientation != null ? this.exifOrientation : 1; 176 } 177 178 @Override 179 public File getFile() { 180 return null; 181 } 182 183 @Override 184 public URI getImageURI() { 185 return this.uri; 186 } 187 188 @Override 189 public int getWidth() { 190 return this.width; 191 } 192 193 @Override 194 public int getHeight() { 195 return this.height; 196 } 197 198 @Override 199 public ILatLon getPos() { 200 return this.pos; 201 } 202 203 @Override 204 public Double getSpeed() { 205 return this.speed; 206 } 207 208 @Override 209 public Double getElevation() { 210 return this.elevation; 211 } 212 213 @Override 214 public Double getExifImgDir() { 215 return this.exifImgDir; 216 } 217 218 @Override 219 public Instant getLastModified() { 220 if (this.getImageURI().getScheme().contains("file:")) { 221 try { 222 return Files.getLastModifiedTime(Paths.get(this.getImageURI())).toInstant(); 223 } catch (IOException e) { 224 throw new UncheckedIOException(e); 225 } 226 } 227 try { 228 return Instant.ofEpochMilli(HttpClient.create(this.getImageURI().toURL(), "HEAD").getResponse().getLastModified()); 229 } catch (MalformedURLException e) { 230 throw new JosmRuntimeException(e); 231 } 232 } 233 234 @Override 235 public boolean hasExifTime() { 236 return this.exifTime != null; 237 } 238 239 @Override 240 public Instant getExifGpsInstant() { 241 return this.exifGpsTime; 242 } 243 244 @Override 245 public boolean hasExifGpsTime() { 246 return this.exifGpsTime != null; 247 } 248 249 @Override 250 public ILatLon getExifCoor() { 251 return this.exifCoor; 252 } 253 254 @Override 255 public Instant getExifInstant() { 256 return this.exifTime; 257 } 258 259 @Override 260 public boolean hasGpsTime() { 261 return this.gpsTime != null; 262 } 263 264 @Override 265 public Instant getGpsInstant() { 266 return this.gpsTime; 267 } 268 269 @Override 270 public String getIptcCaption() { 271 return this.iptcCaption; 272 } 273 274 @Override 275 public String getIptcHeadline() { 276 return this.iptcHeadline; 277 } 278 279 @Override 280 public List<String> getIptcKeywords() { 281 return this.iptcKeywords; 282 } 283 284 @Override 285 public String getIptcObjectName() { 286 return this.iptcObjectName; 287 } 288 289 @Override 290 public Projections getProjectionType() { 291 return this.projection; 292 } 293 294 @Override 295 public InputStream getInputStream() throws IOException { 296 URI u = getImageURI(); 297 if (u.getScheme().contains("file")) { 298 return Files.newInputStream(Paths.get(u)); 299 } 300 return HttpClient.create(u.toURL()).connect().getContent(); 301 } 302 303 @Override 304 public void setProjectionType(Projections newProjection) { 305 this.projection = newProjection; 306 } 307 308 /** 309 * Set the display name for this entry 310 * @param text The display name 311 */ 312 public void setDisplayName(String text) { 313 this.title = text; 314 } 315 } -
src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.layer.markerlayer; 3 3 4 import java.awt.BorderLayout; 5 import java.awt.Cursor; 6 import java.awt.GraphicsEnvironment; 7 import java.awt.Image; 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 8 6 import java.awt.event.ActionEvent; 7 import java.net.URISyntaxException; 9 8 import java.net.URL; 9 import java.time.Instant; 10 10 import java.util.Collections; 11 import java.util.function.Supplier; 11 12 12 import javax.swing.Icon;13 import javax.swing.ImageIcon;14 import javax.swing.JDialog;15 import javax.swing.JLabel;16 13 import javax.swing.JOptionPane; 17 import javax.swing.JPanel;18 import javax.swing.JScrollPane;19 import javax.swing.JToggleButton;20 import javax.swing.JViewport;21 14 22 15 import org.openstreetmap.josm.data.coor.LatLon; 23 16 import org.openstreetmap.josm.data.gpx.GpxConstants; 24 17 import org.openstreetmap.josm.data.gpx.GpxLink; 25 18 import org.openstreetmap.josm.data.gpx.WayPoint; 26 import org.openstreetmap.josm.gui.MainApplication; 27 import org.openstreetmap.josm.tools.ImageProvider; 19 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 20 import org.openstreetmap.josm.gui.Notification; 21 import org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog; 22 import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry; 23 import org.openstreetmap.josm.tools.Logging; 24 import org.openstreetmap.josm.tools.Utils; 28 25 29 26 /** 30 27 * Marker representing an image. Uses a special icon, and when clicked, … … 42 39 this.imageUrl = imageUrl; 43 40 } 44 41 45 @Override public void actionPerformed(ActionEvent ev) { 46 final JPanel p = new JPanel(new BorderLayout()); 47 final JScrollPane scroll = new JScrollPane(new JLabel(loadScaledImage(imageUrl, 580))); 48 final JViewport vp = scroll.getViewport(); 49 p.add(scroll, BorderLayout.CENTER); 42 @Override 43 public void actionPerformed(ActionEvent ev) { 44 ImageViewerDialog.getInstance().displayImage(getRemoteEntry()); 45 } 50 46 51 final JToggleButton scale = new JToggleButton(ImageProvider.get("misc", "rectangle")); 47 private RemoteEntry getRemoteEntry() { 48 try { 49 final RemoteEntry remoteEntry = new RemoteEntry(imageUrl.toURI(), getFirstImage(), getPreviousImage(), 50 getNextImage(), getLastImage()); 51 // First, extract EXIF data 52 remoteEntry.extractExif(); 53 // Then, apply information from this point. This may overwrite details from 54 // the exif, but that will (hopefully) be OK. 55 if (Double.isFinite(this.time)) { 56 remoteEntry.setGpsTime(Instant.ofEpochMilli((long) (this.time * 1000))); 57 } 58 if (this.isLatLonKnown()) { 59 remoteEntry.setPos(this); 60 } 61 if (!Utils.isBlank(this.getText())) { 62 remoteEntry.setDisplayName(this.getText()); 63 } 64 return remoteEntry; 65 } catch (URISyntaxException e) { 66 Logging.trace(e); 67 new Notification(tr("Malformed URI: ", this.imageUrl.toExternalForm())).setIcon(JOptionPane.WARNING_MESSAGE).show(); 68 } 69 return null; 70 } 52 71 53 JPanel p2 = new JPanel(); 54 p2.add(scale); 55 p.add(p2, BorderLayout.SOUTH); 56 scale.addActionListener(ev1 -> { 57 p.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 58 if (scale.getModel().isSelected()) { 59 ((JLabel) vp.getView()).setIcon(loadScaledImage(imageUrl, Math.max(vp.getWidth(), vp.getHeight()))); 60 } else { 61 ((JLabel) vp.getView()).setIcon(new ImageIcon(imageUrl)); 72 private Supplier<RemoteEntry> getFirstImage() { 73 for (Marker marker : this.parentLayer.data) { 74 if (marker instanceof ImageMarker) { 75 if (marker == this) { 76 break; 77 } 78 ImageMarker imageMarker = (ImageMarker) marker; 79 return imageMarker::getRemoteEntry; 62 80 } 63 p.setCursor(Cursor.getDefaultCursor());64 });65 scale.setSelected(true);66 JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE);67 if (!GraphicsEnvironment.isHeadless()) {68 JDialog dlg = pane.createDialog(MainApplication.getMainFrame(), imageUrl.toString());69 dlg.setModal(false);70 dlg.toFront();71 dlg.setVisible(true);72 81 } 82 return () -> null; 73 83 } 74 84 75 private static Icon loadScaledImage(URL u, int maxSize) { 76 Image img = new ImageIcon(u).getImage(); 77 int w = img.getWidth(null); 78 int h = img.getHeight(null); 79 if (w > h) { 80 h = (int) Math.round(maxSize*((double) h/w)); 81 w = maxSize; 82 } else { 83 w = (int) Math.round(maxSize*((double) w/h)); 84 h = maxSize; 85 private Supplier<RemoteEntry> getPreviousImage() { 86 int index = this.parentLayer.data.indexOf(this); 87 for (int i = index - 1; i >= 0; i--) { 88 Marker marker = this.parentLayer.data.get(i); 89 if (marker instanceof ImageMarker) { 90 ImageMarker imageMarker = (ImageMarker) marker; 91 return imageMarker::getRemoteEntry; 92 } 85 93 } 86 return new ImageIcon(img.getScaledInstance(w, h, Image.SCALE_SMOOTH));94 return () -> null; 87 95 } 96 private Supplier<RemoteEntry> getNextImage() { 97 int index = this.parentLayer.data.indexOf(this); 98 for (int i = index + 1; i < this.parentLayer.data.size(); i++) { 99 Marker marker = this.parentLayer.data.get(i); 100 if (marker instanceof ImageMarker) { 101 ImageMarker imageMarker = (ImageMarker) marker; 102 return imageMarker::getRemoteEntry; 103 } 104 } 105 return () -> null; 106 } 107 private Supplier<RemoteEntry> getLastImage() { 108 int index = this.parentLayer.data.indexOf(this); 109 for (int i = this.parentLayer.data.size() - 1; i >= index; i--) { 110 Marker marker = this.parentLayer.data.get(i); 111 if (marker instanceof ImageMarker) { 112 if (marker == this) { 113 break; 114 } 115 ImageMarker imageMarker = (ImageMarker) marker; 116 return imageMarker::getRemoteEntry; 117 } 118 } 119 return () -> null; 120 } 88 121 89 122 @Override 90 123 public WayPoint convertToWayPoint() {
