source: josm/trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java@ 11620

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

checkstyle - enable CatchParameterName rule

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.geoimage;
3
4import java.awt.Image;
5import java.io.File;
6import java.io.IOException;
7import java.util.Collections;
8import java.util.Date;
9
10import org.openstreetmap.josm.Main;
11import org.openstreetmap.josm.data.SystemOfMeasurement;
12import org.openstreetmap.josm.data.coor.CachedLatLon;
13import org.openstreetmap.josm.data.coor.LatLon;
14import org.openstreetmap.josm.tools.ExifReader;
15
16import com.drew.imaging.jpeg.JpegMetadataReader;
17import com.drew.lang.CompoundException;
18import com.drew.metadata.Directory;
19import com.drew.metadata.Metadata;
20import com.drew.metadata.MetadataException;
21import com.drew.metadata.exif.ExifIFD0Directory;
22import com.drew.metadata.exif.GpsDirectory;
23
24/**
25 * Stores info about each image
26 */
27public final class ImageEntry implements Comparable<ImageEntry>, Cloneable {
28 private File file;
29 private Integer exifOrientation;
30 private LatLon exifCoor;
31 private Double exifImgDir;
32 private Date exifTime;
33 /**
34 * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
35 * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track).
36 * The flag can used to decide for which image file the EXIF GPS data is (re-)written.
37 */
38 private boolean isNewGpsData;
39 /** Temporary source of GPS time if not correlated with GPX track. */
40 private Date exifGpsTime;
41 private Image thumbnail;
42
43 /**
44 * The following values are computed from the correlation with the gpx track
45 * or extracted from the image EXIF data.
46 */
47 private CachedLatLon pos;
48 /** Speed in kilometer per hour */
49 private Double speed;
50 /** Elevation (altitude) in meters */
51 private Double elevation;
52 /** The time after correlation with a gpx track */
53 private Date gpsTime;
54
55 /**
56 * When the correlation dialog is open, we like to show the image position
57 * for the current time offset on the map in real time.
58 * On the other hand, when the user aborts this operation, the old values
59 * should be restored. We have a temporary copy, that overrides
60 * the normal values if it is not null. (This may be not the most elegant
61 * solution for this, but it works.)
62 */
63 ImageEntry tmp;
64
65 /**
66 * Constructs a new {@code ImageEntry}.
67 */
68 public ImageEntry() {}
69
70 /**
71 * Constructs a new {@code ImageEntry}.
72 * @param file Path to image file on disk
73 */
74 public ImageEntry(File file) {
75 setFile(file);
76 }
77
78 /**
79 * Returns the position value. The position value from the temporary copy
80 * is returned if that copy exists.
81 * @return the position value
82 */
83 public CachedLatLon getPos() {
84 if (tmp != null)
85 return tmp.pos;
86 return pos;
87 }
88
89 /**
90 * Returns the speed value. The speed value from the temporary copy is
91 * returned if that copy exists.
92 * @return the speed value
93 */
94 public Double getSpeed() {
95 if (tmp != null)
96 return tmp.speed;
97 return speed;
98 }
99
100 /**
101 * Returns the elevation value. The elevation value from the temporary
102 * copy is returned if that copy exists.
103 * @return the elevation value
104 */
105 public Double getElevation() {
106 if (tmp != null)
107 return tmp.elevation;
108 return elevation;
109 }
110
111 /**
112 * Returns the GPS time value. The GPS time value from the temporary copy
113 * is returned if that copy exists.
114 * @return the GPS time value
115 */
116 public Date getGpsTime() {
117 if (tmp != null)
118 return getDefensiveDate(tmp.gpsTime);
119 return getDefensiveDate(gpsTime);
120 }
121
122 /**
123 * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
124 * @return {@code true} if this entry has a GPS time
125 * @since 6450
126 */
127 public boolean hasGpsTime() {
128 return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
129 }
130
131 /**
132 * Returns associated file.
133 * @return associated file
134 */
135 public File getFile() {
136 return file;
137 }
138
139 /**
140 * Returns EXIF orientation
141 * @return EXIF orientation
142 */
143 public Integer getExifOrientation() {
144 return exifOrientation;
145 }
146
147 /**
148 * Returns EXIF time
149 * @return EXIF time
150 */
151 public Date getExifTime() {
152 return getDefensiveDate(exifTime);
153 }
154
155 /**
156 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
157 * @return {@code true} if this entry has a EXIF time
158 * @since 6450
159 */
160 public boolean hasExifTime() {
161 return exifTime != null;
162 }
163
164 /**
165 * Returns the EXIF GPS time.
166 * @return the EXIF GPS time
167 * @since 6392
168 */
169 public Date getExifGpsTime() {
170 return getDefensiveDate(exifGpsTime);
171 }
172
173 /**
174 * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
175 * @return {@code true} if this entry has a EXIF GPS time
176 * @since 6450
177 */
178 public boolean hasExifGpsTime() {
179 return exifGpsTime != null;
180 }
181
182 private static Date getDefensiveDate(Date date) {
183 if (date == null)
184 return null;
185 return new Date(date.getTime());
186 }
187
188 public LatLon getExifCoor() {
189 return exifCoor;
190 }
191
192 public Double getExifImgDir() {
193 if (tmp != null)
194 return tmp.exifImgDir;
195 return exifImgDir;
196 }
197
198 /**
199 * Determines whether a thumbnail is set
200 * @return {@code true} if a thumbnail is set
201 */
202 public boolean hasThumbnail() {
203 return thumbnail != null;
204 }
205
206 /**
207 * Returns the thumbnail.
208 * @return the thumbnail
209 */
210 public Image getThumbnail() {
211 return thumbnail;
212 }
213
214 /**
215 * Sets the thumbnail.
216 * @param thumbnail thumbnail
217 */
218 public void setThumbnail(Image thumbnail) {
219 this.thumbnail = thumbnail;
220 }
221
222 /**
223 * Loads the thumbnail if it was not loaded yet.
224 * @see ThumbsLoader
225 */
226 public void loadThumbnail() {
227 if (thumbnail == null) {
228 new ThumbsLoader(Collections.singleton(this)).run();
229 }
230 }
231
232 /**
233 * Sets the position.
234 * @param pos cached position
235 */
236 public void setPos(CachedLatLon pos) {
237 this.pos = pos;
238 }
239
240 /**
241 * Sets the position.
242 * @param pos position (will be cached)
243 */
244 public void setPos(LatLon pos) {
245 setPos(pos != null ? new CachedLatLon(pos) : null);
246 }
247
248 /**
249 * Sets the speed.
250 * @param speed speed
251 */
252 public void setSpeed(Double speed) {
253 this.speed = speed;
254 }
255
256 /**
257 * Sets the elevation.
258 * @param elevation elevation
259 */
260 public void setElevation(Double elevation) {
261 this.elevation = elevation;
262 }
263
264 /**
265 * Sets associated file.
266 * @param file associated file
267 */
268 public void setFile(File file) {
269 this.file = file;
270 }
271
272 /**
273 * Sets EXIF orientation.
274 * @param exifOrientation EXIF orientation
275 */
276 public void setExifOrientation(Integer exifOrientation) {
277 this.exifOrientation = exifOrientation;
278 }
279
280 /**
281 * Sets EXIF time.
282 * @param exifTime EXIF time
283 */
284 public void setExifTime(Date exifTime) {
285 this.exifTime = getDefensiveDate(exifTime);
286 }
287
288 /**
289 * Sets the EXIF GPS time.
290 * @param exifGpsTime the EXIF GPS time
291 * @since 6392
292 */
293 public void setExifGpsTime(Date exifGpsTime) {
294 this.exifGpsTime = getDefensiveDate(exifGpsTime);
295 }
296
297 public void setGpsTime(Date gpsTime) {
298 this.gpsTime = getDefensiveDate(gpsTime);
299 }
300
301 public void setExifCoor(LatLon exifCoor) {
302 this.exifCoor = exifCoor;
303 }
304
305 public void setExifImgDir(Double exifDir) {
306 this.exifImgDir = exifDir;
307 }
308
309 @Override
310 public ImageEntry clone() {
311 try {
312 return (ImageEntry) super.clone();
313 } catch (CloneNotSupportedException e) {
314 throw new IllegalStateException(e);
315 }
316 }
317
318 @Override
319 public int compareTo(ImageEntry image) {
320 if (exifTime != null && image.exifTime != null)
321 return exifTime.compareTo(image.exifTime);
322 else if (exifTime == null && image.exifTime == null)
323 return 0;
324 else if (exifTime == null)
325 return -1;
326 else
327 return 1;
328 }
329
330 /**
331 * Make a fresh copy and save it in the temporary variable. Use
332 * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable
333 * is not needed anymore.
334 */
335 public void createTmp() {
336 tmp = clone();
337 tmp.tmp = null;
338 }
339
340 /**
341 * Get temporary variable that is used for real time parameter
342 * adjustments. The temporary variable is created if it does not exist
343 * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary
344 * variable is not needed anymore.
345 * @return temporary variable
346 */
347 public ImageEntry getTmp() {
348 if (tmp == null) {
349 createTmp();
350 }
351 return tmp;
352 }
353
354 /**
355 * Copy the values from the temporary variable to the main instance. The
356 * temporary variable is deleted.
357 * @see #discardTmp()
358 */
359 public void applyTmp() {
360 if (tmp != null) {
361 pos = tmp.pos;
362 speed = tmp.speed;
363 elevation = tmp.elevation;
364 gpsTime = tmp.gpsTime;
365 exifImgDir = tmp.exifImgDir;
366 tmp = null;
367 }
368 }
369
370 /**
371 * Delete the temporary variable. Temporary modifications are lost.
372 * @see #applyTmp()
373 */
374 public void discardTmp() {
375 tmp = null;
376 }
377
378 /**
379 * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
380 * @return {@code true} if it has been tagged
381 */
382 public boolean isTagged() {
383 return pos != null;
384 }
385
386 /**
387 * String representation. (only partial info)
388 */
389 @Override
390 public String toString() {
391 return file.getName()+": "+
392 "pos = "+pos+" | "+
393 "exifCoor = "+exifCoor+" | "+
394 (tmp == null ? " tmp==null" :
395 " [tmp] pos = "+tmp.pos);
396 }
397
398 /**
399 * Indicates that the image has new GPS data.
400 * That flag is set by new GPS data providers. It is used e.g. by the photo_geotagging plugin
401 * to decide for which image file the EXIF GPS data needs to be (re-)written.
402 * @since 6392
403 */
404 public void flagNewGpsData() {
405 isNewGpsData = true;
406 }
407
408 /**
409 * Remove the flag that indicates new GPS data.
410 * The flag is cleared by a new GPS data consumer.
411 */
412 public void unflagNewGpsData() {
413 isNewGpsData = false;
414 }
415
416 /**
417 * Queries whether the GPS data changed.
418 * @return {@code true} if GPS data changed, {@code false} otherwise
419 * @since 6392
420 */
421 public boolean hasNewGpsData() {
422 return isNewGpsData;
423 }
424
425 /**
426 * Extract GPS metadata from image EXIF. Has no effect if the image file is not set
427 *
428 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes
429 * @since 9270
430 */
431 public void extractExif() {
432
433 Metadata metadata;
434 Directory dirExif;
435 GpsDirectory dirGps;
436
437 if (file == null) {
438 return;
439 }
440
441 // Changed to silently cope with no time info in exif. One case
442 // of person having time that couldn't be parsed, but valid GPS info
443 try {
444 setExifTime(ExifReader.readTime(file));
445 } catch (RuntimeException ex) {
446 Main.warn(ex);
447 setExifTime(null);
448 }
449
450 try {
451 metadata = JpegMetadataReader.readMetadata(file);
452 dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
453 dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
454 } catch (CompoundException | IOException ex) {
455 Main.warn(ex);
456 setExifCoor(null);
457 setPos(null);
458 return;
459 }
460
461 try {
462 if (dirExif != null) {
463 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);
464 setExifOrientation(orientation);
465 }
466 } catch (MetadataException ex) {
467 Main.debug(ex);
468 }
469
470 if (dirGps == null) {
471 setExifCoor(null);
472 setPos(null);
473 return;
474 }
475
476 try {
477 double speed = dirGps.getDouble(GpsDirectory.TAG_SPEED);
478 String speedRef = dirGps.getString(GpsDirectory.TAG_SPEED_REF);
479 if ("M".equalsIgnoreCase(speedRef)) {
480 // miles per hour
481 speed *= SystemOfMeasurement.IMPERIAL.bValue / 1000;
482 } else if ("N".equalsIgnoreCase(speedRef)) {
483 // knots == nautical miles per hour
484 speed *= SystemOfMeasurement.NAUTICAL_MILE.bValue / 1000;
485 }
486 // default is K (km/h)
487 setSpeed(speed);
488 } catch (MetadataException ex) {
489 Main.debug(ex);
490 }
491
492 try {
493 double ele = dirGps.getDouble(GpsDirectory.TAG_ALTITUDE);
494 int d = dirGps.getInt(GpsDirectory.TAG_ALTITUDE_REF);
495 if (d == 1) {
496 ele *= -1;
497 }
498 setElevation(ele);
499 } catch (MetadataException ex) {
500 Main.debug(ex);
501 }
502
503 try {
504 LatLon latlon = ExifReader.readLatLon(dirGps);
505 setExifCoor(latlon);
506 setPos(getExifCoor());
507
508 } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
509 Main.error("Error reading EXIF from file: " + ex);
510 setExifCoor(null);
511 setPos(null);
512 }
513
514 try {
515 Double direction = ExifReader.readDirection(dirGps);
516 if (direction != null) {
517 setExifImgDir(direction);
518 }
519 } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
520 Main.debug(ex);
521 }
522
523 final Date gpsDate = dirGps.getGpsDate();
524 if (gpsDate != null) {
525 setExifGpsTime(gpsDate);
526 }
527 }
528}
Note: See TracBrowser for help on using the repository browser.