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 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