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}