As we all know GPS system is far from perfect and when you go around with your app using the gps when the GPS loses the signal and it starts calculating again. And if you try to calculate the distance correctly it will start calculating and for example if you were 30 meters away from your destination it will calculate for example 130 meters. Which messes my how calculation of the distance up. So I found some suggestion that I should filter the GPS coordination using the speed.
I want to calculate the speed without the location.getSpeed() I want to calculate the speed by comparing the distance from the last known coordinates to the coordinates at some given point and I get the speed of the device moving. And if the speed is for example greater than 15 m/h coordinates are invalid and don't re-calculate the distance.
It is as simple as V = (Distance) / (Elapsed time).
So lets say you read your location1 at time X.
Than at some point after X lets say at time Y you read your location again.
Than you'll have
float distance = location1.distanceTo(location2);
which is float in meters (see this http://developer.android.com/reference/android/location/Location.html#distanceTo(android.location.Location)
To have your velocity as in meters per seconds if you calculate X - Y as
float X = System.currentTimeMillis(); //When you get the first location
float Y = System.currentTimeMillis(); //When you get the second location
//...
//Than when calculating
float timeElapsed= (Y - X)/1000; //In seconds
Than the velocity in meters per second will be
float V = distance/timeElapsed;
If you want to calculate average velocity, you need to store the velocities in a list lets say, and than calculate average from all those velocities calculated between each two points. So if you have location l1, l2, l3, l4 ..... ln, V1 will be velocity between l1 and l2, V2 will be between l2 and l3, and Vn-1 will be between ln-1 and ln. You will store all Vn in a list (for example) than you caluclate the average as
Vavg = (V1 + V2 + V3 ... + Vn)/n
UPDATE:
In your activity
Location previousLocation = null;
float previousTime = 0;
float velocity = 0;
Than:
public void onLocationChanged(Location loc) {
boolean hasPrevious = true;
if (previousLocation == null || previousTime == 0) {
hasPrevious = false;
}
float currentTime = System.currentTimeMillis();
if (hasPrevious) {
float timeElapsed = (currentTime - previousTime)/1000;
velocity = loc.distanceTo(previousLocation)/timeElapsed;
}
storeToPrevious(loc, currentTime);
}
In a different function
private void storeToPrevious(Location l, float time) {
previousLocation = new Location(l);
previousTime = time;
}
This may do what you want. It is written in Kotlin. It applies a weighted moving average. Most recent locations have a heavier weight. It can "smooth" out the speed, at the cost of adding more lag. This was to get around a bug in certain situations that I could not use getSpeed(). But normally if you use "Fused" location on Android, the speed is quite stable and accurate on a typical modern, ACTIVE phone.
var recentGPSLocationSegments = listOf<Pair<android.location.Location, android.location.Location>>()
fun applyWeightedMovingAverageSpeed(location: android.location.Location, previous: android.location.Location): Double
{
recentGPSLocationSegments += Pair(location, previous)
val cachedLocationsNs = location.elapsedRealtimeNanos - 4500000000 // 4.5 seconds, This will typically get 4 entries (1 second apart)
val targetZeroWeightNs = location.elapsedRealtimeNanos - 5000000000 // 5.0 seconds, Weights will be approx 5000000000, 4000000000, 3000000000, 1000000000
// Toss old locations
recentGPSLocationSegments = recentGPSLocationSegments.filter { it -> it.first.elapsedRealtimeNanos > cachedLocationsNs }
// Total up all the weights. Weight is based on age, younger has higher weight
val weights = recentGPSLocationSegments.map { it.first.elapsedRealtimeNanos - targetZeroWeightNs }.sum()
// Apply the weights and get average speed in meters/second
return recentGPSLocationSegments.map { speedFromGPS(it.first, it.second) * (it.first.elapsedRealtimeNanos - targetZeroWeightNs) }.sum() / weights
}
fun speedFromGPS(location: android.location.Location, previous: android.location.Location): Double
{
val dist = location.distanceTo(previous)
val time = (location.elapsedRealtimeNanos - previous.elapsedRealtimeNanos) / 1000000000.0
return dist / time
}
val locationManagerExample: LocationListener = object : LocationListener
{
var lastLocation: android.location.Location? = null
var lastPreviousLocation: android.location.Location? = null
override fun onLocationChanged(location: android.location.Location?)
{
if (location != null)
{
if (lastPreviousLocation != null)
{
currentSpeed = applyWeightedMovingAverageSpeed(location, lastPreviousLocation!!)
lastPreviousLocation = lastLocation
}
lastLocation = location
if (currentSpeed < 0.0)
{
currentSpeed = 0.0
}
}
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle)
{
}
override fun onProviderEnabled(provider: String)
{
}
override fun onProviderDisabled(provider: String)
{
}
}
Related
I am creating a trip distance calculation app for our drivers. Above way is working. but always calculate distance.
Why always return different LatLng(s) without moving mobile? (mobile on my desk)
Output
I am getting more than 10km result within 10 minutes without moving the device. Why is that?
double calculate(){
double cal = 0;
list.removeWhere((element) => element?.latitude == userLocation?.latitude || element?.longitude == userLocation?.longitude); //remove duplicate values
list.add(userLocation);
if (list.length > 1) {
LocationModel firstLoc = list.elementAt(list.length - 2);
LocationModel lastLoc = list.last;
cal = _cp.calculateDistance(firstLoc.latitude, firstLoc.longitude, lastLoc.latitude, lastLoc.longitude);
}
totalKm += cal;
retun totalKm;
}
double calculateDistance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 - c((lat2 - lat1) * p) / 2 + c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
GpsService Method
I am using geolocator plugin for that. (FusedLocationProviderClient or if not available the LocationManager on Android and CLLocationManager on iOS).
Future<LocationModel> getLocation() async {
try {
Position userLocation = await Geolocator.getCurrentPosition(forceAndroidLocationManager: false, desiredAccuracy: LocationAccuracy.high);
_currentLocation = LocationModel.fromGeoLocatorData(userLocation);
} on Exception catch (e) {
print('Could not get location: $e');
}
return _currentLocation;
}
Timer.periodic(Duration(seconds: 1), (Timer t) async {
userLocation = await locationService.getLocation().then(getLocation);
});
Location tracking is not very deterministic with mobile devices. Even if you stand still, as you poll position, it will change a little with every measurement. Also you should take accuracy of locations into account. Basically, a location you get, does not mean that the position is at exactly lat/long. It actually means, that with a confidence of 68%, the position is within a radius of accuracy meters around lat/long. [1].
Thus, you only should assume, that two locations are different, if the distance between them is bigger than the sum of both of its accuracies. And only if you've decided that a new location is different from the previous one, should you add the distance between them to the total distance.
Also, as one cannot move really far within a second (except perhaps in a race car or rocket) yoi could increase the time between measurements. This would also help in saving battery.
1: https://developer.android.com/reference/android/location/Location
I've been currently trying to get the speed of the user.
I've got the location and time of the two points, but it seems that I just kind find the correct way to calculate the speed, despite many attempts.
This is what I am trying right now:
public void onLocationChanged(Location location) {
Toast.makeText(this, "Location Changed", Toast.LENGTH_SHORT).show();
if (settings == null) {
settings = getSharedPreferences(PREFS_NAME, 0);
}
int ExceedingLimit = settings.getInt("ExceedLimit", 10);
int LowerLimit = settings.getInt("LowerLimit", 2);
int UpperLimit = settings.getInt("UpperLimit", 12);
double speed = 0;
if (location != null) {
double calc_long = Math.pow(location.getLongitude() - OldLocation.getLongitude(), 2);
double calc_lat = Math.pow(location.getLatitude() - OldLocation.getLatitude(), 2);
double time = (double) location.getTime() - (double) OldLocation.getTime();
speed = Math.sqrt(calc_long + calc_lat) / time;
}
if (location != null && location.hasSpeed()) {
speed = location.getSpeed();
}
if (LowerLimit < speed && speed < UpperLimit) {
ExceedInstance += 1;
}
OldLocation = location;
It doesn't seem to work, as it stays on zero even when moving.
I initialise Old Location here:
public void onConnected(Bundle connectionHint) {
Toast.makeText(this, "You have connected", Toast.LENGTH_LONG).show();
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(UPDATE_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
PendingResult<LocationSettingsResult> result =
LocationServices.SettingsApi.checkLocationSettings(mLocationClient, mLocationSettingsRequest
);
result.setResultCallback(ViewingWindow.this);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
LocationServices.FusedLocationApi.requestLocationUpdates(mLocationClient, mLocationRequest, this);
OldLocation = LocationServices.FusedLocationApi.getLastLocation(mLocationClient);
}
}
I've also attempted calculate the two separately and dividing the two, but it ended up as a casting error and it still stayed on zero.
Casting doesn't work either. When I cast it to double, it gives me E-10 to E-11 numbers, absurdly low but still changing every time the location updates.
The problem is, even when I move the speed doesn't go but.
I actually have no idea why any of this is wrong. Can someone help me?
Did you try to cast the needed time to double ?
speed = (double)Math.sqrt(
Math.pow(location.getLongitude() - OldLocation.getLongitude(), 2)
+ Math.pow(location.getLatitude() - OldLocation.getLatitude(), 2)
) / (double)(location.getTime() - OldLocation.getTime());
Because i dont know what the Math.sqrt() returns. It may be returnes a float because it is much faster in caculation.
If your speed isnt to high you can also change the speed variable to float.
Try this
double calc_long = Math.pow(location.getLongitude() - OldLocation.getLongitude(), 2);
double calc_lat = Math.pow(location.getLatitude() - OldLocation.getLatitude(), 2);
double time = location.getTime() - OldLocation.getTime();
speed = (double) Math.sqrt(calc_long + calc_lat) / time;
Also, how do you test the speed? From my experience longitude and latitude need a few seconds to update and register any move, so if you try to keep a constant speed for around a minute (doesn't matter how) it should trigger the speed meter.
I am calculating distance between two location using latitude and longitude... i am getting the distance easily and converting it into miles... but first difference value come in wrong format and other value coming right.
First distance is coming like this = 2.0E-4
While other like this = 48.8881
i have used the following code
Location l1 = new Location("One");
l1.setLatitude(cur_latitude);
l1.setLongitude(cur_longitude);
for (int i = 0; i < lctn.size(); i++) {
Location l2 = new Location("LocationB");
l2.setLatitude(lctn.get(i).getLocation_lat());
l2.setLongitude(lctn.get(i).getLocation_lng());
float distance = l1.distanceTo(l2);
distance =Float.parseFloat(new DecimalFormat("##.####").format( distance / 1000.0f));
Double mile = Double.parseDouble(new DecimalFormat("##.####").format(distance * 0.6214));
Log.e("km_", "" + distance);
Log.e("miles_", "" + mile);
Also please check whether i am using right formula for calculating miles....Thanks in Advance
Distance is in points that why it happening, You need to use accuracy variable and assign some value to this variable,
in my case i use
int accuracy = 20;
Then compare this accuracy with Location accuracy
if(l2.getAccuracy() <= accuracy ) {
Log.e("km_", "" + distance);
Log.e("miles_", "" + mile);
}
I suggest you to use fused location api for accurate distance.
Anyone knows how to get smooth vertical orientation degree in Android?
I already tried OrientationEventListener as shown below but it's very noisy. already tried all rates, Normal, Delay, Game and Fastest, all shown the same result.
myOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
#Override
public void onOrientationChanged(int arg0) {
orientaion = arg0;
Log.i("orientaion", "orientaion:" + orientaion);
}
};
So there are two things going on that can affect what you need.
Sensor delay. Android provides four different sensor delay modes: SENSOR_DELAY_UI, SENSOR_DELAY_NORMAL, SENSOR_DELAY_GAME, and SENSOR_DELAY_FASTEST, where SENSOR_DELAY_UI has the longest interval between two data points and SENSOR_DELAY_FASTEST has the shortest. The shorter the interval the higher data sampling rate (number of samples per second). Higher sampling rate gives you more "responsive" data, but comes with greater noise, while lower sampling rate gives you more "laggy" data, but more smooth.
Noise filtering. With the above in mind, you need to decide which route you want to take. Does your application need fast response? If it does, you probably want to choose a higher sampling rate. Does your application need smooth data? I guess this is obviously YES given the context of the question, which means you need noise filtering. For sensor data, noise is mostly high frequency in nature (noise value oscillates very fast with time). So a low pass filter (LPF) is generally adequate.
A simple way to implement LPF is exponential smoothing. To integrate with your code:
int orientation = <init value>;
float update_rate = <value between 0 to 1>;
myOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
#Override
public void onOrientationChanged(int arg0) {
orientation = (int)(orientation * (1f - update_rate) + arg0 * update_rate);
Log.i("orientation", "orientation:" + orientation);
}
};
Larger update_value means the resulting data is less smooth, which should be intuitive: if update_value == 1f, it falls back to your original code. Another note about update_value is it depends on the time interval between updates (related to sensor delay modes). You probably can tune this value to find one works for you, but if you want to know exactly how it works, check the alpha value definition under Electronic low-pass filters -> Discrete-time realization.
I had a similar problem showing an artificial horizon on my device. The low pass filter (LPF) solved this issue.
However you need to consider when you use the orientation angle in degrees and apply the LPF on it blindly, the result is faulty when the device is in portrait mode and turned from left to ride or opposite. The reason for this is the shift between 359 and 0 degree. Therefore I recommend to convert the degree into radians and apply the LPF on the sin and cos values of the orientation angle.
Further I recommend to use a dynamic alpha or update rate for the LPF. A static value for the alpha might be perfect on your device but not on any other.
The following class filters based on radians and uses a dynamic alpha as described above:
import static java.lang.Math.*;
Filter {
private static final float TIME_CONSTANT = .297f;
private static final float NANOS = 1000000000.0f;
private static final int MAX = 360;
private double alpha;
private float timestamp;
private float timestampOld;
private int count;
private int values[];
Filter() {
timestamp = System.nanoTime();
timestampOld = System.nanoTime();
values = new int[0];
}
int filter(int input) {
//there is no need to filter if we have only one
if(values.length == 0) {
values = new int[] {0, input};
return input;
}
//filter based on last element from array and input
int filtered = filter(values[1], input);
//new array based on previous result and filter
values = new int[] {values[1], filtered};
return filtered;
}
private int filter(int previous, int current) {
calculateAlpha();
//convert to radians
double radPrev = toRadians(previous);
double radCurrent = toRadians(current);
//filter based on sin & cos
double sumSin = filter(sin(radPrev), sin(radCurrent));
double sumCos = filter(cos(radPrev), cos(radCurrent));
//calculate result angle
double radRes = atan2(sumSin, sumCos);
//convert radians to degree, round it and normalize (modulo of 360)
long round = round(toDegrees(radRes));
return (int) ((MAX + round) % MAX);
}
//dynamic alpha
private void calculateAlpha() {
timestamp = System.nanoTime();
float diff = timestamp - timestampOld;
double dt = 1 / (count / (diff / NANOS));
count++;
alpha = dt/(TIME_CONSTANT + dt);
}
private double filter(double previous, double current) {
return (previous + alpha * (current - previous));
}
}
For further readings see this discussion.
This question already has answers here:
How to use Accelerometer to measure distance for Android Application Development
(2 answers)
Closed 9 years ago.
I know i am opening up a can of worms with trying to get the linear motion of a device using the accelerometer, but please just humor me.
I am trying to figure out the right formula to take the Sensor.TYPE_LINEAR_ACCELEROMETER (which i believe is normal accelerometer data minus gravity) and essentially say "this much time has passed and the i have accelerated x amount since last time, so i have traveled d amount.
should be something like distanceTraveledOnX = linearAccerationOfX * TimePassed;
easy enough in the real world right? if i have been going 1 mile a minute for 10minutes then i have traveled 10 miles.. speed * time = distance
problem is im not sure what the linearAcceleration is using for unit of measure. I know my timePassed is in NanoSeconds as i am saying (in my onSensorChanged)
currentTime = System.nanoTime();//var of type (double)
timePassed = currentTime - lastTime;
lastTime = currentTime;
can someone please help me figure out the formula for translating the linearAcceleration value to a nanoSecond measurement..
thanks
EDIT
here is the code im currently using but im always getting 0 :
public void onSensorChanged(SensorEvent evt) {
if (type == Sensor.TYPE_LINEAR_ACCELERATION) {
newTime = System.currentTimeMillis()/1000;
float oldVelocity = lastTime1-lastTime0;
float newVelocity = newTime- lastTime1;
if(oldVelocity<1)oldVelocity =1;
newX = lastX1 + ((lastX1 - lastX0)/oldVelocity)*newVelocity +(evt.values[0]/2)*(newVelocity*newVelocity);
lastX0 = lastX1;
lastX1 = newX;
lastTime0 = lastTime1;
lastTime1 = newTime;
Log.v("SENSOR MAN LINEAR", "new X:"+newX);
}
}
This stuff is high school physics, and if you don't know the difference between acceleration and velocity, you'll need to review it before you have any hope here.
I can tell you this much: the linear acceleration readings from a cell phone or tablet aren't remotely precise or accurate enough to do what you want without constant correction (via gps or other methods). There is an entire field of study trying to solve this problem. I've attended conferences on it.
That said, you also need to take into account that the orientation of your device will also change, unless this is some sort of special application, e.g. the device is trapped onto a sled which can only move in one direction.
Let's assume that case, and assume that the device is strapped to your sled with the right side of the device (+X axis) aligned in the direction of travel. Let's also assume that the initial position of the sled is known (call it X0) when the program starts, and that the initial velocity is zero.
Your code looks approximately like this:
double x0; // previous position, meters
double x; // current position
double v0; // previous velocity, meters/second
double v; // current velocity
long t0; // previous time, nanoseconds
long t; // current time
public void onStart() {
x0 = getInitialPosition();
x = x0;
v0 = 0;
v = v;
t0 = System.getCurrentTime() * 1000000;
// Enable sensors; left as an exercise for the reader
}
public void onSensorChanged(SensorEvent event) {
// Assume linear acceleration is the only active sensor
double accel = event.values[0]; // X axis is our axis of acceleration
t = event.timestamp;
double dt = (t - t0) * .000001;
v = v0 + accel * dt;
x = x0 + v * dt;
t0 = t;
v0 = v;
x0 = x;
}
This is by no means a complete solution. Doing this right involves differential equations which I'm not equipped to explain here (translation: I've forgotten everything I learned in college). However, if your acceleration value is accurate enough, and your time slice is short enough, this is viable.
If you need to solve this in more than one direction, it's only slightly more complicated provided that the device never changes orientation. If it does, then you also need to capture the rotation sensor and learn about quaternions and rotation matrices.
And even if you do everything right, errors will still accumulate, so now you want some sort of correction factor based on GPS, known geometry of the environment (e.g. if you're indoors and the software has a map of the building, it can make corrections when you turn a corner), and other environmental clues such as WiFi hotspots in known locations.
You might want to read up on Kalman filters at this point.
Executive summary: this is a HARD problem in the general case, and if you solve it, there's probably fame and fortune waiting for you.
Well, the correct form, known from school, is
finalXPosition = (linearAcceleration*timePassed^2)/2+ initialVelocity*timePassed+initialXPosition
finalVelocity = initialVelocity*timePassed
chaining these chunks you'll get your theoretical values.
In practice, best results are achieved by regular calibration of initialXPosition and initialVelocity through GPS.
simple example to receive calibrated horizontal acceleration in onSensorChanged:
class Integrator {
private float position = 0f;
private float velocity = 0f;
public void setGpsPosition (float gpsPosition) {
position = gpsPosition;
}
public void setGpsVelocity (float gpsVelocity) {
velocity = gpsVelocity;
}
public void onAccelerationChangeHandler(float acceleration, float timePassed) {
position += acceleration*timePassed*timePassed/2f + velocity*timePassed;
velocity += acceleration*timePassed;
}
public float getCurrentPosition() {
return position;
}
}
usage for x-acceleration:
long lastTime = 0;
public void onSensorChanged(SensorEvent evt) {
if (evt.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
long newTime = System.currentTimeMillis();
OnAccelerationChangeHandler(evt.values[0], (newTime-lastTime)/1000);
lastTime = newTime;
}
Please, note that outside a minute scale the error makes this all meaningless w/o gps correction. Understand, that if you are walking at constant speed the sensor won't give you anything at all.