source: josm/trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java@ 17459

Last change on this file since 17459 was 17459, checked in by Don-vip, 3 years ago

fix #20363 - Extract necessary method for QuadBuckets into separate interface (patch by taylor.smock, modified)

  • Property svn:eol-style set to native
File size: 18.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.gpx;
3
4import java.io.File;
5import java.io.IOException;
6import java.util.Date;
7import java.util.List;
8import java.util.Objects;
9import java.util.function.Consumer;
10
11import org.openstreetmap.josm.data.IQuadBucketType;
12import org.openstreetmap.josm.data.coor.CachedLatLon;
13import org.openstreetmap.josm.data.coor.LatLon;
14import org.openstreetmap.josm.data.osm.BBox;
15import org.openstreetmap.josm.tools.ExifReader;
16import org.openstreetmap.josm.tools.JosmRuntimeException;
17import org.openstreetmap.josm.tools.Logging;
18
19import com.drew.imaging.jpeg.JpegMetadataReader;
20import com.drew.lang.CompoundException;
21import com.drew.metadata.Directory;
22import com.drew.metadata.Metadata;
23import com.drew.metadata.MetadataException;
24import com.drew.metadata.exif.ExifIFD0Directory;
25import com.drew.metadata.exif.GpsDirectory;
26import com.drew.metadata.iptc.IptcDirectory;
27import com.drew.metadata.jpeg.JpegDirectory;
28
29/**
30 * Stores info about each image
31 * @since 14205 (extracted from gui.layer.geoimage.ImageEntry)
32 */
33public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType {
34 private File file;
35 private Integer exifOrientation;
36 private LatLon exifCoor;
37 private Double exifImgDir;
38 private Date exifTime;
39 /**
40 * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
41 * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track).
42 * The flag can used to decide for which image file the EXIF GPS data is (re-)written.
43 */
44 private boolean isNewGpsData;
45 /** Temporary source of GPS time if not correlated with GPX track. */
46 private Date exifGpsTime;
47
48 private String iptcCaption;
49 private String iptcHeadline;
50 private List<String> iptcKeywords;
51 private String iptcObjectName;
52
53 /**
54 * The following values are computed from the correlation with the gpx track
55 * or extracted from the image EXIF data.
56 */
57 private CachedLatLon pos;
58 /** Speed in kilometer per hour */
59 private Double speed;
60 /** Elevation (altitude) in meters */
61 private Double elevation;
62 /** The time after correlation with a gpx track */
63 private Date gpsTime;
64
65 private int width;
66 private int height;
67
68 /**
69 * When the correlation dialog is open, we like to show the image position
70 * for the current time offset on the map in real time.
71 * On the other hand, when the user aborts this operation, the old values
72 * should be restored. We have a temporary copy, that overrides
73 * the normal values if it is not null. (This may be not the most elegant
74 * solution for this, but it works.)
75 */
76 private GpxImageEntry tmp;
77
78 /**
79 * Constructs a new {@code GpxImageEntry}.
80 */
81 public GpxImageEntry() {}
82
83 /**
84 * Constructs a new {@code GpxImageEntry} from an existing instance.
85 * @param other existing instance
86 * @since 14624
87 */
88 public GpxImageEntry(GpxImageEntry other) {
89 file = other.file;
90 exifOrientation = other.exifOrientation;
91 exifCoor = other.exifCoor;
92 exifImgDir = other.exifImgDir;
93 exifTime = other.exifTime;
94 isNewGpsData = other.isNewGpsData;
95 exifGpsTime = other.exifGpsTime;
96 pos = other.pos;
97 speed = other.speed;
98 elevation = other.elevation;
99 gpsTime = other.gpsTime;
100 width = other.width;
101 height = other.height;
102 tmp = other.tmp;
103 }
104
105 /**
106 * Constructs a new {@code GpxImageEntry}.
107 * @param file Path to image file on disk
108 */
109 public GpxImageEntry(File file) {
110 setFile(file);
111 }
112
113 /**
114 * Returns width of the image this GpxImageEntry represents.
115 * @return width of the image this GpxImageEntry represents
116 * @since 13220
117 */
118 public int getWidth() {
119 return width;
120 }
121
122 /**
123 * Returns height of the image this GpxImageEntry represents.
124 * @return height of the image this GpxImageEntry represents
125 * @since 13220
126 */
127 public int getHeight() {
128 return height;
129 }
130
131 /**
132 * Returns the position value. The position value from the temporary copy
133 * is returned if that copy exists.
134 * @return the position value
135 */
136 public CachedLatLon getPos() {
137 if (tmp != null)
138 return tmp.pos;
139 return pos;
140 }
141
142 /**
143 * Returns the speed value. The speed value from the temporary copy is
144 * returned if that copy exists.
145 * @return the speed value
146 */
147 public Double getSpeed() {
148 if (tmp != null)
149 return tmp.speed;
150 return speed;
151 }
152
153 /**
154 * Returns the elevation value. The elevation value from the temporary
155 * copy is returned if that copy exists.
156 * @return the elevation value
157 */
158 public Double getElevation() {
159 if (tmp != null)
160 return tmp.elevation;
161 return elevation;
162 }
163
164 /**
165 * Returns the GPS time value. The GPS time value from the temporary copy
166 * is returned if that copy exists.
167 * @return the GPS time value
168 */
169 public Date getGpsTime() {
170 if (tmp != null)
171 return getDefensiveDate(tmp.gpsTime);
172 return getDefensiveDate(gpsTime);
173 }
174
175 /**
176 * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
177 * @return {@code true} if this entry has a GPS time
178 * @since 6450
179 */
180 public boolean hasGpsTime() {
181 return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
182 }
183
184 /**
185 * Returns associated file.
186 * @return associated file
187 */
188 public File getFile() {
189 return file;
190 }
191
192 /**
193 * Returns EXIF orientation
194 * @return EXIF orientation
195 */
196 public Integer getExifOrientation() {
197 return exifOrientation != null ? exifOrientation : 1;
198 }
199
200 /**
201 * Returns EXIF time
202 * @return EXIF time
203 */
204 public Date getExifTime() {
205 return getDefensiveDate(exifTime);
206 }
207
208 /**
209 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
210 * @return {@code true} if this entry has a EXIF time
211 * @since 6450
212 */
213 public boolean hasExifTime() {
214 return exifTime != null;
215 }
216
217 /**
218 * Returns the EXIF GPS time.
219 * @return the EXIF GPS time
220 * @since 6392
221 */
222 public Date getExifGpsTime() {
223 return getDefensiveDate(exifGpsTime);
224 }
225
226 /**
227 * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
228 * @return {@code true} if this entry has a EXIF GPS time
229 * @since 6450
230 */
231 public boolean hasExifGpsTime() {
232 return exifGpsTime != null;
233 }
234
235 private static Date getDefensiveDate(Date date) {
236 if (date == null)
237 return null;
238 return new Date(date.getTime());
239 }
240
241 public LatLon getExifCoor() {
242 return exifCoor;
243 }
244
245 public Double getExifImgDir() {
246 if (tmp != null)
247 return tmp.exifImgDir;
248 return exifImgDir;
249 }
250
251 /**
252 * Sets the width of this GpxImageEntry.
253 * @param width set the width of this GpxImageEntry
254 * @since 13220
255 */
256 public void setWidth(int width) {
257 this.width = width;
258 }
259
260 /**
261 * Sets the height of this GpxImageEntry.
262 * @param height set the height of this GpxImageEntry
263 * @since 13220
264 */
265 public void setHeight(int height) {
266 this.height = height;
267 }
268
269 /**
270 * Sets the position.
271 * @param pos cached position
272 */
273 public void setPos(CachedLatLon pos) {
274 this.pos = pos;
275 }
276
277 /**
278 * Sets the position.
279 * @param pos position (will be cached)
280 */
281 public void setPos(LatLon pos) {
282 setPos(pos != null ? new CachedLatLon(pos) : null);
283 }
284
285 /**
286 * Sets the speed.
287 * @param speed speed
288 */
289 public void setSpeed(Double speed) {
290 this.speed = speed;
291 }
292
293 /**
294 * Sets the elevation.
295 * @param elevation elevation
296 */
297 public void setElevation(Double elevation) {
298 this.elevation = elevation;
299 }
300
301 /**
302 * Sets associated file.
303 * @param file associated file
304 */
305 public void setFile(File file) {
306 this.file = file;
307 }
308
309 /**
310 * Sets EXIF orientation.
311 * @param exifOrientation EXIF orientation
312 */
313 public void setExifOrientation(Integer exifOrientation) {
314 this.exifOrientation = exifOrientation;
315 }
316
317 /**
318 * Sets EXIF time.
319 * @param exifTime EXIF time
320 */
321 public void setExifTime(Date exifTime) {
322 this.exifTime = getDefensiveDate(exifTime);
323 }
324
325 /**
326 * Sets the EXIF GPS time.
327 * @param exifGpsTime the EXIF GPS time
328 * @since 6392
329 */
330 public void setExifGpsTime(Date exifGpsTime) {
331 this.exifGpsTime = getDefensiveDate(exifGpsTime);
332 }
333
334 public void setGpsTime(Date gpsTime) {
335 this.gpsTime = getDefensiveDate(gpsTime);
336 }
337
338 public void setExifCoor(LatLon exifCoor) {
339 this.exifCoor = exifCoor;
340 }
341
342 public void setExifImgDir(Double exifDir) {
343 this.exifImgDir = exifDir;
344 }
345
346 /**
347 * Sets the IPTC caption.
348 * @param iptcCaption the IPTC caption
349 * @since 15219
350 */
351 public void setIptcCaption(String iptcCaption) {
352 this.iptcCaption = iptcCaption;
353 }
354
355 /**
356 * Sets the IPTC headline.
357 * @param iptcHeadline the IPTC headline
358 * @since 15219
359 */
360 public void setIptcHeadline(String iptcHeadline) {
361 this.iptcHeadline = iptcHeadline;
362 }
363
364 /**
365 * Sets the IPTC keywords.
366 * @param iptcKeywords the IPTC keywords
367 * @since 15219
368 */
369 public void setIptcKeywords(List<String> iptcKeywords) {
370 this.iptcKeywords = iptcKeywords;
371 }
372
373 /**
374 * Sets the IPTC object name.
375 * @param iptcObjectName the IPTC object name
376 * @since 15219
377 */
378 public void setIptcObjectName(String iptcObjectName) {
379 this.iptcObjectName = iptcObjectName;
380 }
381
382 /**
383 * Returns the IPTC caption.
384 * @return the IPTC caption
385 * @since 15219
386 */
387 public String getIptcCaption() {
388 return iptcCaption;
389 }
390
391 /**
392 * Returns the IPTC headline.
393 * @return the IPTC headline
394 * @since 15219
395 */
396 public String getIptcHeadline() {
397 return iptcHeadline;
398 }
399
400 /**
401 * Returns the IPTC keywords.
402 * @return the IPTC keywords
403 * @since 15219
404 */
405 public List<String> getIptcKeywords() {
406 return iptcKeywords;
407 }
408
409 /**
410 * Returns the IPTC object name.
411 * @return the IPTC object name
412 * @since 15219
413 */
414 public String getIptcObjectName() {
415 return iptcObjectName;
416 }
417
418 @Override
419 public int compareTo(GpxImageEntry image) {
420 if (exifTime != null && image.exifTime != null)
421 return exifTime.compareTo(image.exifTime);
422 else if (exifTime == null && image.exifTime == null)
423 return 0;
424 else if (exifTime == null)
425 return -1;
426 else
427 return 1;
428 }
429
430 @Override
431 public int hashCode() {
432 return Objects.hash(height, width, isNewGpsData,
433 elevation, exifCoor, exifGpsTime, exifImgDir, exifOrientation, exifTime,
434 iptcCaption, iptcHeadline, iptcKeywords, iptcObjectName,
435 file, gpsTime, pos, speed, tmp);
436 }
437
438 @Override
439 public boolean equals(Object obj) {
440 if (this == obj)
441 return true;
442 if (obj == null || getClass() != obj.getClass())
443 return false;
444 GpxImageEntry other = (GpxImageEntry) obj;
445 return height == other.height
446 && width == other.width
447 && isNewGpsData == other.isNewGpsData
448 && Objects.equals(elevation, other.elevation)
449 && Objects.equals(exifCoor, other.exifCoor)
450 && Objects.equals(exifGpsTime, other.exifGpsTime)
451 && Objects.equals(exifImgDir, other.exifImgDir)
452 && Objects.equals(exifOrientation, other.exifOrientation)
453 && Objects.equals(exifTime, other.exifTime)
454 && Objects.equals(iptcCaption, other.iptcCaption)
455 && Objects.equals(iptcHeadline, other.iptcHeadline)
456 && Objects.equals(iptcKeywords, other.iptcKeywords)
457 && Objects.equals(iptcObjectName, other.iptcObjectName)
458 && Objects.equals(file, other.file)
459 && Objects.equals(gpsTime, other.gpsTime)
460 && Objects.equals(pos, other.pos)
461 && Objects.equals(speed, other.speed)
462 && Objects.equals(tmp, other.tmp);
463 }
464
465 /**
466 * Make a fresh copy and save it in the temporary variable. Use
467 * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable
468 * is not needed anymore.
469 */
470 public void createTmp() {
471 tmp = new GpxImageEntry(this);
472 tmp.tmp = null;
473 }
474
475 /**
476 * Get temporary variable that is used for real time parameter
477 * adjustments. The temporary variable is created if it does not exist
478 * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary
479 * variable is not needed anymore.
480 * @return temporary variable
481 */
482 public GpxImageEntry getTmp() {
483 if (tmp == null) {
484 createTmp();
485 }
486 return tmp;
487 }
488
489 /**
490 * Copy the values from the temporary variable to the main instance. The
491 * temporary variable is deleted.
492 * @see #discardTmp()
493 */
494 public void applyTmp() {
495 if (tmp != null) {
496 pos = tmp.pos;
497 speed = tmp.speed;
498 elevation = tmp.elevation;
499 gpsTime = tmp.gpsTime;
500 exifImgDir = tmp.exifImgDir;
501 isNewGpsData = tmp.isNewGpsData;
502 tmp = null;
503 }
504 }
505
506 /**
507 * Delete the temporary variable. Temporary modifications are lost.
508 * @see #applyTmp()
509 */
510 public void discardTmp() {
511 tmp = null;
512 }
513
514 /**
515 * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
516 * @return {@code true} if it has been tagged
517 */
518 public boolean isTagged() {
519 return pos != null;
520 }
521
522 /**
523 * String representation. (only partial info)
524 */
525 @Override
526 public String toString() {
527 return file.getName()+": "+
528 "pos = "+pos+" | "+
529 "exifCoor = "+exifCoor+" | "+
530 (tmp == null ? " tmp==null" :
531 " [tmp] pos = "+tmp.pos);
532 }
533
534 /**
535 * Indicates that the image has new GPS data.
536 * That flag is set by new GPS data providers. It is used e.g. by the photo_geotagging plugin
537 * to decide for which image file the EXIF GPS data needs to be (re-)written.
538 * @since 6392
539 */
540 public void flagNewGpsData() {
541 isNewGpsData = true;
542 }
543
544 @Override
545 public BBox getBBox() {
546 // new BBox(LatLon) is null safe.
547 return new BBox(this.getExifCoor());
548 }
549
550 /**
551 * Remove the flag that indicates new GPS data.
552 * The flag is cleared by a new GPS data consumer.
553 */
554 public void unflagNewGpsData() {
555 isNewGpsData = false;
556 }
557
558 /**
559 * Queries whether the GPS data changed. The flag value from the temporary
560 * copy is returned if that copy exists.
561 * @return {@code true} if GPS data changed, {@code false} otherwise
562 * @since 6392
563 */
564 public boolean hasNewGpsData() {
565 if (tmp != null)
566 return tmp.isNewGpsData;
567 return isNewGpsData;
568 }
569
570 /**
571 * Extract GPS metadata from image EXIF. Has no effect if the image file is not set
572 *
573 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes
574 * @since 9270
575 */
576 public void extractExif() {
577
578 Metadata metadata;
579
580 if (file == null) {
581 return;
582 }
583
584 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;
592 }
593
594 // Changed to silently cope with no time info in exif. One case
595 // of person having time that couldn't be parsed, but valid GPS info
596 try {
597 setExifTime(ExifReader.readTime(metadata));
598 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
599 Logging.warn(ex);
600 setExifTime(null);
601 }
602
603 final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
604 final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
605 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
606
607 try {
608 if (dirExif != null) {
609 setExifOrientation(dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION));
610 }
611 } catch (MetadataException ex) {
612 Logging.debug(ex);
613 }
614
615 try {
616 if (dir != null) {
617 // there are cases where these do not match width and height stored in dirExif
618 setWidth(dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH));
619 setHeight(dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT));
620 }
621 } catch (MetadataException ex) {
622 Logging.debug(ex);
623 }
624
625 if (dirGps == null) {
626 setExifCoor(null);
627 setPos(null);
628 return;
629 }
630
631 ifNotNull(ExifReader.readSpeed(dirGps), this::setSpeed);
632 ifNotNull(ExifReader.readElevation(dirGps), this::setElevation);
633
634 try {
635 setExifCoor(ExifReader.readLatLon(dirGps));
636 setPos(getExifCoor());
637 } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
638 Logging.error("Error reading EXIF from file: " + ex);
639 setExifCoor(null);
640 setPos(null);
641 }
642
643 try {
644 ifNotNull(ExifReader.readDirection(dirGps), this::setExifImgDir);
645 } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
646 Logging.debug(ex);
647 }
648
649 ifNotNull(dirGps.getGpsDate(), this::setExifGpsTime);
650
651 IptcDirectory dirIptc = metadata.getFirstDirectoryOfType(IptcDirectory.class);
652 if (dirIptc != null) {
653 ifNotNull(ExifReader.readCaption(dirIptc), this::setIptcCaption);
654 ifNotNull(ExifReader.readHeadline(dirIptc), this::setIptcHeadline);
655 ifNotNull(ExifReader.readKeywords(dirIptc), this::setIptcKeywords);
656 ifNotNull(ExifReader.readObjectName(dirIptc), this::setIptcObjectName);
657 }
658 }
659
660 private static <T> void ifNotNull(T value, Consumer<T> setter) {
661 if (value != null) {
662 setter.accept(value);
663 }
664 }
665}
Note: See TracBrowser for help on using the repository browser.