001// License: GPL. For details, see LICENSE file.
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 */
014public final 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;
026    /** Temporary source of GPS time if not correlated with GPX track. */
027    private Date exifGpsTime;
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 hour */
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
061    public Double getSpeed() {
062        if (tmp != null)
063            return tmp.speed;
064        return speed;
065    }
066
067    public Double getElevation() {
068        if (tmp != null)
069            return tmp.elevation;
070        return elevation;
071    }
072
073    public Date getGpsTime() {
074        if (tmp != null)
075            return getDefensiveDate(tmp.gpsTime);
076        return getDefensiveDate(gpsTime);
077    }
078
079    /**
080     * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
081     * @return {@code true} if this entry has a GPS time
082     * @since 6450
083     */
084    public boolean hasGpsTime() {
085        return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
086    }
087
088    /**
089     * other getter methods
090     */
091    public File getFile() {
092        return file;
093    }
094
095    public Integer getExifOrientation() {
096        return exifOrientation;
097    }
098
099    public Date getExifTime() {
100        return getDefensiveDate(exifTime);
101    }
102
103    /**
104     * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
105     * @return {@code true} if this entry has a EXIF time
106     * @since 6450
107     */
108    public boolean hasExifTime() {
109        return exifTime != null;
110    }
111
112    /**
113     * Returns the EXIF GPS time.
114     * @return the EXIF GPS time
115     * @since 6392
116     */
117    public Date getExifGpsTime() {
118        return getDefensiveDate(exifGpsTime);
119    }
120
121    /**
122     * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
123     * @return {@code true} if this entry has a EXIF GPS time
124     * @since 6450
125     */
126    public boolean hasExifGpsTime() {
127        return exifGpsTime != null;
128    }
129
130    private static Date getDefensiveDate(Date date) {
131        if (date == null)
132            return null;
133        return new Date(date.getTime());
134    }
135
136    public LatLon getExifCoor() {
137        return exifCoor;
138    }
139
140    public Double getExifImgDir() {
141        return exifImgDir;
142    }
143
144    public boolean hasThumbnail() {
145        return thumbnail != null;
146    }
147
148    /**
149     * setter methods
150     */
151    public void setPos(CachedLatLon pos) {
152        this.pos = pos;
153    }
154
155    public void setPos(LatLon pos) {
156        this.pos = new CachedLatLon(pos);
157    }
158
159    public void setSpeed(Double speed) {
160        this.speed = speed;
161    }
162
163    public void setElevation(Double elevation) {
164        this.elevation = elevation;
165    }
166
167    public void setFile(File file) {
168        this.file = file;
169    }
170
171    public void setExifOrientation(Integer exifOrientation) {
172        this.exifOrientation = exifOrientation;
173    }
174
175    public void setExifTime(Date exifTime) {
176        this.exifTime = getDefensiveDate(exifTime);
177    }
178
179    /**
180     * Sets the EXIF GPS time.
181     * @param exifGpsTime the EXIF GPS time
182     * @since 6392
183     */
184    public void setExifGpsTime(Date exifGpsTime) {
185        this.exifGpsTime = getDefensiveDate(exifGpsTime);
186    }
187
188    public void setGpsTime(Date gpsTime) {
189        this.gpsTime = getDefensiveDate(gpsTime);
190    }
191
192    public void setExifCoor(LatLon exifCoor) {
193        this.exifCoor = exifCoor;
194    }
195
196    public void setExifImgDir(double exifDir) {
197        this.exifImgDir = exifDir;
198    }
199
200    @Override
201    public ImageEntry clone() {
202        Object c;
203        try {
204            c = super.clone();
205        } catch (CloneNotSupportedException e) {
206            throw new RuntimeException(e);
207        }
208        return (ImageEntry) c;
209    }
210
211    @Override
212    public int compareTo(ImageEntry image) {
213        if (exifTime != null && image.exifTime != null)
214            return exifTime.compareTo(image.exifTime);
215        else if (exifTime == null && image.exifTime == null)
216            return 0;
217        else if (exifTime == null)
218            return -1;
219        else
220            return 1;
221    }
222
223    /**
224     * Make a fresh copy and save it in the temporary variable.
225     */
226    public void cleanTmp() {
227        tmp = clone();
228        tmp.setPos(null);
229        tmp.tmp = null;
230    }
231
232    /**
233     * Copy the values from the temporary variable to the main instance.
234     */
235    public void applyTmp() {
236        if (tmp != null) {
237            pos = tmp.pos;
238            speed = tmp.speed;
239            elevation = tmp.elevation;
240            gpsTime = tmp.gpsTime;
241            tmp = null;
242        }
243    }
244
245    /**
246     * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
247     */
248    public boolean isTagged() {
249        return pos != null;
250    }
251
252    /**
253     * String representation. (only partial info)
254     */
255    @Override
256    public String toString() {
257        return file.getName()+": "+
258        "pos = "+pos+" | "+
259        "exifCoor = "+exifCoor+" | "+
260        (tmp == null ? " tmp==null" :
261            " [tmp] pos = "+tmp.pos);
262    }
263
264    /**
265     * Indicates that the image has new GPS data.
266     * That flag is set by new GPS data providers.  It is used e.g. by the photo_geotagging plugin
267     * to decide for which image file the EXIF GPS data needs to be (re-)written.
268     * @since 6392
269     */
270    public void flagNewGpsData() {
271        isNewGpsData = true;
272   }
273
274    /**
275     * Remove the flag that indicates new GPS data.
276     * The flag is cleared by a new GPS data consumer.
277     */
278    public void unflagNewGpsData() {
279        isNewGpsData = false;
280    }
281
282    /**
283     * Queries whether the GPS data changed.
284     * @return {@code true} if GPS data changed, {@code false} otherwise
285     * @since 6392
286     */
287    public boolean hasNewGpsData() {
288        return isNewGpsData;
289    }
290}