Search code examples
androidandroid-gps

Android - How to Calculate Distance Traveled


I am using the FusedLocationProviderClient to get locationUpdates after setting up LocationRequest properties.

To calculate 'distance traveled' I thought I would do the following:

Location lastLocation;   // initialized to start location with getLocation() - code not shown.
LocationCallback locationCallback;
Double DistanceTraveled = 0.0;

locationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
        if (locationResult != null) {
            // Get last location in result if more than one are returned.
            Location thisLocation = locationResult.getLastLocation();
            // Sum up DistanceTraveled
            DistanceTraveled = DistanceTraveled + lastLocation.distanceTo(thisLocation);
            // Save thisLocation for next time onLocationResult() is called
            lastLocation.set(thisLocation);
        }
    }
}

Well, this doesn't work very well. On every callback, the exact location changes by 0 meters to 10 meters randomly just due to the accuracy of the result. So, if I am standing perfectly still for 10 minutes with a 5 second update using this algorithm, it will sum up several meters that I have traveled, when I haven't moved at all!

What should I be doing to get an accurate accounting of my distance traveled? What options exist?


Solution

  • Ok - it has been 12 days since I posted my question here. Lots of reading, testing, coding, more testing, more reading. I now have a responsibility to contribute to the site that gives so much to me. So here goes.

    First, the following post has many tidbits of info and links to assist. calculate actual distance travelled by mobile

    Now, I am specifically concerned about tracking the distance traveled by someone walking; to be exact while bird watching. My app is for bird watchers. So, I am interested in logging the birds seen, along with where, and tracking the overall distance walked. It turns out these are two different issues. Getting the current coords for a sighting of a bird is easy stuff; just get the location. The accuracy is that which is reported.

    Now, to the problem of this post. Calculating distance walked. That is a different problem altogether. And this is what I was looking for when posting the question. How do people get an accurate accounting of distance traveled, when walking? Because just registering for location updates and then summing up the Location1.distanceTo(Location2) usually gives one a wildly inflated total distance. Why? I didn't understand the problem. Now I do. And if I had this understanding, then the path to the solution would have been clearer.

    Let me give an example. Let's say I walk for 10 seconds. At second 0 (t0) is where I start. And let's say I move 1 meter by t1. I stand at this position until t5, and then walk for another 5 seconds until t10. A combination of walking/standing help to illustrate the problem.

    Each GPS location update contains a coordinate (lat/lng), or a point, along with an accuracy rating in meters. Along with other data, but the only other one I care about is time in milliseconds. In this example, always 1000 ms more than the last update.

    Think of this GPS update as a point at the epicenter of a circle with radius accuracy-in-meters as returned. Your actual location could be anywhere within that circle or on the circle's edge, in theory. Not sure how accurate that accuracy-in-meters is (seems like it should have it's own accuracy rating), but let's assume it is accurate, and your true location is no more than accuracy-in-meters away from the point reported. In fact, let's assume it is always that distance from your true location for purposes of illustration. Let's also assume that this accuracy-in-meters is 20m in this example for each point returned.

    So, at t0, my true location could be 20m ahead of where it reported. I walk 1 meter forward and location update at t1 reports I am 20m ahead of where I truly am. So, computing t0.distanceTo(t1) reports that I have moved 40m when I have truly only moved 1m. Starting to see the picture? Read on...

    Now, I stand here until t5. And I get 4 more location updates, which for example purposes, are 20m ahead, 20 meters behind, 20 meters to my left and 20 meters to my right. (This is an extreme example, but uses easy numbers for illustration). So, in addition to my supposed 40m of travel after 1 second, it now thinks I have moved approximately 20x4 or 80m more, for a total of 120m. And I have only moved 1m in reality! Continue to walk 5 more meters to t10 and your distance could again be 5x20 or 100m more for a total of 220m, when you have only walked 6m in 10 seconds. That is why simply summing up distance traveled will never be accurate as long as there is any error in the accuracy.

    That's the problem. And until one understands that, you are befuddled by how this crappy Galaxy S9 is doing this to you.

    What about averaging points? "midpoint(t0,t1).distanceTo(midpoint(t2,t3)" and so on? This will always produce less-than true distance traveled. Depending on movement, sometimes by a lot.

    There are lots of methods (see link above) to try in order to reduce the error in Distance Traveled calculations. I have found that my method below produces more accurate results when walking, than Google's MyTracks app. I have repeatedly tested at 200m, 500m and 1000m distances. And this consistently produces good results usually with < 10% error. Half the time, Google's MyTracks says I have walked 500m+ on the 200m test.

    onLocationResult(LocationResult locationResult) {
    
        lastLocationReceived.set(locationResult.getLastLocation());
        // Throw away updates that don't meet a certain accuracy. e.g. 30m
        if (lastLocationReceived.hasAccuracy() && locatLocationReceived.getAccuracy() <= MIN_ACC_METERS_FOR_DISTANCE_TRAVELED) {
            // Don't use it if the current accuracy X 1.5 > distance traveled since last valid location
            if ((lastLocationReceived.getAccuracy() * 1.5) < loastLocationReceived.distanceTo(lastLocationUsedForDistanceTraveled) {
                // Calculate speed of travel to last valid location; avg walking speed is 1.4m/s; I used 2.0m/s as a high end value. 
                // Throw away values that are too high because it would have required that you run to get there.
                // Sometimes the location is somewhere you could not have gotten to given the time.
                long timeDelta = lastLocationReceived.getTime() - lastLocationUsedForDistanceTraveled.getTime();
                if ((lastLocationReceived.distanceTo(lastLocationUsedForDistanceTraveled) / timeDelta) > MAX_WALKING_SPEED) {
                    // NOW we have a value we can use to minimize error
                    distanceTraveled += lastLocationReceived.distanceTo(lastLocationUsedForDistanceTraveled);
                    // Update last valid location
                    lastLocationUsedForDistanceTraveled.set(lastLocationReceived);
                }
            }
        }
    };
    

    Notes:

    1. onPause() for activity stop locationUpdates.
    2. onResume() restart locationUpdates.
    3. Setting/using locationRequest.setSmallestDisplacement() produced strange results for me. I ended up never setting this value in my locationRequest. How does it know you have moved 5m when the hAccuracy is 20m?
    4. I don't find a need to keep getting location updates with the app paused (in one's pocket). As soon as you pull you phone out of your pocket to use it to record a sighting, it reacquires fairly quickly. More testing may prove that this is error prone. But for now, it seems to be ok.

    I have tested more methodologies than I can remember with wildly differing results. This simple method works best, for WALKING, in my tests. I am sure there is more that I could do that might further refine the error by a few percentage points. If anyone has concrete examples for how to achieve tighter accuracy, please post comments.

    In my testing, I created an activity that allowed me to adjust locationRequest() settings in real time (stop and restart locationUpdates required on changes); as well as modifying min accuracy levels and accuracy multiplier (used above in code as 1.5). So I was able to test and play with different parameter settings to see what produced the best results.

    I hope this helps others get started down this path for the first time. Would love to hear comments, corrections, improvements if anyone has some.