1 package com.xtremelabs.robolectric.shadows;
2 
3 import android.location.Location;
4 import android.os.Bundle;
5 import com.xtremelabs.robolectric.internal.Implementation;
6 import com.xtremelabs.robolectric.internal.Implements;
7 
8 import 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)
17 public 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 
__constructor__(Location l)44     public void __constructor__(Location l) {
45         set(l);
46     }
47 
__constructor__(String provider)48     public void __constructor__(String provider) {
49         this.provider = provider;
50         time = System.currentTimeMillis();
51     }
52 
53     @Implementation
set(Location l)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
getProvider()71     public String getProvider() {
72         return provider;
73     }
74 
75     @Implementation
setProvider(String provider)76     public void setProvider(String provider) {
77         this.provider = provider;
78     }
79 
80     @Implementation
getTime()81     public long getTime() {
82         return time;
83     }
84 
85     @Implementation
setTime(long time)86     public void setTime(long time) {
87         this.time = time;
88     }
89 
90     @Implementation
getAccuracy()91     public float getAccuracy() {
92         return accuracy;
93     }
94 
95     @Implementation
setAccuracy(float accuracy)96     public void setAccuracy(float accuracy) {
97         this.accuracy = accuracy;
98         this.hasAccuracy = true;
99     }
100 
101     @Implementation
removeAccuracy()102     public void removeAccuracy() {
103         this.accuracy = 0.0f;
104         this.hasAccuracy = false;
105     }
106 
107     @Implementation
hasAccuracy()108     public boolean hasAccuracy() {
109         return hasAccuracy;
110     }
111 
112     @Implementation
getAltitude()113     public double getAltitude() {
114         return altitude;
115     }
116 
117     @Implementation
setAltitude(double altitude)118     public void setAltitude(double altitude) {
119         this.altitude = altitude;
120         this.hasAltitude = true;
121     }
122 
123     @Implementation
removeAltitude()124     public void removeAltitude() {
125         this.altitude = 0.0d;
126         this.hasAltitude = false;
127     }
128 
129     @Implementation
hasAltitude()130     public boolean hasAltitude() {
131         return hasAltitude;
132     }
133 
134     @Implementation
getBearing()135     public float getBearing() {
136         return bearing;
137     }
138 
139     @Implementation
setBearing(float bearing)140     public void setBearing(float bearing) {
141         this.bearing = bearing;
142         this.hasBearing = true;
143     }
144 
145     @Implementation
removeBearing()146     public void removeBearing() {
147         this.bearing = 0.0f;
148         this.hasBearing = false;
149     }
150 
151     @Implementation
hasBearing()152     public boolean hasBearing() {
153         return hasBearing;
154     }
155 
156 
157     @Implementation
getLatitude()158     public double getLatitude() {
159         return latitude;
160     }
161 
162     @Implementation
setLatitude(double latitude)163     public void setLatitude(double latitude) {
164         this.latitude = latitude;
165     }
166 
167     @Implementation
getLongitude()168     public double getLongitude() {
169         return longitude;
170     }
171 
172     @Implementation
setLongitude(double longitude)173     public void setLongitude(double longitude) {
174         this.longitude = longitude;
175     }
176 
177     @Implementation
getSpeed()178     public float getSpeed() {
179         return speed;
180     }
181 
182     @Implementation
setSpeed(float speed)183     public void setSpeed(float speed) {
184         this.speed = speed;
185         this.hasSpeed = true;
186     }
187 
188     @Implementation
removeSpeed()189     public void removeSpeed() {
190         this.hasSpeed = false;
191         this.speed = 0.0f;
192     }
193 
194     @Implementation
hasSpeed()195     public boolean hasSpeed() {
196         return hasSpeed;
197     }
198 
199     @Override @Implementation
equals(Object o)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
hashCode()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
toString()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 
computeDistanceAndBearing(double lat1, double lon1, double lat2, double lon2, float[] results)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
distanceBetween(double startLatitude, double startLongitude, double endLatitude, double endLongitude, float[] results)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
distanceTo(Location dest)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
bearingTo(Location dest)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
getExtras()429     public Bundle getExtras() {
430         return extras;
431     }
432 
433     @Implementation
setExtras(Bundle extras)434     public void setExtras(Bundle extras) {
435         this.extras = extras;
436     }
437 }
438