diff options
author | vng <viktor.govako@gmail.com> | 2013-09-19 19:54:33 +0400 |
---|---|---|
committer | Alex Zolotarev <alex@maps.me> | 2015-09-23 02:02:11 +0300 |
commit | 8816d36b62f5b99fe0147a97afcf9707a1d8c1dc (patch) | |
tree | 2358dc753d79c3c2459d485b9605506a49cb1cd4 | |
parent | a6d0e9b2f594ebc27b8e4ba573ee4665154ad208 (diff) |
[android] New location filter strategy with accuracy, speed and time delta formula.
3 files changed, 101 insertions, 224 deletions
diff --git a/android/src/com/mapswithme/maps/location/LocationService.java b/android/src/com/mapswithme/maps/location/LocationService.java index be980c380e..1300d25709 100644 --- a/android/src/com/mapswithme/maps/location/LocationService.java +++ b/android/src/com/mapswithme/maps/location/LocationService.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import android.annotation.SuppressLint; import android.content.Context; import android.hardware.GeomagneticField; import android.hardware.Sensor; @@ -16,12 +17,13 @@ import android.location.LocationListener; import android.location.LocationManager; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.SystemClock; import android.view.Display; import android.view.Surface; import com.mapswithme.maps.MWMApplication; import com.mapswithme.util.ConnectionState; -import com.mapswithme.util.LocationUtils; +import com.mapswithme.util.Utils; import com.mapswithme.util.log.Logger; import com.mapswithme.util.log.StubLogger; @@ -30,6 +32,11 @@ public class LocationService implements LocationListener, SensorEventListener, W { private Logger mLogger = StubLogger.get();//SimpleLogger.get(this.toString()); + private static final double DEFAULT_SPEED_MpS = 5; + private static final float DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M = 1000; + private static final float MIN_SPEED_CALC_DIRECTION_MpS = 1; + private static final long LOCATION_EXPIRATION_TIME_MILLIS = 5 * 60 * 1000; + /// These constants should correspond to values defined in platform/location.hpp /// Leave 0-value as no any error. public static final int ERROR_NOT_SUPPORTED = 1; @@ -45,8 +52,11 @@ public class LocationService implements LocationListener, SensorEventListener, W private HashSet<Listener> mObservers = new HashSet<Listener>(10); - /// Used to filter locations from different providers + /// Last accepted location private Location mLastLocation = null; + /// System timestamp for the last location + private long mLastLocationTime; + /// Current heading if we are moving (-1.0 otherwise) private double mDrivingHeading = -1.0; private WifiLocation mWifiScanner = null; @@ -103,17 +113,55 @@ public class LocationService implements LocationListener, SensorEventListener, W it.next().onCompassUpdated(time, magneticNorth, trueNorth, accuracy); } - /* - private void printLocation(Location l) + private static boolean isSameLocationProvider(String p1, String p2) { - final String p = l.getProvider(); - Log.d(TAG, "Lat = " + l.getLatitude() + - "; Lon = " + l.getLongitude() + - "; Time = " + l.getTime() + - "; Acc = " + l.getAccuracy() + - "; Provider = " + (p != null ? p : "")); + if (p1 == null || p2 == null) + return false; + return p1.equals(p2); + } + + @SuppressLint("NewApi") + private double getLocationTimeDiffS(Location l) + { + if (Utils.apiEqualOrGreaterThan(17)) + return (l.getElapsedRealtimeNanos() - mLastLocation.getElapsedRealtimeNanos()) * 1.0E-9; + else + { + long time = l.getTime(); + long lastTime = mLastLocation.getTime(); + if (!isSameLocationProvider(l.getProvider(), mLastLocation.getProvider())) + { + // Do compare current and previous system times in case when + // we have incorrect time settings on a device. + time = System.currentTimeMillis(); + lastTime = mLastLocationTime; + } + + return (time - lastTime) * 1.0E-3; + } + } + + private boolean isLocationBetter(Location l) + { + if (l == null) + return false; + if (mLastLocation == null) + return true; + + final double s = Math.max(DEFAULT_SPEED_MpS, (l.getSpeed() + mLastLocation.getSpeed()) / 2.0); + return (l.getAccuracy() < (mLastLocation.getAccuracy() + s * getLocationTimeDiffS(l))); + } + + @SuppressLint("NewApi") + private static boolean isNotExpired(Location l, long t) + { + long timeDiff; + if (Utils.apiEqualOrGreaterThan(17)) + timeDiff = (SystemClock.elapsedRealtimeNanos() - l.getElapsedRealtimeNanos()) / 1000000; + else + timeDiff = System.currentTimeMillis() - t; + return (timeDiff <= LOCATION_EXPIRATION_TIME_MILLIS); } - */ public void startUpdate(Listener observer) { @@ -146,34 +194,23 @@ public class LocationService implements LocationListener, SensorEventListener, W registerSensorListeners(); // Choose best location from available - List<Location> notExpiredLocations = getAllNotExpiredLocations(providers); - Location lastKnownLocation = null; + final Location l = getBestLastLocation(providers); + mLogger.d("Last location: ", l); - if (notExpiredLocations.size() > 0) + if (isLocationBetter(l)) { - final Location newestLocation = LocationUtils.getNewestLocation(notExpiredLocations); - mLogger.d("Last newest location: ", newestLocation); - final Location mostAccurateLocation = LocationUtils.getMostAccurateLocation(notExpiredLocations); - mLogger.d("Last accurate location: ", mostAccurateLocation); - - if (LocationUtils.isFirstOneBetterLocation(newestLocation, mostAccurateLocation)) - lastKnownLocation = newestLocation; - else - lastKnownLocation = mostAccurateLocation; + // get last better location + emitLocation(l); } - - if (LocationUtils.isNotExpired(mLastLocation) && - LocationUtils.isFirstOneBetterLocation(mLastLocation, lastKnownLocation)) + else if (mLastLocation != null && isNotExpired(mLastLocation, mLastLocationTime)) { - lastKnownLocation = mLastLocation; + // notify UI about last valid location + notifyLocationUpdated(mLastLocation); } - - // Pass last known location only in the end of all registerListener - // in case, when we want to disconnect in listener. - if (lastKnownLocation != null) + else { - LocationUtils.hackLocationTime(lastKnownLocation); - emitLocation(lastKnownLocation); + // forget about old location + mLastLocation = null; } } @@ -258,32 +295,26 @@ public class LocationService implements LocationListener, SensorEventListener, W } } - private static final long MAXTIME_CALC_DIRECTIONS = 1000 * 10; - - private List<Location> getAllNotExpiredLocations(List<String> providers) + private Location getBestLastLocation(List<String> providers) { - List<Location> locations = new ArrayList<Location>(providers.size()); + Location res = null; for (String pr : providers) { final Location l = mLocationManager.getLastKnownLocation(pr); - if (LocationUtils.isNotExpired(l)) - locations.add(l); + if (l != null && isNotExpired(l, l.getTime())) + { + if (res == null || res.getAccuracy() > l.getAccuracy()) + res = l; + } } - - return locations; + return res; } - private void calcDirection(Location l, long t) + private void calcDirection(Location l) { - // Try to calculate user direction if he is moving and - // we have previous close position. - if ((l.getSpeed() >= 1.0) && (t - mLastLocation.getTime() <= MAXTIME_CALC_DIRECTIONS)) - { - if (l.hasBearing()) - mDrivingHeading = bearingToHeading(l.getBearing()); - else if (mLastLocation.distanceTo(l) > 5.0) - mDrivingHeading = bearingToHeading(mLastLocation.bearingTo(l)); - } + // Try to calculate direction if we are moving + if (l.getSpeed() >= MIN_SPEED_CALC_DIRECTION_MpS && l.hasBearing()) + mDrivingHeading = bearingToHeading(l.getBearing()); else mDrivingHeading = -1.0; } @@ -293,12 +324,11 @@ public class LocationService implements LocationListener, SensorEventListener, W mLogger.d("Location accepted: ", l); mLastLocation = l; + mLastLocationTime = System.currentTimeMillis(); + notifyLocationUpdated(l); } - /// Delta distance when we need to recreate GeomagneticField (to calculate declination). - private final static float DISTANCE_TO_RECREATE_MAGNETIC_FIELD = 1000.0f; - @Override public void onLocationChanged(Location l) { @@ -308,19 +338,16 @@ public class LocationService implements LocationListener, SensorEventListener, W if (l.getAccuracy() <= 0.0) return; - LocationUtils.hackLocationTime(l); - if (LocationUtils.isFirstOneBetterLocation(l, mLastLocation)) + if (isLocationBetter(l)) { - final long timeNow = System.currentTimeMillis(); - if (mLastLocation != null) - calcDirection(l, timeNow); + calcDirection(l); // Used for more precise compass updates if (mSensorManager != null) { // Recreate magneticField if location has changed significantly if (mMagneticField == null || - (mLastLocation == null || l.distanceTo(mLastLocation) > DISTANCE_TO_RECREATE_MAGNETIC_FIELD)) + (mLastLocation == null || l.distanceTo(mLastLocation) > DISTANCE_TO_RECREATE_MAGNETIC_FIELD_M)) { mMagneticField = new GeomagneticField((float)l.getLatitude(), (float)l.getLongitude(), (float)l.getAltitude(), l.getTime()); diff --git a/android/src/com/mapswithme/maps/location/WifiLocation.java b/android/src/com/mapswithme/maps/location/WifiLocation.java index 1101c252f2..631eaa3b31 100644 --- a/android/src/com/mapswithme/maps/location/WifiLocation.java +++ b/android/src/com/mapswithme/maps/location/WifiLocation.java @@ -10,6 +10,7 @@ import java.util.List; import org.json.JSONException; import org.json.JSONObject; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -19,8 +20,8 @@ import android.location.LocationManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.os.AsyncTask; +import android.os.SystemClock; -import com.mapswithme.util.LocationUtils; import com.mapswithme.util.Utils; import com.mapswithme.util.log.Logger; import com.mapswithme.util.log.StubLogger; @@ -32,7 +33,7 @@ public class WifiLocation extends BroadcastReceiver private static final String MWM_GEOLOCATION_SERVER = "http://geolocation.server/"; /// Limit received WiFi accuracy with 20 meters. - private static final double MIN_PASSED_ACCURACY = 20; + private static final double MIN_PASSED_ACCURACY_M = 20; public interface Listener { @@ -77,6 +78,14 @@ public class WifiLocation extends BroadcastReceiver mWifi = null; } + @SuppressLint("NewApi") + private void setLocationCurrentTime(Location l) + { + if (Utils.apiEqualOrGreaterThan(17)) + l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + l.setTime(System.currentTimeMillis()); + } + @Override public void onReceive(Context context, Intent intent) { @@ -199,7 +208,7 @@ public class WifiLocation extends BroadcastReceiver Utils.closeStream(wr); // Get the response - mLogger.d("Get JSON responce with code = ", conn.getResponseCode()); + mLogger.d("Get JSON response with code = ", conn.getResponseCode()); rd = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line = null; String response = ""; @@ -213,10 +222,10 @@ public class WifiLocation extends BroadcastReceiver final double acc = jLocation.getDouble("accuracy"); mLocation = new Location("wifiscanner"); - mLocation.setAccuracy((float) Math.max(MIN_PASSED_ACCURACY, acc)); + mLocation.setAccuracy((float) Math.max(MIN_PASSED_ACCURACY_M, acc)); mLocation.setLatitude(lat); mLocation.setLongitude(lon); - LocationUtils.setLocationCurrentTime(mLocation); + setLocationCurrentTime(mLocation); return true; } diff --git a/android/src/com/mapswithme/util/LocationUtils.java b/android/src/com/mapswithme/util/LocationUtils.java deleted file mode 100644 index 85ec818bd7..0000000000 --- a/android/src/com/mapswithme/util/LocationUtils.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.mapswithme.util; - -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; - -import android.annotation.SuppressLint; -import android.location.Location; -import android.os.SystemClock; - -/** - * Locations utils from {@link http://developer.android.com/guide/topics/location/strategies.html} - * Partly modified and suited for MWM. - */ -public class LocationUtils -{ - private static final int TWO_MINUTES = 2 * 60 * 1000; - private static final long LOCATION_EXPIRATION_TIME = 5 * 60 * 1000; - - @SuppressLint("NewApi") - public static boolean isNotExpired(Location l) - { - if (l != null) - { - long timeDiff; - if (Utils.apiEqualOrGreaterThan(17)) - timeDiff = (SystemClock.elapsedRealtimeNanos() - l.getElapsedRealtimeNanos()) / 1000000; - else - timeDiff = System.currentTimeMillis() - l.getTime(); - return (timeDiff <= LOCATION_EXPIRATION_TIME); - } - return false; - } - - @SuppressLint("NewApi") - public static long timeDiffMillis(Location l1, Location l2) - { - if (Utils.apiEqualOrGreaterThan(17)) - return (l1.getElapsedRealtimeNanos() - l2.getElapsedRealtimeNanos()) / 1000000; - else - return (l1.getTime() - l2.getTime()); - } - - @SuppressLint("NewApi") - public static void setLocationCurrentTime(Location l) - { - if (Utils.apiEqualOrGreaterThan(17)) - l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); - l.setTime(System.currentTimeMillis()); - } - - /// Call this function before comparing or after getting location to - /// avoid troubles with invalid system time. - @SuppressLint("NewApi") - public static void hackLocationTime(Location l) - { - if (Utils.apiLowerThan(17)) - l.setTime(System.currentTimeMillis()); - } - - /** Determines whether one Location reading is better than the current Location fix - * @param firstLoc The new Location that you want to evaluate - * @param secondLoc The current Location fix, to which you want to compare the new one - */ - public static boolean isFirstOneBetterLocation(Location firstLoc, Location secondLoc) - { - if (firstLoc == null) - return false; - if (secondLoc == null) - return true; - - // Check whether the new location fix is newer or older - final long timeDelta = timeDiffMillis(firstLoc, secondLoc); - - // If it's been more than two minutes since the current location, - // use the new location because the user has likely moved - if (timeDelta > TWO_MINUTES) - { - // significantly newer - return true; - } - else if (timeDelta < -TWO_MINUTES) - { - // significantly older - return false; - } - - // Check whether the new location fix is more or less accurate - final float accuracyDelta = firstLoc.getAccuracy() - secondLoc.getAccuracy(); - // Relative difference, not absolute - final boolean almostAsAccurate = Math.abs(accuracyDelta) <= 0.1*secondLoc.getAccuracy(); - - // Determine location quality using a combination of timeliness and accuracy - final boolean isNewer = timeDelta > 0; - if (accuracyDelta < 0) - { - // more accurate and has the same time order - return true; - } - else if (isNewer && almostAsAccurate) - { - // newer and has the same accuracy order - return true; - } - else if (isNewer && accuracyDelta <= 200 && - isSameProvider(firstLoc.getProvider(), secondLoc.getProvider())) - { - // not significantly less accurate and from the same provider - return true; - } - - return false; - } - - /** Checks whether two providers are the same */ - public static boolean isSameProvider(String provider1, String provider2) - { - if (provider1 == null) - return provider2 == null; - else - return provider1.equals(provider2); - } - - public static Location getBestLocation(Collection<Location> locations, Comparator<Location> comparator) - { - return Collections.max(locations, comparator); - } - - public static Location getMostAccurateLocation(Collection<Location> locations) - { - final Comparator<Location> accuracyComparator = new Comparator<Location>() - { - @Override - public int compare(Location lhs, Location rhs) - { - return (int) (1 * Math.signum(rhs.getAccuracy() - lhs.getAccuracy())); - } - }; - - return getBestLocation(locations, accuracyComparator); - } - - public static Location getNewestLocation(Collection<Location> locations) - { - final Comparator<Location> newerFirstComparator = new Comparator<Location>() - { - @Override - public int compare(Location lhs, Location rhs) - { - return (int) (1 * Math.signum(timeDiffMillis(lhs, rhs))); - } - }; - - return getBestLocation(locations, newerFirstComparator); - } - - - private LocationUtils() {}; -} |