I need to get the speed and heading from the gps. However the only number i have from location.getSpeed()
is 0 or sometimes not available. my code:
On a spherical planet distance should be calculated with these formulas :
private static Double distance(Location one, Location two) {
int R = 6371000;
Double dLat = toRad(two.getLatitude() - one.getLatitude());
Double dLon = toRad(two.getLongitude() - one.getLongitude());
Double lat1 = toRad(one.getLatitude());
Double lat2 = toRad(two.getLatitude());
Double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
Double d = R * c;
return d;
}
private static double toRad(Double d) {
return d * Math.PI / 180;
}
There is my custom LocationListener used to get speed manually and by location object if has speed.
new LocationListener() {
private Location mLastLocation;
@Override
public void onLocationChanged(Location pCurrentLocation) {
//calcul manually speed
double speed = 0;
if (this.mLastLocation != null)
speed = Math.sqrt(
Math.pow(pCurrentLocation.getLongitude() - mLastLocation.getLongitude(), 2)
+ Math.pow(pCurrentLocation.getLatitude() - mLastLocation.getLatitude(), 2)
) / (pCurrentLocation.getTime() - this.mLastLocation.getTime());
//if there is speed from location
if (pCurrentLocation.hasSpeed())
//get location speed
speed = pCurrentLocation.getSpeed();
this.mLastLocation = pCurrentLocation;
////////////
//DO WHAT YOU WANT WITH speed VARIABLE
////////////
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
};
getspeed() works fine. You don't have to do the math using distance formula. It is already there in getspeed, As long as there are latitude and longitude, there will be a speed in getspeed.
getSpeed() method actually works fine but you have to use a high request interval like 1 second and you need high accuracy, first i was doing 3 second interval and PRIORITY_BALANCED_POWER_ACCURACY and i kept getting 0 values until i change it like i say. I am using fused location provider api.
public class Main3Activity extends AppCompatActivity {
private FusedLocationProviderClient mFusedLocationClient;
private int speed;
private double lat;
private double lng;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
}
@Override
protected void onResume() {
super.onResume();
if(!runtime_permissions()) {
requestLocations();
}
}
@Override
protected void onPause() {
super.onPause();
//stop location updates when Activity is no longer active
if (mFusedLocationClient != null) {
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
}
}
@SuppressLint("MissingPermission")
private void requestLocations(){
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(1000);;
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
}
LocationCallback mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
List<Location> locationList = locationResult.getLocations();
if (locationList.size() > 0) {
//The last location in the list is the newest
Location location = locationList.get(locationList.size() - 1);
lat = location.getLatitude();
lng = location.getLongitude();
//speed in km/h
speed = (int) ((location.getSpeed() * 3600) / 1000);
}
}
};
private boolean runtime_permissions() {
if(Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},100);
return true;
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 100){
if( grantResults[0] == PackageManager.PERMISSION_GRANTED){
onResume();
}else{
runtime_permissions();
}
}
}
}
Based on other suggestions here I put together this code with comments to why:
public static float getSpeedKmh(Location newLocation, Location previousLocation)
{
if(newLocation == null) throw new IllegalArgumentException("newLocation must not be null");
float speed = -1.0f;
if(newLocation.hasSpeed()) /* gps doppler speed at the current moment preferred */
{
speed = newLocation.getSpeed();
}else /* may be wifi or celltower based location: compute AVERAGE speed since last fix */
{
if(previousLocation == null) throw new IllegalArgumentException("Cannot compute speed without previousLocation (was null)");
if(previousLocation.getTime()==newLocation.getTime())
throw new IllegalArgumentException("Cannot compute speed from same time locations!"); /* diff by zero protection */
speed = newLocation.distanceTo(previousLocation) * 1000.0f /* time diff in millis so multiply by 1000 */
/ Math.abs(newLocation.getTime() - previousLocation.getTime()); /* Math abs: so you can even swap new and older location without effect */
}
return speed * 3.6f; /* m/s -> km/h */
}
location.getSpeed() only returns what was set with location.setSpeed(). This is a value that you can set for a location object.
To calculate the speed using GPS, you'll have to do a little math:
Speed = distance / time
So you would need to do:
(currentGPSPoint - lastGPSPoint) / (time between GPS points)
All converted to ft/sec, or however you want to show the speed. This is how I did it when I made a runner app.
More specifically, you'll need to calculate for absolute distances:
(sqrt((currentGPSPointX - lastGPSPointX)^2) + (currentGPSPointY - lastGPSPointY)^2)) / (time between GPS points)
It might help to make a new TrackPoint class or something, which keeps the GPS location and time it was taken inside.