am f402fa46: Merge "Simplify fused location provider." into jb-mr1-dev
* commit 'f402fa46257f4ef61ce1e91901dfb44ff5f7f87c': Simplify fused location provider.
This commit is contained in:
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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();
|
||||||
@@ -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()) {
|
||||||
@@ -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) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// A provider is better if the reading is sufficiently newer. Heading
|
||||||
|
// underground can cause GPS to stop reporting fixes. In this case it's
|
||||||
|
// appropriate to revert to cell, even when its accuracy is less.
|
||||||
|
if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static double weighAge(Location loc) {
|
// A provider is better if it has better accuracy. Assuming both readings
|
||||||
long ageSeconds = SystemClock.elapsedRealtimeNanos() - loc.getElapsedRealtimeNanos();
|
// are fresh (and by that accurate), choose the one with the smaller
|
||||||
ageSeconds /= 1000000000L;
|
// accuracy circle.
|
||||||
if (ageSeconds < 0) ageSeconds = 0;
|
if (!locationA.hasAccuracy()) {
|
||||||
return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S);
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!locationB.hasAccuracy()) {
|
||||||
private double weigh(double gps, double network) {
|
return true;
|
||||||
return (gps * mGpsWeight) + (network * mNetworkWeight);
|
}
|
||||||
}
|
return locationA.getAccuracy() < locationB.getAccuracy();
|
||||||
|
|
||||||
private double weigh(double gps, double network, double wrapMin, double wrapMax) {
|
|
||||||
// apply aliasing
|
|
||||||
double wrapWidth = wrapMax - wrapMin;
|
|
||||||
if (gps - network > wrapWidth / 2) network += wrapWidth;
|
|
||||||
else if (network - gps > wrapWidth / 2) gps += wrapWidth;
|
|
||||||
|
|
||||||
double result = weigh(gps, network);
|
|
||||||
|
|
||||||
// remove aliasing
|
|
||||||
if (result > wrapMax) result -= wrapWidth;
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user