001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.gui.layer.geoimage; 003 004import java.awt.Image; 005import java.io.File; 006import java.util.Date; 007 008import org.openstreetmap.josm.data.coor.CachedLatLon; 009import org.openstreetmap.josm.data.coor.LatLon; 010 011/** 012 * Stores info about each image 013 */ 014final public class ImageEntry implements Comparable<ImageEntry>, Cloneable { 015 private File file; 016 private Integer exifOrientation; 017 private LatLon exifCoor; 018 private Double exifImgDir; 019 private Date exifTime; 020 /** 021 * Flag isNewGpsData indicates that the GPS data of the image is new or has changed. 022 * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track). 023 * The flag can used to decide for which image file the EXIF GPS data is (re-)written. 024 */ 025 private boolean isNewGpsData = false; 026 /** Temporary source of GPS time if not correlated with GPX track. */ 027 private Date exifGpsTime = null; 028 Image thumbnail; 029 030 /** 031 * The following values are computed from the correlation with the gpx track 032 * or extracted from the image EXIF data. 033 */ 034 private CachedLatLon pos; 035 /** Speed in kilometer per second */ 036 private Double speed; 037 /** Elevation (altitude) in meters */ 038 private Double elevation; 039 /** The time after correlation with a gpx track */ 040 private Date gpsTime; 041 042 /** 043 * When the correlation dialog is open, we like to show the image position 044 * for the current time offset on the map in real time. 045 * On the other hand, when the user aborts this operation, the old values 046 * should be restored. We have a temprary copy, that overrides 047 * the normal values if it is not null. (This may be not the most elegant 048 * solution for this, but it works.) 049 */ 050 ImageEntry tmp; 051 052 /** 053 * getter methods that refer to the temporary value 054 */ 055 public CachedLatLon getPos() { 056 if (tmp != null) 057 return tmp.pos; 058 return pos; 059 } 060 public Double getSpeed() { 061 if (tmp != null) 062 return tmp.speed; 063 return speed; 064 } 065 public Double getElevation() { 066 if (tmp != null) 067 return tmp.elevation; 068 return elevation; 069 } 070 071 public Date getGpsTime() { 072 if (tmp != null) 073 return getDefensiveDate(tmp.gpsTime); 074 return getDefensiveDate(gpsTime); 075 } 076 077 /** 078 * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy. 079 * @return {@code true} if this entry has a GPS time 080 * @since 6450 081 */ 082 public final boolean hasGpsTime() { 083 return (tmp != null && tmp.gpsTime != null) || gpsTime != null; 084 } 085 086 /** 087 * other getter methods 088 */ 089 public File getFile() { 090 return file; 091 } 092 public Integer getExifOrientation() { 093 return exifOrientation; 094 } 095 public Date getExifTime() { 096 return getDefensiveDate(exifTime); 097 } 098 099 /** 100 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy. 101 * @return {@code true} if this entry has a EXIF time 102 * @since 6450 103 */ 104 public final boolean hasExifTime() { 105 return exifTime != null; 106 } 107 108 /** 109 * Returns the EXIF GPS time. 110 * @return the EXIF GPS time 111 * @since 6392 112 */ 113 public final Date getExifGpsTime() { 114 return getDefensiveDate(exifGpsTime); 115 } 116 117 /** 118 * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy. 119 * @return {@code true} if this entry has a EXIF GPS time 120 * @since 6450 121 */ 122 public final boolean hasExifGpsTime() { 123 return exifGpsTime != null; 124 } 125 126 private static Date getDefensiveDate(Date date) { 127 if (date == null) 128 return null; 129 return new Date(date.getTime()); 130 } 131 132 public LatLon getExifCoor() { 133 return exifCoor; 134 } 135 public Double getExifImgDir() { 136 return exifImgDir; 137 } 138 139 public boolean hasThumbnail() { 140 return thumbnail != null; 141 } 142 143 /** 144 * setter methods 145 */ 146 public void setPos(CachedLatLon pos) { 147 this.pos = pos; 148 } 149 public void setPos(LatLon pos) { 150 this.pos = new CachedLatLon(pos); 151 } 152 public void setSpeed(Double speed) { 153 this.speed = speed; 154 } 155 public void setElevation(Double elevation) { 156 this.elevation = elevation; 157 } 158 public void setFile(File file) { 159 this.file = file; 160 } 161 public void setExifOrientation(Integer exifOrientation) { 162 this.exifOrientation = exifOrientation; 163 } 164 public void setExifTime(Date exifTime) { 165 this.exifTime = getDefensiveDate(exifTime); 166 } 167 168 /** 169 * Sets the EXIF GPS time. 170 * @param exifGpsTime the EXIF GPS time 171 * @since 6392 172 */ 173 public final void setExifGpsTime(Date exifGpsTime) { 174 this.exifGpsTime = getDefensiveDate(exifGpsTime); 175 } 176 177 public void setGpsTime(Date gpsTime) { 178 this.gpsTime = getDefensiveDate(gpsTime); 179 } 180 public void setExifCoor(LatLon exifCoor) { 181 this.exifCoor = exifCoor; 182 } 183 public void setExifImgDir(double exifDir) { 184 this.exifImgDir = exifDir; 185 } 186 187 @Override 188 public ImageEntry clone() { 189 Object c; 190 try { 191 c = super.clone(); 192 } catch (CloneNotSupportedException e) { 193 throw new RuntimeException(); 194 } 195 return (ImageEntry) c; 196 } 197 198 @Override 199 public int compareTo(ImageEntry image) { 200 if (exifTime != null && image.exifTime != null) 201 return exifTime.compareTo(image.exifTime); 202 else if (exifTime == null && image.exifTime == null) 203 return 0; 204 else if (exifTime == null) 205 return -1; 206 else 207 return 1; 208 } 209 210 /** 211 * Make a fresh copy and save it in the temporary variable. 212 */ 213 public void cleanTmp() { 214 tmp = clone(); 215 tmp.setPos(null); 216 tmp.tmp = null; 217 } 218 219 /** 220 * Copy the values from the temporary variable to the main instance. 221 */ 222 public void applyTmp() { 223 if (tmp != null) { 224 pos = tmp.pos; 225 speed = tmp.speed; 226 elevation = tmp.elevation; 227 gpsTime = tmp.gpsTime; 228 tmp = null; 229 } 230 } 231 232 /** 233 * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif 234 */ 235 public boolean isTagged() { 236 return pos != null; 237 } 238 239 /** 240 * String representation. (only partial info) 241 */ 242 @Override 243 public String toString() { 244 String result = file.getName()+": "+ 245 "pos = "+pos+" | "+ 246 "exifCoor = "+exifCoor+" | "+ 247 (tmp == null ? " tmp==null" : 248 " [tmp] pos = "+tmp.pos+""); 249 return result; 250 } 251 252 /** 253 * Indicates that the image has new GPS data. 254 * That flag is used e.g. by the photo_geotagging plugin to decide for which image 255 * file the EXIF GPS data needs to be (re-)written. 256 * @since 6392 257 */ 258 public void flagNewGpsData() { 259 isNewGpsData = true; 260 // We need to set the GPS time to tell the system (mainly the photo_geotagging plug-in) 261 // that the GPS data has changed. Check for existing GPS time and take EXIF time otherwise. 262 // This can be removed once isNewGpsData is used instead of the GPS time. 263 if (gpsTime == null) { 264 Date time = getExifGpsTime(); 265 if (time == null) { 266 time = getExifTime(); 267 if (time == null) { 268 // Time still not set, take the current time. 269 time = new Date(); 270 } 271 } 272 gpsTime = time; 273 } 274 if (tmp != null && !tmp.hasGpsTime()) { 275 // tmp.gpsTime overrides gpsTime, so we set it too. 276 tmp.gpsTime = gpsTime; 277 } 278 } 279 280 /** 281 * Queries whether the GPS data changed. 282 * @return {@code true} if GPS data changed, {@code false} otherwise 283 * @since 6392 284 */ 285 public boolean hasNewGpsData() { 286 return isNewGpsData; 287 } 288}