@@ -42,16 +42,7 @@ public interface Callback {
4242 private static final String NETWORK = LocationManager.NETWORK_PROVIDER;
4343 private static final String GPS = LocationManager.GPS_PROVIDER;
4444
45- // threshold below which a location is considered stale enough
46- // that we shouldn't use its bearing, altitude, speed etc
47- private static final double WEIGHT_THRESHOLD = 0.5;
48- // accuracy in meters at which a Location's weight is halved (compared to 0 accuracy)
49- private static final double ACCURACY_HALFLIFE_M = 20.0;
50- // age in seconds at which a Location's weight is halved (compared to 0 age)
51- private static final double AGE_HALFLIFE_S = 60.0;
52-
53- private static final double ACCURACY_DECAY_CONSTANT_M = Math.log(2) / ACCURACY_HALFLIFE_M;
54- private static final double AGE_DECAY_CONSTANT_S = Math.log(2) / AGE_HALFLIFE_S;
45+ public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000; // 11 seconds
5546
5647 private final Context mContext;
5748 private final LocationManager mLocationManager;
@@ -62,8 +53,6 @@ public interface Callback {
6253 private Location mFusedLocation;
6354 private Location mGpsLocation;
6455 private Location mNetworkLocation;
65- private double mNetworkWeight;
66- private double mGpsWeight;
6756
6857 private boolean mEnabled;
6958 private ProviderRequestUnbundled mRequest;
@@ -102,10 +91,6 @@ public void deinit() {
10291 Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")");
10392 }
10493
105- private boolean isAvailable() {
106- return mStats.get(GPS).available || mStats.get(NETWORK).available;
107- }
108-
10994 /** Called on mLooper thread */
11095 public void enable() {
11196 mEnabled = true;
@@ -130,7 +115,6 @@ private static class ProviderStats {
130115 public boolean requested;
131116 public long requestTime;
132117 public long minTime;
133- public long lastRequestTtff;
134118 @Override
135119 public String toString() {
136120 StringBuilder s = new StringBuilder();
@@ -171,9 +155,6 @@ private void updateRequirements() {
171155 return;
172156 }
173157
174- ProviderStats gpsStats = mStats.get(GPS);
175- ProviderStats networkStats = mStats.get(NETWORK);
176-
177158 long networkInterval = Long.MAX_VALUE;
178159 long gpsInterval = Long.MAX_VALUE;
179160 for (LocationRequest request : mRequest.getLocationRequests()) {
@@ -209,104 +190,46 @@ private void updateRequirements() {
209190 }
210191 }
211192
212- private static double weighAccuracy(Location loc) {
213- double accuracy = loc.getAccuracy();
214- return Math.exp(-accuracy * ACCURACY_DECAY_CONSTANT_M);
215- }
216-
217- private static double weighAge(Location loc) {
218- long ageSeconds = SystemClock.elapsedRealtimeNanos() - loc.getElapsedRealtimeNanos();
219- ageSeconds /= 1000000000L;
220- if (ageSeconds < 0) ageSeconds = 0 ;
221- return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S);
222- }
223-
224- private double weigh(double gps, double network) {
225- return (gps * mGpsWeight ) + (network * mNetworkWeight);
226- }
227-
228- private double weigh(double gps, double network, double wrapMin, double wrapMax) {
229- // apply aliasing
230- double wrapWidth = wrapMax - wrapMin;
231- if (gps - network > wrapWidth / 2) network += wrapWidth;
232- else if (network - gps > wrapWidth / 2) gps += wrapWidth;
233-
234- double result = weigh(gps, network);
235-
236- // remove aliasing
237- if (result > wrapMax) result -= wrapWidth;
238- return result ;
193+ /**
194+ * Test whether one location (a) is better to use than another (b).
195+ */
196+ private static boolean isBetterThan(Location locationA, Location locationB) {
197+ if (locationA == null) {
198+ return false;
199+ }
200+ if (locationB == null) {
201+ return true ;
202+ }
203+ // A provider is better if the reading is sufficiently newer. Heading
204+ // underground can cause GPS to stop reporting fixes. In this case it's
205+ // appropriate to revert to cell, even when its accuracy is less.
206+ if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos( ) + SWITCH_ON_FRESHNESS_CLIFF_NS) {
207+ return true;
208+ }
209+
210+ // A provider is better if it has better accuracy. Assuming both readings
211+ // are fresh (and by that accurate), choose the one with the smaller
212+ // accuracy circle.
213+ if (!locationA.hasAccuracy()) {
214+ return false;
215+ }
216+ if (!locationB.hasAccuracy()) {
217+ return true;
218+ }
219+ return locationA.getAccuracy() < locationB.getAccuracy() ;
239220 }
240221
241222 private void updateFusedLocation() {
242- // naive fusion
243- mNetworkWeight = weighAccuracy(mNetworkLocation) * weighAge(mNetworkLocation);
244- mGpsWeight = weighAccuracy(mGpsLocation) * weighAge(mGpsLocation);
245- // scale mNetworkWeight and mGpsWeight so that they add to 1
246- double totalWeight = mNetworkWeight + mGpsWeight;
247- mNetworkWeight /= totalWeight;
248- mGpsWeight /= totalWeight;
249-
250- Location fused = new Location(LocationManager.FUSED_PROVIDER);
251- // fuse lat/long
252- // assumes the two locations are close enough that earth curvature doesn't matter
253- fused.setLatitude(weigh(mGpsLocation.getLatitude(), mNetworkLocation.getLatitude()));
254- fused.setLongitude(weigh(mGpsLocation.getLongitude(), mNetworkLocation.getLongitude(),
255- -180.0, 180.0));
256-
257- // fused accuracy
258- //TODO: use some real math instead of this crude fusion
259- // one suggestion is to fuse in a quadratic manner, eg
260- // sqrt(weigh(gpsAcc^2, netAcc^2)).
261- // another direction to explore is to consider the difference in the 2
262- // locations. If the component locations overlap, the fused accuracy is
263- // better than the component accuracies. If they are far apart,
264- // the fused accuracy is much worse.
265- fused.setAccuracy((float)weigh(mGpsLocation.getAccuracy(), mNetworkLocation.getAccuracy()));
266-
267- // fused time - now
268- fused.setTime(System.currentTimeMillis());
269- fused.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
270-
271- // fuse altitude
272- if (mGpsLocation.hasAltitude() && !mNetworkLocation.hasAltitude() &&
273- mGpsWeight > WEIGHT_THRESHOLD) {
274- fused.setAltitude(mGpsLocation.getAltitude()); // use GPS
275- } else if (!mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude() &&
276- mNetworkWeight > WEIGHT_THRESHOLD) {
277- fused.setAltitude(mNetworkLocation.getAltitude()); // use Network
278- } else if (mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude()) {
279- fused.setAltitude(weigh(mGpsLocation.getAltitude(), mNetworkLocation.getAltitude()));
280- }
281-
282- // fuse speed
283- if (mGpsLocation.hasSpeed() && !mNetworkLocation.hasSpeed() &&
284- mGpsWeight > WEIGHT_THRESHOLD) {
285- fused.setSpeed(mGpsLocation.getSpeed()); // use GPS if its not too old
286- } else if (!mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed() &&
287- mNetworkWeight > WEIGHT_THRESHOLD) {
288- fused.setSpeed(mNetworkLocation.getSpeed()); // use Network
289- } else if (mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed()) {
290- fused.setSpeed((float)weigh(mGpsLocation.getSpeed(), mNetworkLocation.getSpeed()));
291- }
292-
293- // fuse bearing
294- if (mGpsLocation.hasBearing() && !mNetworkLocation.hasBearing() &&
295- mGpsWeight > WEIGHT_THRESHOLD) {
296- fused.setBearing(mGpsLocation.getBearing()); // use GPS if its not too old
297- } else if (!mGpsLocation.hasBearing() && mNetworkLocation.hasBearing() &&
298- mNetworkWeight > WEIGHT_THRESHOLD) {
299- fused.setBearing(mNetworkLocation.getBearing()); // use Network
300- } else if (mGpsLocation.hasBearing() && mNetworkLocation.hasBearing()) {
301- fused.setBearing((float)weigh(mGpsLocation.getBearing(), mNetworkLocation.getBearing(),
302- 0.0, 360.0));
223+ // may the best location win!
224+ if (isBetterThan(mGpsLocation, mNetworkLocation)) {
225+ mFusedLocation = new Location(mGpsLocation);
226+ } else {
227+ mFusedLocation = new Location(mNetworkLocation);
303228 }
304-
305229 if (mNetworkLocation != null) {
306- fused .setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation);
230+ mFusedLocation .setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation);
307231 }
308-
309- mFusedLocation = fused;
232+ mFusedLocation.setProvider(LocationManager.FUSED_PROVIDER);
310233
311234 mCallback.reportLocation(mFusedLocation);
312235 }
@@ -349,9 +272,9 @@ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
349272 StringBuilder s = new StringBuilder();
350273 s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n');
351274 s.append("fused=").append(mFusedLocation).append('\n');
352- s.append(String.format("gps %.3f % s\n", mGpsWeight , mGpsLocation));
275+ s.append(String.format("gps %s\n", mGpsLocation));
353276 s.append(" ").append(mStats.get(GPS)).append('\n');
354- s.append(String.format("net %.3f % s\n", mNetworkWeight , mNetworkLocation));
277+ s.append(String.format("net %s\n", mNetworkLocation));
355278 s.append(" ").append(mStats.get(NETWORK)).append('\n');
356279 pw.append(s);
357280 }
0 commit comments