001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside;
003
004import java.text.SimpleDateFormat;
005import java.util.Calendar;
006import java.util.Date;
007import java.util.Locale;
008
009import org.openstreetmap.josm.data.coor.LatLon;
010import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
011
012/**
013 * Abstract superclass for all image objects. At the moment there are just 2,
014 * {@link StreetsideImportedImage} and {@link StreetsideImage}.
015 *
016 * @author nokutu
017 *
018 */
019public abstract class StreetsideAbstractImage implements Comparable<StreetsideAbstractImage> {
020        /**
021         * If two values for field cd differ by less than EPSILON both values are
022         * considered equal.
023         */
024        private static final float EPSILON = 1e-5f;
025
026        protected String id;
027
028        /** The time the image was captured, in Epoch format. */
029        protected long cd;
030        /** Sequence of pictures containing this object. */
031        private StreetsideSequence sequence;
032
033        /** Position of the picture. */
034        protected LatLon latLon;
035        /** Direction of the picture. */
036        protected double he;
037        /** Temporal position of the picture until it is uploaded. */
038        private LatLon tempLatLon;
039        /**
040         * When the object is being dragged in the map, the temporal position is stored
041         * here.
042         */
043        private LatLon movingLatLon;
044        /** Temporal direction of the picture until it is uploaded */
045        private double tempHe;
046        /**
047         * When the object direction is being moved in the map, the temporal direction
048         * is stored here
049         */
050        protected double movingHe;
051        /** Whether the image must be drown in the map or not */
052        private boolean visible;
053
054        /**
055         * Creates a new object in the given position and with the given direction.
056         * {@link LatLon}
057         *
058         * @param id - the Streetside image id
059         *
060         * @param latLon
061         *            The latitude and longitude of the image.
062         * @param he
063         *            The direction of the picture (0 means north im Mapillary
064         *            camera direction is not yet supported in the Streetside plugin).
065         */
066        protected StreetsideAbstractImage(final String id, final LatLon latLon, final double he) {
067                this.id = id;
068                this.latLon = latLon;
069                tempLatLon = this.latLon;
070                movingLatLon = this.latLon;
071                this.he = he;
072                tempHe = he;
073                movingHe = he;
074                visible = true;
075        }
076
077        /**
078         * Creates a new object with the given id.
079         *
080         * @param id - the image id (All images require ids in Streetside)
081         */
082        protected StreetsideAbstractImage(final String id) {
083                this.id = id;
084
085                visible = true;
086        }
087
088        /**
089         * @return the id
090         */
091        public String getId() {
092                return id;
093        }
094
095        /**
096         * @param id
097         *            the id to set
098         */
099        public void setId(String id) {
100                this.id = id;
101        }
102
103        /**
104         * Returns the original direction towards the image has been taken.
105         *
106         * @return The direction of the image (0 means north and goes clockwise).
107         */
108        public double getHe() {
109                return he;
110        }
111
112        /**
113         * Returns the Epoch time when the image was captured.
114         *
115         * @return The long containing the Epoch time when the image was captured.
116         */
117        public long getCd() {
118                return cd;
119        }
120
121        /**
122         * Returns the date the picture was taken in DMY format.
123         *
124         * @return A String object containing the date when the picture was taken.
125         */
126        public String getDate() {
127                final StringBuilder format = new StringBuilder(26);
128                format.append("m/d/YYYY");
129                if (StreetsideProperties.DISPLAY_HOUR.get()) {
130                        if (StreetsideProperties.TIME_FORMAT_24.get()) {
131                                format.append(" - HH:mm:ss");
132                        } else {
133                                format.append(" - h:mm:ss a");
134                        }
135                }
136                return getDate(format.toString());
137        }
138
139        /**
140         * Returns the date the picture was taken in the given format.
141         *
142         * @param format
143         *            Format of the date. See {@link SimpleDateFormat}.
144         * @return A String containing the date the picture was taken using the given
145         *         format.
146         * @throws NullPointerException
147         *             if parameter format is <code>null</code>
148         */
149        public String getDate(String format) {
150                final Date date = new Date(getCd());
151                final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.US);
152                formatter.setTimeZone(Calendar.getInstance().getTimeZone());
153                return formatter.format(date);
154        }
155
156        /**
157         * Returns a LatLon object containing the original coordinates of the object.
158         *
159         * @return The LatLon object with the position of the object.
160         */
161        public LatLon getLatLon() {
162                return latLon;
163        }
164
165        /**
166         * Returns the direction towards the image has been taken.
167         *
168         * @return The direction of the image (0 means north and goes clockwise).
169         */
170        public double getMovingHe() {
171                return movingHe;
172        }
173
174        /**
175         * Returns a LatLon object containing the current coordinates of the object.
176         * When you are dragging the image this changes.
177         *
178         * @return The LatLon object with the position of the object.
179         */
180        public LatLon getMovingLatLon() {
181                return movingLatLon;
182        }
183
184        /**
185         * Returns the sequence which contains this image. Never null.
186         *
187         * @return The StreetsideSequence object that contains this StreetsideImage.
188         */
189
190        public StreetsideSequence getSequence() {
191                synchronized (this) {
192                        if (sequence == null) {
193                                sequence = new StreetsideSequence();
194                                sequence.add(this);
195                        }
196                        return sequence;
197                }
198        }
199
200        /**
201         * Returns the last fixed direction of the object.
202         *
203         * @return The last fixed direction of the object. 0 means north.
204         */
205        public double getTempHe() {
206                return tempHe;
207        }
208
209        /**
210         * Returns the last fixed coordinates of the object.
211         *
212         * @return A LatLon object containing.
213         */
214        public LatLon getTempLatLon() {
215                return tempLatLon;
216        }
217
218        /**
219         * Returns whether the object has been modified or not.
220         *
221         * @return true if the object has been modified; false otherwise.
222         */
223        public boolean isModified() {
224                return !getMovingLatLon().equals(latLon) || Math.abs(getMovingHe() - cd) > EPSILON;
225        }
226
227        /**
228         * Returns whether the image is visible on the map or not.
229         *
230         * @return True if the image is visible; false otherwise.
231         */
232        public boolean isVisible() {
233                return visible;
234        }
235
236        /**
237         * Moves the image temporally to another position
238         *
239         * @param x
240         *            The movement of the image in longitude units.
241         * @param y
242         *            The movement of the image in latitude units.
243         */
244        public void move(final double x, final double y) {
245                movingLatLon = new LatLon(tempLatLon.getY() + y, tempLatLon.getX() + x);
246        }
247
248        /**
249         * If the StreetsideImage belongs to a StreetsideSequence, returns the next
250         * image in the sequence.
251         *
252         * @return The following StreetsideImage, or null if there is none.
253         */
254        public StreetsideAbstractImage next() {
255                synchronized (this) {
256                        return getSequence().next(this);
257                }
258        }
259
260        /**
261         * If the StreetsideImage belongs to a StreetsideSequence, returns the previous
262         * image in the sequence.
263         *
264         * @return The previous StreetsideImage, or null if there is none.
265         */
266        public StreetsideAbstractImage previous() {
267                synchronized (this) {
268                        return getSequence().previous(this);
269                }
270        }
271
272        public void setHe(final double he) {
273                this.he = he;
274        }
275
276        /**
277         * Sets the Epoch time when the picture was captured.
278         *
279         * @param cd
280         *            Epoch time when the image was captured.
281         */
282        public synchronized void setCd(final long cd) {
283                this.cd = cd;
284        }
285
286        public void setLatLon(final LatLon latLon) {
287                if (latLon != null) {
288                        this.latLon = latLon;
289                }
290        }
291
292        /**
293         * Sets the StreetsideSequence object which contains the StreetsideImage.
294         *
295         * @param sequence
296         *            The StreetsideSequence that contains the StreetsideImage.
297         * @throws IllegalArgumentException
298         *             if the image is not already part of the
299         *             {@link StreetsideSequence}. Call
300         *             {@link StreetsideSequence#add(StreetsideAbstractImage)} first.
301         */
302        public void setSequence(final StreetsideSequence sequence) {
303                synchronized (this) {
304                        if (sequence != null && !sequence.getImages().contains(this)) {
305                                throw new IllegalArgumentException();
306                        }
307                        this.sequence = sequence;
308                }
309        }
310
311        /**
312         * Set's whether the image should be visible on the map or not.
313         *
314         * @param visible
315         *            true if the image is set to be visible; false otherwise.
316         */
317        public void setVisible(final boolean visible) {
318                this.visible = visible;
319        }
320
321        /**
322         * Called when the mouse button is released, meaning that the picture has
323         * stopped being dragged, so the temporal values are saved.
324         */
325        public void stopMoving() {
326                tempLatLon = movingLatLon;
327                tempHe = movingHe;
328        }
329
330        /**
331         * Turns the image direction.
332         *
333         * @param ca
334         *            The angle the image is moving.
335         */
336        public void turn(final double ca) {
337                movingHe = tempHe + ca;
338        }
339}