Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Fused Location Provider API issues with satellite information (count, signal, etc)

I am working on a project where we are trying to track the position of a device and keep the data for later use. Before I talk about the issue I would like to provide some background.

By searching around StackExchange and Google and everywhere else, I have come to the conclusion that it is virtually impossible to get information about the satellites using the Fused Location API (good job there Google).

The method that most people are using is to actually use a LocationManager along side the Fused location to get the GPS Status. My first question comes here: How can we be 100% sure that the numbers provided by the LocationManager are in sync with what the Fused Location has given us? Does the Fused Location use the Manager internally?

And now the issue. The app is using an "always on" sticky service to pick up the positions no matter what. When there are no satellites everything works as intended. Placing the device to a position where it can see satellites it does not seem to have a lock. Using the debugger the GpsStatus.getSatellites() brings an empty list. Now, without moving the device I start the app Compass (by Catch.com as there are many) that has a GPS type compass scheme. That one locks the satellites, and quite fast, and from that moment on my app also reports the satellites. If the compass is closed then the app gets stuck on the last number the Compass was providing!!! The device I am personally using for testing is a Nexus 7 2013 with its latest official updates (Android 6.0.1).

Here is some code:

public class BackgroundLocationService extends Service implements
    GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener,
    GpsStatus.Listener,
    LocationListener {

// Constants here....

private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
private LocationManager locationManager;
// Flag that indicates if a request is underway.
private boolean mInProgress;

private NotificationManagement myNotificationManager;
private Boolean servicesAvailable = false;

//And other variables here...

@Override
public void onCreate()
{
    super.onCreate();

    myNotificationManager = new NotificationManagement(getApplicationContext());
    myNotificationManager.displayMainNotification();

    mInProgress = false;
    // Create the LocationRequest object
    mLocationRequest = LocationRequest.create();
    // Use high accuracy
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    // Set the update interval
    mLocationRequest.setInterval(PREFERRED_INTERVAL);
    // Set the fastest update interval
    mLocationRequest.setFastestInterval(FASTEST_INTERVAL);

    servicesAvailable = servicesConnected();

    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    locationManager.addGpsStatusListener(this);

    setUpLocationClientIfNeeded();
}

/**
 * Create a new location client, using the enclosing class to
 * handle callbacks.
 */
protected synchronized void buildGoogleApiClient()
{
    this.mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
}

private boolean servicesConnected()
{

    // Check that Google Play services is available
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

    // If Google Play services is available
    if (ConnectionResult.SUCCESS == resultCode)
    {
        return true;
    }
    else
    {
        return false;
    }
}

public int onStartCommand(Intent intent, int flags, int startId)
{
    super.onStartCommand(intent, flags, startId);

    if (!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress)
        return START_STICKY;

    setUpLocationClientIfNeeded();
    if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress)
    {
        mInProgress = true;
        mGoogleApiClient.connect();
    }
    return START_STICKY;
}


private void setUpLocationClientIfNeeded()
{
    if (mGoogleApiClient == null)
        buildGoogleApiClient();
}

public void onGpsStatusChanged(int event)
{

}

// Define the callback method that receives location updates
@Override
public void onLocationChanged(Location location)
{
    simpleGPSFilter(location);
}

// Other fancy and needed stuff here...

/**
 * "Stupid" filter that utilizes experience data to filter out location noise.
 * @param location Location object carrying all the needed information
 */
private void simpleGPSFilter(Location location)
{
    //Loading all the required variables
    int signalPower = 0;
    satellites = 0;
    // Getting the satellites
    mGpsStatus = locationManager.getGpsStatus(mGpsStatus);
    Iterable<GpsSatellite> sats = mGpsStatus.getSatellites();
    if (sats != null)
    {
        for (GpsSatellite sat : sats)
        {
            if (sat.usedInFix())
            {
                satellites++;
                signalPower += sat.getSnr();
            }
        }
    }
    if (satellites != 0)
        signalPower = signalPower/satellites;
    mySpeed = (location.getSpeed() * 3600) / 1000;
    myAccuracy = location.getAccuracy();
    myBearing = location.getBearing();
    latitude = location.getLatitude();
    longitude = location.getLongitude();
    Log.i("START OF CYCLE", "START OF CYCLE");
    Log.i("Sat Strength", Integer.toString(signalPower));
    Log.i("Locked Sats", Integer.toString(satellites));

    // Do the math for the coordinates distance
    /*
     * Earth's radius at given Latitude.
     * Formula: Radius = sqrt( ((equatorR^2 * cos(latitude))^2 + (poleR^2 * sin(latitude))^2 ) / ((equatorR * cos(latitude))^2 + (poleR * sin(latitude))^2)
     * IMPORTANT: Math lib uses radians for the trigonometry equations so do not forget to use toRadians()
     */
    Log.i("Lat for Radius", Double.toString(latitude));
    double earthRadius = Math.sqrt((Math.pow((EARTH_RADIUS_EQUATOR * EARTH_RADIUS_EQUATOR * Math.cos(Math.toRadians(latitude))), 2)
            + Math.pow((EARTH_RADIUS_POLES * EARTH_RADIUS_POLES * Math.cos(Math.toRadians(latitude))), 2))
            / (Math.pow((EARTH_RADIUS_EQUATOR * Math.cos(Math.toRadians(latitude))), 2)
            + Math.pow((EARTH_RADIUS_POLES * Math.cos(Math.toRadians(latitude))), 2)));
    Log.i("Earth Radius", Double.toString(earthRadius));

    /*
     * Calculating distance between 2 points on map using the Haversine formula (arctangent writing) with the following algorithm
     * latDifference = latitude - lastLatitude;
     * lngDifference = longitude - lastLongitude;
     * a = (sin(latDifference/2))^2 + cos(lastLatitude) * cos(latitude) * (sin(lngDifference/2))^2
     * c = 2 * atan2( sqrt(a), sqrt(1-a) )
     * distance = earthRadius * c
     */
    double latDifference = latitude - lastLatitude;
    double lngDifference = longitude - lastLongitude;
    double a = Math.pow((Math.sin(Math.toRadians(latDifference / 2))), 2) + (Math.cos(Math.toRadians(lastLatitude))
            * Math.cos(Math.toRadians(latitude))
            * Math.pow((Math.sin(Math.toRadians(lngDifference / 2))), 2));
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double distance = earthRadius * c;
    Log.i("New point distance", Double.toString(distance));

    // Filter logic
    // Make an initial location log
    if ((!isInit) && (myAccuracy < ACCEPTED_ACCURACY))
    {
        isInit = true;
        lastLatitude = latitude;
        lastLongitude = longitude;
        logLocations(location);
    }
    else
    {
        // Satellite lock (use of GPS) on the higher level
        if (satellites == 0)
        {
            // Accuracy filtering at the second level
            if (myAccuracy < ACCEPTED_ACCURACY)
            {
                if ((distance > ACCEPTED_DISTANCE))
                {
                    lastLatitude = latitude;
                    lastLongitude = longitude;
                    logLocations(location);
                    Log.i("Location Logged", "No Sats");
                    /*
                    // Calculate speed in correlation to perceived movement
                    double speed = distance / (PREFERRED_INTERVAL / 1000);  // TODO: Need to make actual time dynamic as the fused location does not have fixed timing
                    if (speed < ACCEPTED_SPEED)
                    {
                        lastLatitude = latitude;
                        lastLongitude = longitude;
                        logLocations(location);
                    } */
                }
            }
        }
        else if ((satellites < 4) && (signalPower > ACCEPTED_SIGNAL))
        {
            if (myAccuracy < (ACCEPTED_ACCURACY + 50))
            {
                logLocations(location);
                Log.i("Location Logged", "With Sats");
            }
        }
        else
        {
            if (myAccuracy < (ACCEPTED_ACCURACY + 100))
            {
                lastSpeed = mySpeed;
                lastBearing = myBearing;
                lastLatitude = latitude;
                lastLongitude = longitude;
                logLocations(location);
                Log.i("Location Logged", "With Good Sats");
            }
        }
    }
    Log.i("END OF CYCLE", "END OF CYCLE");
}

private void logLocations(Location location)
{
    String myprovider = "false";

    String temp = timestampFormat.format(location.getTime());
    MySQLiteHelper dbHelper = new MySQLiteHelper(getApplicationContext());

    try
    {
        dbHelper.createEntry(latitude, longitude, allschemes, temp, mySpeed, myAccuracy, myBearing, myprovider, satellites);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    CheckAutoArrive(String.valueOf(latitude), String.valueOf(longitude));

}

This is the part of the code I think might be needed. I am leaving all the filtering code there along with the math to compute Earth's radius given the latitude and the distance between 2 points on the map. Feel free to use that if you need it.

In connection to the fact that the Compass app can actually make the system get satellites while my app cannot. Is there a way to actually force read the location services? Is it possible that the Fused Location actually uses GPS but the Location Manager does not know it?

Finally I would like to mention that the application has been tested in other devices (phones, not tablets) with different versions of Android and seems to be working properly.

Any ideas would be more than welcome. And of course go ahead and ask anything I might have forgotten to mention.

EDIT : My actual questions were hidden in the text so to lay them out:

1) Are the Location data we get from Fused Location and the rest of the GPS data we can, seemingly, only get from the Location Manager in sync or is there the possibility to get a Location but wrong number of locked satellites for the particular point?

2) What could be the reason behind the weird behavior where the application cannot get a lock to satellites but if the lock comes from another application it seems to be used properly by the application? To make this even weirder this happens to a Nexus 7 (Android 6.0.1) but not to other devices tested with different Android versions.

like image 290
MadDim Avatar asked Oct 20 '16 18:10

MadDim


People also ask

How accurate is fused location provider?

The accuracy values are typically more than 10 meters, while a third party GPS app (such as GPS test) typically reaches better accuracy if I wait long enough.

What does fused location mean on Android?

The fused location provider is a location API in Google Play services that intelligently combines different signals to provide the location information that your app needs.

How do I disable fused location provider?

Download the app Disable Service. In the app select the System tab search for the service named Fused Location and disable it. Also search for the service group Google Play Services. Inside it there will be another Fused Location service.


1 Answers

From my understanding:

1)

The FusedLocationApi returns a new location every time there is a new reading from any relevant provider on the client device (WiFi, CellTower, GPS, Bluetooth). This reading gets fused with the previous location estimate (probably using an extended Kalman filter or similar). The resulting location update is a fused estimate of several sources, which is why there is no metadata from individual providers attached.

So the Location data you get from the API may coincide with a pure GPS reading obtained from the LocationManager (if GPS was the most recent and relevant location source), but it doesn't have to. Consequently, the number of satellites obtained from the last pure GPS reading may apply to the latest Location returned by the FusedLocationApi or it may not.

In short:

There is no guarantee that a location reading obtained from the LocationManager is in sync with a location from the FusedLocationApi.

2)

First off: To pinpoint the root cause of this issue you need to test with several devices at several locations. Since you asked

What could be the reason behind the weird behavior?

I'll throw one theory out there: Assuming that the LocationManager and the FusedLocationApi work completely separately, the LocationManager may be struggling to obtain a fix because you're relying on GPS only. Try using the NETWORK_PROVIDER in addition to GPS to speed up the time-to-first-fix (thus enabling the LocationManager to make use of Assisted GPS). Other apps (like the Compass app) are almost certainly doing this, which would explain why they are getting a quicker fix. Note: After you start to receive GPS data, you can of course unregister the network provider. Or you keep it on but simply ignore its updates.

That's one possible explanation for the weird behavior. You are probably aware that location behavior is dependent on the device, OS, GPS chipset, firmware and location you are in - so if you plan to go manual (i.e. not use the FusedLocationApi) you will have to experiment quite a bit.


In addition to the answers, let me provide an opinionated take on your problem (to be taken with a grain of salt ;-): I think you're trying to combine two things that were made for very different use cases and aren't meant to be combined.

Obtaining the number of satellites is something utterly technical. No end user will ever be interested in this kind of info, unless your application teaches them about GNSS. If you want to record it for internal analytics purposes that's fine, but then you must be able to deal with situations where this info is not available.

Scenario 1: For some reason you decide that you absolutely need the detailed (technical) specifics of GPS readings. In that case, build the logic yourself, the old school way. I.e. ask for GPS readings (and possibly speed up this process by using the network provider) via the LocationManager, then fuse this stuff on your own. However, in this scenario don't ever touch the FusedLocationApi. Even though it may seem old-fashioned and arcane nowadays, using the LocationManager with DIY fusion logic still makes perfect sense for a small number of use cases. That's why the API is still around.

Scenario 2: You simply want to get quick and accurate updates on the client's location. In that case specify your desired update frequency and accuracy and let the FusedLocationApi do its job. The FusedLocationApi has come a long way in the past years and chances are that nowadays it will be quicker and better at figuring out how to obtain location info than any DIY logic. This is because obtaining location info is a very heterogenous problem that depends on the capabilities of the client device (chipset, firmware, OS, GPS, WiFi, GSM/LTE, Bluetooth etc.) as much as on the physical surroundings (WiFi/CellTower/Bluetooth signals nearby, inside or outside, clear sky or urban canyon etc.). In this scenario, don't touch the manual providers. If you do, don't expect to make any meaningful inferences on the relation between the readings of individual providers and the fused results.


Two final remarks:

  • Android provides Location.distanceTo() and Location.distanceBetween(), so there is no need to implement the Haversine formula in your code.

  • If you simply need quick and reliable updates from the FusedLocationApi, I have written a small utility class, called the LocationAssistant, that simplifies the setup and does most of the heavy lifting for you.

like image 164
KlaasNotFound Avatar answered Sep 27 '22 18:09

KlaasNotFound