1package com.xtremelabs.robolectric.shadows;
2
3import android.location.Location;
4import android.os.Bundle;
5import com.xtremelabs.robolectric.internal.Implementation;
6import com.xtremelabs.robolectric.internal.Implements;
7
8import static com.xtremelabs.robolectric.Robolectric.shadowOf_;
9
10/**
11 * Shadow of {@code Location} that treats it primarily as a data-holder
12 * todo: support Location's static utility methods
13 */
14
15@SuppressWarnings({"UnusedDeclaration"})
16@Implements(Location.class)
17public class ShadowLocation {
18    private long time;
19    private String provider;
20    private double latitude;
21    private double longitude;
22    private float accuracy;
23    private float bearing;
24    private double altitude;
25    private float speed;
26    private boolean hasAccuracy;
27    private boolean hasAltitude;
28    private boolean hasBearing;
29    private boolean hasSpeed;
30
31    // Cache the inputs and outputs of computeDistanceAndBearing
32    // so calls to distanceTo() and bearingTo() can share work
33    private double mLat1 = 0.0;
34    private double mLon1 = 0.0;
35    private double mLat2 = 0.0;
36    private double mLon2 = 0.0;
37    private float mDistance = 0.0f;
38    private float mInitialBearing = 0.0f;
39    // Scratchpad
40    private final float[] mResults = new float[2];
41
42    private Bundle extras = new Bundle();
43
44    public void __constructor__(Location l) {
45        set(l);
46    }
47
48    public void __constructor__(String provider) {
49        this.provider = provider;
50        time = System.currentTimeMillis();
51    }
52
53    @Implementation
54    public void set(Location l) {
55        time = l.getTime();
56        provider = l.getProvider();
57        latitude = l.getLatitude();
58        longitude = l.getLongitude();
59        accuracy = l.getAccuracy();
60        bearing = l.getBearing();
61        altitude = l.getAltitude();
62        speed = l.getSpeed();
63
64        hasAccuracy = l.hasAccuracy();
65        hasAltitude = l.hasAltitude();
66        hasBearing = l.hasBearing();
67        hasSpeed = l.hasSpeed();
68    }
69
70    @Implementation
71    public String getProvider() {
72        return provider;
73    }
74
75    @Implementation
76    public void setProvider(String provider) {
77        this.provider = provider;
78    }
79
80    @Implementation
81    public long getTime() {
82        return time;
83    }
84
85    @Implementation
86    public void setTime(long time) {
87        this.time = time;
88    }
89
90    @Implementation
91    public float getAccuracy() {
92        return accuracy;
93    }
94
95    @Implementation
96    public void setAccuracy(float accuracy) {
97        this.accuracy = accuracy;
98        this.hasAccuracy = true;
99    }
100
101    @Implementation
102    public void removeAccuracy() {
103        this.accuracy = 0.0f;
104        this.hasAccuracy = false;
105    }
106
107    @Implementation
108    public boolean hasAccuracy() {
109        return hasAccuracy;
110    }
111
112    @Implementation
113    public double getAltitude() {
114        return altitude;
115    }
116
117    @Implementation
118    public void setAltitude(double altitude) {
119        this.altitude = altitude;
120        this.hasAltitude = true;
121    }
122
123    @Implementation
124    public void removeAltitude() {
125        this.altitude = 0.0d;
126        this.hasAltitude = false;
127    }
128
129    @Implementation
130    public boolean hasAltitude() {
131        return hasAltitude;
132    }
133
134    @Implementation
135    public float getBearing() {
136        return bearing;
137    }
138
139    @Implementation
140    public void setBearing(float bearing) {
141        this.bearing = bearing;
142        this.hasBearing = true;
143    }
144
145    @Implementation
146    public void removeBearing() {
147        this.bearing = 0.0f;
148        this.hasBearing = false;
149    }
150
151    @Implementation
152    public boolean hasBearing() {
153        return hasBearing;
154    }
155
156
157    @Implementation
158    public double getLatitude() {
159        return latitude;
160    }
161
162    @Implementation
163    public void setLatitude(double latitude) {
164        this.latitude = latitude;
165    }
166
167    @Implementation
168    public double getLongitude() {
169        return longitude;
170    }
171
172    @Implementation
173    public void setLongitude(double longitude) {
174        this.longitude = longitude;
175    }
176
177    @Implementation
178    public float getSpeed() {
179        return speed;
180    }
181
182    @Implementation
183    public void setSpeed(float speed) {
184        this.speed = speed;
185        this.hasSpeed = true;
186    }
187
188    @Implementation
189    public void removeSpeed() {
190        this.hasSpeed = false;
191        this.speed = 0.0f;
192    }
193
194    @Implementation
195    public boolean hasSpeed() {
196        return hasSpeed;
197    }
198
199    @Override @Implementation
200    public boolean equals(Object o) {
201        if (o == null) return false;
202        o = shadowOf_(o);
203        if (o == null) return false;
204        if (getClass() != o.getClass()) return false;
205        if (this == o) return true;
206
207        ShadowLocation that = (ShadowLocation) o;
208
209        if (Double.compare(that.latitude, latitude) != 0) return false;
210        if (Double.compare(that.longitude, longitude) != 0) return false;
211        if (time != that.time) return false;
212        if (provider != null ? !provider.equals(that.provider) : that.provider != null) return false;
213        if (accuracy != that.accuracy) return false;
214        return true;
215    }
216
217    @Override @Implementation
218    public int hashCode() {
219        int result;
220        long temp;
221        result = (int) (time ^ (time >>> 32));
222        result = 31 * result + (provider != null ? provider.hashCode() : 0);
223        temp = latitude != +0.0d ? Double.doubleToLongBits(latitude) : 0L;
224        result = 31 * result + (int) (temp ^ (temp >>> 32));
225        temp = longitude != +0.0d ? Double.doubleToLongBits(longitude) : 0L;
226        result = 31 * result + (int) (temp ^ (temp >>> 32));
227        temp = accuracy != 0f ? Float.floatToIntBits(accuracy) : 0;
228        result = 31 * result + (int) (temp ^ (temp >>> 32));
229        return result;
230    }
231
232    @Override @Implementation
233    public String toString() {
234        return "Location{" +
235                "time=" + time +
236                ", provider='" + provider + '\'' +
237                ", latitude=" + latitude +
238                ", longitude=" + longitude +
239                ", accuracy=" + accuracy +
240                '}';
241    }
242
243    private static void computeDistanceAndBearing(double lat1, double lon1,
244            double lat2, double lon2, float[] results) {
245        // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
246        // using the "Inverse Formula" (section 4)
247
248        int MAXITERS = 20;
249        // Convert lat/long to radians
250        lat1 *= Math.PI / 180.0;
251        lat2 *= Math.PI / 180.0;
252        lon1 *= Math.PI / 180.0;
253        lon2 *= Math.PI / 180.0;
254
255        double a = 6378137.0; // WGS84 major axis
256        double b = 6356752.3142; // WGS84 semi-major axis
257        double f = (a - b) / a;
258        double aSqMinusBSqOverBSq = (a * a - b * b) / (b * b);
259
260        double L = lon2 - lon1;
261        double A = 0.0;
262        double U1 = Math.atan((1.0 - f) * Math.tan(lat1));
263        double U2 = Math.atan((1.0 - f) * Math.tan(lat2));
264
265        double cosU1 = Math.cos(U1);
266        double cosU2 = Math.cos(U2);
267        double sinU1 = Math.sin(U1);
268        double sinU2 = Math.sin(U2);
269        double cosU1cosU2 = cosU1 * cosU2;
270        double sinU1sinU2 = sinU1 * sinU2;
271
272        double sigma = 0.0;
273        double deltaSigma = 0.0;
274        double cosSqAlpha = 0.0;
275        double cos2SM = 0.0;
276        double cosSigma = 0.0;
277        double sinSigma = 0.0;
278        double cosLambda = 0.0;
279        double sinLambda = 0.0;
280
281        double lambda = L; // initial guess
282        for (int iter = 0; iter < MAXITERS; iter++) {
283            double lambdaOrig = lambda;
284            cosLambda = Math.cos(lambda);
285            sinLambda = Math.sin(lambda);
286            double t1 = cosU2 * sinLambda;
287            double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
288            double sinSqSigma = t1 * t1 + t2 * t2; // (14)
289            sinSigma = Math.sqrt(sinSqSigma);
290            cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15)
291            sigma = Math.atan2(sinSigma, cosSigma); // (16)
292            double sinAlpha = (sinSigma == 0) ? 0.0 :
293                cosU1cosU2 * sinLambda / sinSigma; // (17)
294            cosSqAlpha = 1.0 - sinAlpha * sinAlpha;
295            cos2SM = (cosSqAlpha == 0) ? 0.0 :
296                cosSigma - 2.0 * sinU1sinU2 / cosSqAlpha; // (18)
297
298            double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn
299            A = 1 + (uSquared / 16384.0) * // (3)
300                (4096.0 + uSquared *
301                 (-768 + uSquared * (320.0 - 175.0 * uSquared)));
302            double B = (uSquared / 1024.0) * // (4)
303                (256.0 + uSquared *
304                 (-128.0 + uSquared * (74.0 - 47.0 * uSquared)));
305            double C = (f / 16.0) *
306                cosSqAlpha *
307                (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10)
308            double cos2SMSq = cos2SM * cos2SM;
309            deltaSigma = B * sinSigma * // (6)
310                (cos2SM + (B / 4.0) *
311                 (cosSigma * (-1.0 + 2.0 * cos2SMSq) -
312                  (B / 6.0) * cos2SM *
313                  (-3.0 + 4.0 * sinSigma * sinSigma) *
314                  (-3.0 + 4.0 * cos2SMSq)));
315
316            lambda = L +
317                (1.0 - C) * f * sinAlpha *
318                (sigma + C * sinSigma *
319                 (cos2SM + C * cosSigma *
320                  (-1.0 + 2.0 * cos2SM * cos2SM))); // (11)
321
322            double delta = (lambda - lambdaOrig) / lambda;
323            if (Math.abs(delta) < 1.0e-12) {
324                break;
325            }
326        }
327
328        float distance = (float) (b * A * (sigma - deltaSigma));
329        results[0] = distance;
330        if (results.length > 1) {
331            float initialBearing = (float) Math.atan2(cosU2 * sinLambda,
332                cosU1 * sinU2 - sinU1 * cosU2 * cosLambda);
333            initialBearing *= 180.0 / Math.PI;
334            results[1] = initialBearing;
335            if (results.length > 2) {
336                float finalBearing = (float) Math.atan2(cosU1 * sinLambda,
337                    -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda);
338                finalBearing *= 180.0 / Math.PI;
339                results[2] = finalBearing;
340            }
341        }
342    }
343
344    /**
345     * Computes the approximate distance in meters between two
346     * locations, and optionally the initial and final bearings of the
347     * shortest path between them.  Distance and bearing are defined using the
348     * WGS84 ellipsoid.
349     *
350     * <p> The computed distance is stored in results[0].  If results has length
351     * 2 or greater, the initial bearing is stored in results[1]. If results has
352     * length 3 or greater, the final bearing is stored in results[2].
353     *
354     * @param startLatitude the starting latitude
355     * @param startLongitude the starting longitude
356     * @param endLatitude the ending latitude
357     * @param endLongitude the ending longitude
358     * @param results an array of floats to hold the results
359     *
360     * @throws IllegalArgumentException if results is null or has length < 1
361     */
362    @Implementation
363    public static void distanceBetween(double startLatitude, double startLongitude,
364        double endLatitude, double endLongitude, float[] results) {
365        if (results == null || results.length < 1) {
366            throw new IllegalArgumentException("results is null or has length < 1");
367        }
368        computeDistanceAndBearing(startLatitude, startLongitude,
369            endLatitude, endLongitude, results);
370    }
371
372    /**
373     * Returns the approximate distance in meters between this
374     * location and the given location.  Distance is defined using
375     * the WGS84 ellipsoid.
376     *
377     * @param dest the destination location
378     * @return the approximate distance in meters
379     */
380    @Implementation
381    public float distanceTo(Location dest) {
382        // See if we already have the result
383        synchronized (mResults) {
384            if (latitude != mLat1 || longitude != mLon1 ||
385                dest.getLatitude() != mLat2 || dest.getLongitude() != mLon2) {
386                computeDistanceAndBearing(latitude, longitude,
387                    dest.getLatitude(), dest.getLongitude(), mResults);
388                mLat1 = latitude;
389                mLon1 = longitude;
390                mLat2 = dest.getLatitude();
391                mLon2 = dest.getLongitude();
392                mDistance = mResults[0];
393                mInitialBearing = mResults[1];
394            }
395            return mDistance;
396        }
397    }
398
399    /**
400     * Returns the approximate initial bearing in degrees East of true
401     * North when traveling along the shortest path between this
402     * location and the given location.  The shortest path is defined
403     * using the WGS84 ellipsoid.  Locations that are (nearly)
404     * antipodal may produce meaningless results.
405     *
406     * @param dest the destination location
407     * @return the initial bearing in degrees
408     */
409    @Implementation
410    public float bearingTo(Location dest) {
411        synchronized (mResults) {
412            // See if we already have the result
413            if (latitude != mLat1 || longitude != mLon1 ||
414                            dest.getLatitude() != mLat2 || dest.getLongitude() != mLon2) {
415                computeDistanceAndBearing(latitude, longitude,
416                    dest.getLatitude(), dest.getLongitude(), mResults);
417                mLat1 = latitude;
418                mLon1 = longitude;
419                mLat2 = dest.getLatitude();
420                mLon2 = dest.getLongitude();
421                mDistance = mResults[0];
422                mInitialBearing = mResults[1];
423            }
424            return mInitialBearing;
425        }
426    }
427
428    @Implementation
429    public Bundle getExtras() {
430        return extras;
431    }
432
433    @Implementation
434    public void setExtras(Bundle extras) {
435        this.extras = extras;
436    }
437}
438