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