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 */
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 = 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 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    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(e);
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        return file.getName()+": "+
245        "pos = "+pos+" | "+
246        "exifCoor = "+exifCoor+" | "+
247        (tmp == null ? " tmp==null" :
248            " [tmp] pos = "+tmp.pos);
249    }
250
251    /**
252     * Indicates that the image has new GPS data.
253     * That flag is set by new GPS data providers.  It is used e.g. by the photo_geotagging plugin
254     * to decide for which image file the EXIF GPS data needs to be (re-)written.
255     * @since 6392
256     */
257    public void flagNewGpsData() {
258        isNewGpsData = true;
259   }
260
261    /**
262     * Remove the flag that indicates new GPS data.
263     * The flag is cleared by a new GPS data consumer.
264     */
265    public void unflagNewGpsData() {
266        isNewGpsData = false;
267    }
268
269    /**
270     * Queries whether the GPS data changed.
271     * @return {@code true} if GPS data changed, {@code false} otherwise
272     * @since 6392
273     */
274    public boolean hasNewGpsData() {
275        return isNewGpsData;
276    }
277}