Home | History | Annotate | Download | only in shadows
      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