Loading packages/FusedLocation/src/com/android/location/fused/FusionEngine.java +37 −114 Original line number Original line Diff line number Diff line Loading @@ -42,16 +42,7 @@ public class FusionEngine implements LocationListener { private static final String NETWORK = LocationManager.NETWORK_PROVIDER; private static final String NETWORK = LocationManager.NETWORK_PROVIDER; private static final String GPS = LocationManager.GPS_PROVIDER; private static final String GPS = LocationManager.GPS_PROVIDER; // threshold below which a location is considered stale enough public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000; // 11 seconds // that we shouldn't use its bearing, altitude, speed etc private static final double WEIGHT_THRESHOLD = 0.5; // accuracy in meters at which a Location's weight is halved (compared to 0 accuracy) private static final double ACCURACY_HALFLIFE_M = 20.0; // age in seconds at which a Location's weight is halved (compared to 0 age) private static final double AGE_HALFLIFE_S = 60.0; private static final double ACCURACY_DECAY_CONSTANT_M = Math.log(2) / ACCURACY_HALFLIFE_M; private static final double AGE_DECAY_CONSTANT_S = Math.log(2) / AGE_HALFLIFE_S; private final Context mContext; private final Context mContext; private final LocationManager mLocationManager; private final LocationManager mLocationManager; Loading @@ -62,8 +53,6 @@ public class FusionEngine implements LocationListener { private Location mFusedLocation; private Location mFusedLocation; private Location mGpsLocation; private Location mGpsLocation; private Location mNetworkLocation; private Location mNetworkLocation; private double mNetworkWeight; private double mGpsWeight; private boolean mEnabled; private boolean mEnabled; private ProviderRequestUnbundled mRequest; private ProviderRequestUnbundled mRequest; Loading Loading @@ -102,10 +91,6 @@ public class FusionEngine implements LocationListener { Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); } } private boolean isAvailable() { return mStats.get(GPS).available || mStats.get(NETWORK).available; } /** Called on mLooper thread */ /** Called on mLooper thread */ public void enable() { public void enable() { mEnabled = true; mEnabled = true; Loading @@ -130,7 +115,6 @@ public class FusionEngine implements LocationListener { public boolean requested; public boolean requested; public long requestTime; public long requestTime; public long minTime; public long minTime; public long lastRequestTtff; @Override @Override public String toString() { public String toString() { StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder(); Loading Loading @@ -171,9 +155,6 @@ public class FusionEngine implements LocationListener { return; return; } } ProviderStats gpsStats = mStats.get(GPS); ProviderStats networkStats = mStats.get(NETWORK); long networkInterval = Long.MAX_VALUE; long networkInterval = Long.MAX_VALUE; long gpsInterval = Long.MAX_VALUE; long gpsInterval = Long.MAX_VALUE; for (LocationRequest request : mRequest.getLocationRequests()) { for (LocationRequest request : mRequest.getLocationRequests()) { Loading Loading @@ -209,104 +190,46 @@ public class FusionEngine implements LocationListener { } } } } private static double weighAccuracy(Location loc) { /** double accuracy = loc.getAccuracy(); * Test whether one location (a) is better to use than another (b). return Math.exp(-accuracy * ACCURACY_DECAY_CONSTANT_M); */ private static boolean isBetterThan(Location locationA, Location locationB) { if (locationA == null) { return false; } } if (locationB == null) { private static double weighAge(Location loc) { return true; long ageSeconds = SystemClock.elapsedRealtimeNanos() - loc.getElapsedRealtimeNanos(); ageSeconds /= 1000000000L; if (ageSeconds < 0) ageSeconds = 0; return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S); } } // A provider is better if the reading is sufficiently newer. Heading private double weigh(double gps, double network) { // underground can cause GPS to stop reporting fixes. In this case it's return (gps * mGpsWeight) + (network * mNetworkWeight); // appropriate to revert to cell, even when its accuracy is less. if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) { return true; } } private double weigh(double gps, double network, double wrapMin, double wrapMax) { // A provider is better if it has better accuracy. Assuming both readings // apply aliasing // are fresh (and by that accurate), choose the one with the smaller double wrapWidth = wrapMax - wrapMin; // accuracy circle. if (gps - network > wrapWidth / 2) network += wrapWidth; if (!locationA.hasAccuracy()) { else if (network - gps > wrapWidth / 2) gps += wrapWidth; return false; } double result = weigh(gps, network); if (!locationB.hasAccuracy()) { return true; // remove aliasing } if (result > wrapMax) result -= wrapWidth; return locationA.getAccuracy() < locationB.getAccuracy(); return result; } } private void updateFusedLocation() { private void updateFusedLocation() { // naive fusion // may the best location win! mNetworkWeight = weighAccuracy(mNetworkLocation) * weighAge(mNetworkLocation); if (isBetterThan(mGpsLocation, mNetworkLocation)) { mGpsWeight = weighAccuracy(mGpsLocation) * weighAge(mGpsLocation); mFusedLocation = new Location(mGpsLocation); // scale mNetworkWeight and mGpsWeight so that they add to 1 } else { double totalWeight = mNetworkWeight + mGpsWeight; mFusedLocation = new Location(mNetworkLocation); mNetworkWeight /= totalWeight; mGpsWeight /= totalWeight; Location fused = new Location(LocationManager.FUSED_PROVIDER); // fuse lat/long // assumes the two locations are close enough that earth curvature doesn't matter fused.setLatitude(weigh(mGpsLocation.getLatitude(), mNetworkLocation.getLatitude())); fused.setLongitude(weigh(mGpsLocation.getLongitude(), mNetworkLocation.getLongitude(), -180.0, 180.0)); // fused accuracy //TODO: use some real math instead of this crude fusion // one suggestion is to fuse in a quadratic manner, eg // sqrt(weigh(gpsAcc^2, netAcc^2)). // another direction to explore is to consider the difference in the 2 // locations. If the component locations overlap, the fused accuracy is // better than the component accuracies. If they are far apart, // the fused accuracy is much worse. fused.setAccuracy((float)weigh(mGpsLocation.getAccuracy(), mNetworkLocation.getAccuracy())); // fused time - now fused.setTime(System.currentTimeMillis()); fused.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); // fuse altitude if (mGpsLocation.hasAltitude() && !mNetworkLocation.hasAltitude() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setAltitude(mGpsLocation.getAltitude()); // use GPS } else if (!mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setAltitude(mNetworkLocation.getAltitude()); // use Network } else if (mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude()) { fused.setAltitude(weigh(mGpsLocation.getAltitude(), mNetworkLocation.getAltitude())); } // fuse speed if (mGpsLocation.hasSpeed() && !mNetworkLocation.hasSpeed() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setSpeed(mGpsLocation.getSpeed()); // use GPS if its not too old } else if (!mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setSpeed(mNetworkLocation.getSpeed()); // use Network } else if (mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed()) { fused.setSpeed((float)weigh(mGpsLocation.getSpeed(), mNetworkLocation.getSpeed())); } // fuse bearing if (mGpsLocation.hasBearing() && !mNetworkLocation.hasBearing() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setBearing(mGpsLocation.getBearing()); // use GPS if its not too old } else if (!mGpsLocation.hasBearing() && mNetworkLocation.hasBearing() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setBearing(mNetworkLocation.getBearing()); // use Network } else if (mGpsLocation.hasBearing() && mNetworkLocation.hasBearing()) { fused.setBearing((float)weigh(mGpsLocation.getBearing(), mNetworkLocation.getBearing(), 0.0, 360.0)); } } if (mNetworkLocation != null) { if (mNetworkLocation != null) { fused.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation); mFusedLocation.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation); } } mFusedLocation.setProvider(LocationManager.FUSED_PROVIDER); mFusedLocation = fused; mCallback.reportLocation(mFusedLocation); mCallback.reportLocation(mFusedLocation); } } Loading Loading @@ -349,9 +272,9 @@ public class FusionEngine implements LocationListener { StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder(); s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n'); s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n'); s.append("fused=").append(mFusedLocation).append('\n'); s.append("fused=").append(mFusedLocation).append('\n'); s.append(String.format("gps %.3f %s\n", mGpsWeight, mGpsLocation)); s.append(String.format("gps %s\n", mGpsLocation)); s.append(" ").append(mStats.get(GPS)).append('\n'); s.append(" ").append(mStats.get(GPS)).append('\n'); s.append(String.format("net %.3f %s\n", mNetworkWeight, mNetworkLocation)); s.append(String.format("net %s\n", mNetworkLocation)); s.append(" ").append(mStats.get(NETWORK)).append('\n'); s.append(" ").append(mStats.get(NETWORK)).append('\n'); pw.append(s); pw.append(s); } } Loading Loading
packages/FusedLocation/src/com/android/location/fused/FusionEngine.java +37 −114 Original line number Original line Diff line number Diff line Loading @@ -42,16 +42,7 @@ public class FusionEngine implements LocationListener { private static final String NETWORK = LocationManager.NETWORK_PROVIDER; private static final String NETWORK = LocationManager.NETWORK_PROVIDER; private static final String GPS = LocationManager.GPS_PROVIDER; private static final String GPS = LocationManager.GPS_PROVIDER; // threshold below which a location is considered stale enough public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000; // 11 seconds // that we shouldn't use its bearing, altitude, speed etc private static final double WEIGHT_THRESHOLD = 0.5; // accuracy in meters at which a Location's weight is halved (compared to 0 accuracy) private static final double ACCURACY_HALFLIFE_M = 20.0; // age in seconds at which a Location's weight is halved (compared to 0 age) private static final double AGE_HALFLIFE_S = 60.0; private static final double ACCURACY_DECAY_CONSTANT_M = Math.log(2) / ACCURACY_HALFLIFE_M; private static final double AGE_DECAY_CONSTANT_S = Math.log(2) / AGE_HALFLIFE_S; private final Context mContext; private final Context mContext; private final LocationManager mLocationManager; private final LocationManager mLocationManager; Loading @@ -62,8 +53,6 @@ public class FusionEngine implements LocationListener { private Location mFusedLocation; private Location mFusedLocation; private Location mGpsLocation; private Location mGpsLocation; private Location mNetworkLocation; private Location mNetworkLocation; private double mNetworkWeight; private double mGpsWeight; private boolean mEnabled; private boolean mEnabled; private ProviderRequestUnbundled mRequest; private ProviderRequestUnbundled mRequest; Loading Loading @@ -102,10 +91,6 @@ public class FusionEngine implements LocationListener { Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); } } private boolean isAvailable() { return mStats.get(GPS).available || mStats.get(NETWORK).available; } /** Called on mLooper thread */ /** Called on mLooper thread */ public void enable() { public void enable() { mEnabled = true; mEnabled = true; Loading @@ -130,7 +115,6 @@ public class FusionEngine implements LocationListener { public boolean requested; public boolean requested; public long requestTime; public long requestTime; public long minTime; public long minTime; public long lastRequestTtff; @Override @Override public String toString() { public String toString() { StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder(); Loading Loading @@ -171,9 +155,6 @@ public class FusionEngine implements LocationListener { return; return; } } ProviderStats gpsStats = mStats.get(GPS); ProviderStats networkStats = mStats.get(NETWORK); long networkInterval = Long.MAX_VALUE; long networkInterval = Long.MAX_VALUE; long gpsInterval = Long.MAX_VALUE; long gpsInterval = Long.MAX_VALUE; for (LocationRequest request : mRequest.getLocationRequests()) { for (LocationRequest request : mRequest.getLocationRequests()) { Loading Loading @@ -209,104 +190,46 @@ public class FusionEngine implements LocationListener { } } } } private static double weighAccuracy(Location loc) { /** double accuracy = loc.getAccuracy(); * Test whether one location (a) is better to use than another (b). return Math.exp(-accuracy * ACCURACY_DECAY_CONSTANT_M); */ private static boolean isBetterThan(Location locationA, Location locationB) { if (locationA == null) { return false; } } if (locationB == null) { private static double weighAge(Location loc) { return true; long ageSeconds = SystemClock.elapsedRealtimeNanos() - loc.getElapsedRealtimeNanos(); ageSeconds /= 1000000000L; if (ageSeconds < 0) ageSeconds = 0; return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S); } } // A provider is better if the reading is sufficiently newer. Heading private double weigh(double gps, double network) { // underground can cause GPS to stop reporting fixes. In this case it's return (gps * mGpsWeight) + (network * mNetworkWeight); // appropriate to revert to cell, even when its accuracy is less. if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) { return true; } } private double weigh(double gps, double network, double wrapMin, double wrapMax) { // A provider is better if it has better accuracy. Assuming both readings // apply aliasing // are fresh (and by that accurate), choose the one with the smaller double wrapWidth = wrapMax - wrapMin; // accuracy circle. if (gps - network > wrapWidth / 2) network += wrapWidth; if (!locationA.hasAccuracy()) { else if (network - gps > wrapWidth / 2) gps += wrapWidth; return false; } double result = weigh(gps, network); if (!locationB.hasAccuracy()) { return true; // remove aliasing } if (result > wrapMax) result -= wrapWidth; return locationA.getAccuracy() < locationB.getAccuracy(); return result; } } private void updateFusedLocation() { private void updateFusedLocation() { // naive fusion // may the best location win! mNetworkWeight = weighAccuracy(mNetworkLocation) * weighAge(mNetworkLocation); if (isBetterThan(mGpsLocation, mNetworkLocation)) { mGpsWeight = weighAccuracy(mGpsLocation) * weighAge(mGpsLocation); mFusedLocation = new Location(mGpsLocation); // scale mNetworkWeight and mGpsWeight so that they add to 1 } else { double totalWeight = mNetworkWeight + mGpsWeight; mFusedLocation = new Location(mNetworkLocation); mNetworkWeight /= totalWeight; mGpsWeight /= totalWeight; Location fused = new Location(LocationManager.FUSED_PROVIDER); // fuse lat/long // assumes the two locations are close enough that earth curvature doesn't matter fused.setLatitude(weigh(mGpsLocation.getLatitude(), mNetworkLocation.getLatitude())); fused.setLongitude(weigh(mGpsLocation.getLongitude(), mNetworkLocation.getLongitude(), -180.0, 180.0)); // fused accuracy //TODO: use some real math instead of this crude fusion // one suggestion is to fuse in a quadratic manner, eg // sqrt(weigh(gpsAcc^2, netAcc^2)). // another direction to explore is to consider the difference in the 2 // locations. If the component locations overlap, the fused accuracy is // better than the component accuracies. If they are far apart, // the fused accuracy is much worse. fused.setAccuracy((float)weigh(mGpsLocation.getAccuracy(), mNetworkLocation.getAccuracy())); // fused time - now fused.setTime(System.currentTimeMillis()); fused.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); // fuse altitude if (mGpsLocation.hasAltitude() && !mNetworkLocation.hasAltitude() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setAltitude(mGpsLocation.getAltitude()); // use GPS } else if (!mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setAltitude(mNetworkLocation.getAltitude()); // use Network } else if (mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude()) { fused.setAltitude(weigh(mGpsLocation.getAltitude(), mNetworkLocation.getAltitude())); } // fuse speed if (mGpsLocation.hasSpeed() && !mNetworkLocation.hasSpeed() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setSpeed(mGpsLocation.getSpeed()); // use GPS if its not too old } else if (!mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setSpeed(mNetworkLocation.getSpeed()); // use Network } else if (mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed()) { fused.setSpeed((float)weigh(mGpsLocation.getSpeed(), mNetworkLocation.getSpeed())); } // fuse bearing if (mGpsLocation.hasBearing() && !mNetworkLocation.hasBearing() && mGpsWeight > WEIGHT_THRESHOLD) { fused.setBearing(mGpsLocation.getBearing()); // use GPS if its not too old } else if (!mGpsLocation.hasBearing() && mNetworkLocation.hasBearing() && mNetworkWeight > WEIGHT_THRESHOLD) { fused.setBearing(mNetworkLocation.getBearing()); // use Network } else if (mGpsLocation.hasBearing() && mNetworkLocation.hasBearing()) { fused.setBearing((float)weigh(mGpsLocation.getBearing(), mNetworkLocation.getBearing(), 0.0, 360.0)); } } if (mNetworkLocation != null) { if (mNetworkLocation != null) { fused.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation); mFusedLocation.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation); } } mFusedLocation.setProvider(LocationManager.FUSED_PROVIDER); mFusedLocation = fused; mCallback.reportLocation(mFusedLocation); mCallback.reportLocation(mFusedLocation); } } Loading Loading @@ -349,9 +272,9 @@ public class FusionEngine implements LocationListener { StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder(); s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n'); s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n'); s.append("fused=").append(mFusedLocation).append('\n'); s.append("fused=").append(mFusedLocation).append('\n'); s.append(String.format("gps %.3f %s\n", mGpsWeight, mGpsLocation)); s.append(String.format("gps %s\n", mGpsLocation)); s.append(" ").append(mStats.get(GPS)).append('\n'); s.append(" ").append(mStats.get(GPS)).append('\n'); s.append(String.format("net %.3f %s\n", mNetworkWeight, mNetworkLocation)); s.append(String.format("net %s\n", mNetworkLocation)); s.append(" ").append(mStats.get(NETWORK)).append('\n'); s.append(" ").append(mStats.get(NETWORK)).append('\n'); pw.append(s); pw.append(s); } } Loading