Android - How to Calculate Distance Traveled - android

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?

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:
onPause() for activity stop locationUpdates.
onResume() restart locationUpdates.
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?
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.

Related

How to figure out when a gps coordinate is valid in Android

I'm developing an Android app and I need some help to save GPS coordinates for a route... coordinates are very important for my app, so: how can I do in order to get very good coordinates (especially at a first try)? How can I understand whether the position is correct or not? How can I use getAccuracy method or similar to figure out whether the position is wrong so that I have to reject it?
For example: I get a 1st LatLng coordinate but the 2nd LatLng is located 100 meters away from the 1st coordinate, so I guess that is very unlikely that a user can move 100 meters away in a few seconds... how can I create such an alghoritm?
Android Defining a Model for the Best Performance to get location
http://developer.android.com/guide/topics/location/strategies.html#BestPerformance

how to calculate distance while walking in android?

I am developing a demo for my app, in which there are two buttons named as "START" and "STOP". When user taps on "START" he will start walking. What I want to do is make it so that when users tap "STOP" then the demo will calculate his distance between "START" and "STOP". If the user pressed "START" and pressed "STOP" without taking a single step, then it must show 0km or 0m. I don't have any idea how I should start this; please make a suggestion.
There are different ways to do this:
GPS: Keep adding GPS distance between 2 points every X seconds (say 10 sec). Check Android Location.distanceTo or distanceBetween. Check My Tracks app, it is open source. GPS is not available indoors and would have error if user is changing direction very frequently (read every 1-2 second)
Accelerometer: Look for code/library for step detection using accelerometer. Distance comes from double integration of acceleration, errors can add up very quickly here.
Step detector: Built-in in Nexus 5. Google must have taken care of accelerometer errors to extent possible. This is hardware-based computation, consumes less battery but not available in most of handsets as of date.
You can also check Pedestrian dead reckoning
One way to go about it is using the accelerometer data. Your app should continuously record the accelerometer data after the user presses the Start button. You will observe a peak in your data whenever the user takes a step. Apply a filter on this data, and you shall be able to detect the number of steps taken with reasonable accuracy. Multiply it by the step length and you should get an approximation of the distance travelled. Take height of the user as an input argument. Step length is around 0.45*Height of a person. Since this approach is independent of GPS, It will also work indoors.
EDIT:
You'll need to use the accelerometer values for all three axes to make it fairly independent of the device orientation.You can go with x^2 + y^2 + z^2
Ask for GPS permissions in your app. When start is tapped, record the GPS coordinates. Do likewise for stop. You now have two coordinates. You can then apply the distance formula to get the total distance traveled.
Edit:
As for the case clarified in the comments, I think what you need to look into is Android's motion sensors. You may have to make a lot of assumptions or ask your users to calibrate your app before actual use.
Assume that you know your user's pace factor. Using the motion sensor, time how long is the user "walking" (obviously, there's no easy way to determine if your user is actually walking or just shaking the phone). Multiply this with your user's pace factor and you get a pretty rough idea of how much walking has your user done.
Comment to "There is one problem with this solution. If you are traveling at constant speed, the acceleration is 0, so accelerometer wont pick-up any readings. – jnovacho 16 secs ago"
(sorry, don't have enough reputation to comment directly)
when you accerlerate, save the accerleration and then calculate the speed you are walking. Stop calculation of speed whenever the acceleration changes and start over. If you stop, you should receive a negative accerleration, you'd then have to calculate if you just slowed down or stopped completely. But thats simply math :)
I had gone with the Gps method.
With the following steps:
On the click of start button, the latitude and longitude of the starting point were fetched and stored it in my dto with a proper TripId.
on the click of stop button :
TripDto dto = service.GetStartLatLong(TripIdA);
double lat = Double.valueOf(dto.getStartLati());
double lon = Double.valueOf(dto.getStartLongi());
Location locationa = new Location("point A");
locationa.setLatitude(lat);
locationa.setLongitude(lon);
double distance = location.distanceTo(locationa);
The distance returned by the location.distanceTo() method is in meters.
Try using sensors for this, I feel you should not use GPS as it may not be so accurate.
Refer to the following open source pedometer project for what you are talking about.
Pedometer library
Will update this answer with more specified code if you want to go with sensor.
public double getDistance(double lat1, double lon1, double lat2, double lon2)
{
double latA = Math.toRadians(lat1);
double lonA = Math.toRadians(lon1);
double latB = Math.toRadians(lat2);
double lonB = Math.toRadians(lon2);
double cosAng = (Math.cos(latA) * Math.cos(latB) * Math.cos(lonB-lonA)) +
(Math.sin(latA) * Math.sin(latB));
double ang = Math.acos(cosAng);
double dist = ang *6371;
return dist;
}
You can find the Latitude and Longitude of the current location using START button using location manager and store it in the variables. Then find the latitude and longitude of your end point using same method. Find the Distance between them by using this -
https://www.geeksforgeeks.org/program-distance-two-points-earth/#:~:text=For%20this%20divide%20the%20values,is%20the%20radius%20of%20Earth.
if your track is not a direct way (curve or zigzag) then you should use check location every 3-10 second
some one else say before me (x second).

Which is a higher accuracy criteria: ACCURACY_HIGH or ACCURACY_FINE?

In the Criteria class, there are two constants, ACCURACY_HIGH and ACCURACY_FINE, which are apparently used to require the LocationManager to return higher accuracy location updates. Here is what the documentation says about each of these constants:
public static final int ACCURACY_FINE (Added in API level 1)
A constant indicating a finer location accuracy requirement
Constant Value: 1 (0x00000001)
public static final int ACCURACY_HIGH (Added in API level 9)
a constant indicating a high accuracy requirement - may be used for horizontal, altitude, speed or bearing accuracy. For horizontal and vertical position this corresponds roughly to an accuracy of less than 100 meters.
Constant Value: 3 (0x00000003)
Does anyone know which of these two constants provides (i.e. requires) the highest level of accuracy?
From what I can see in the source code, ACCURACY_FINE is grouped with ACCURACY_COARSE with constant values of 1 & 2 respectively. ACCURACY_LOW, MEDIUM and HIGH are grouped together with constant values 1, 2 & 3.
It seems that setAccuracy expects and returns either COARSE or FINE, while setVerticalAccuracy, setHorizontalAccuracy, setSpeedAccuracy and setBearingAccuracy expect LOW, MEDIUM or HIGH. Furthermore, when you call setAccuracy, it sets horizontal accuracy like so:
public void setAccuracy(int accuracy) {
if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) {
throw new IllegalArgumentException("accuracy=" + accuracy);
}
if (accuracy == ACCURACY_FINE) {
mHorizontalAccuracy = ACCURACY_HIGH;
} else {
mHorizontalAccuracy = ACCURACY_LOW;
}
}
It's really confusing, but I hope I cleared it up for you a bit. Here's a link to the source in grepcode, you can download it and see for yourself if you don't have the source locally.
Both constants mean the highest accuracy, but are intended for different methods. From the documentation (which may have been updated since the question was asked):
ACCURACY_FINE is valid input for setAccuracy.
ACCURACY_HIGH is valid input for setBearingAccuracy, setHorizontalAccuracy, setSpeedAccuracy and setVerticalAccuracy.
I believe the idea of this modeling is that FINE and COARSE give you a choice of two opposites only, whereas LOW, MEDIUM and HIGH allow for a more subtle distinction. (The question remains, why setSpeedAccuracy ridicules that modeling by allowing only LOW and HIGH. But then, this might be a bug in either documentation or design. :) )
As mentioned by the OP, they introduced those values later, API level 9, in contrast to API level 1 for FINE-COARSE. The code found by npace simply reveals that internally the Android developers now map everything to the names and values of the LOW to HIGH value range, to get some consistency internally.
There are several criteia to determine the accuracy of the location
Once the device starts providing real-time locations, my suggestion is to check the accuracy of each result and consider rejecting those greater than a certain amount that are based on your requirements. The longer the location provider runs, and if the device has an unobstructed views of the sky and good cellular connection, then typically the accuracy will improve up to a certain point and then level off, and then it will fluctuate. Here’s a pseudo-code snippet showing how to check the accuracy of each GPS location result:
1
2
3
Here are some rough examples of accuracy thresholds . Your requirements may vary as to how these different thresholds will affect the behavior of your application; these were examples that required geocoding that converted the current location to an approximate address. Depending on the result the application gave different feedback to the user:
Rooftop <= 10 meters (desired result)
Street >10 meters and <= 100 meters (let user know it’s close but not perfect. Good enough?)
Neighborhood > 100 meters and <= 500 meters (give visual feedback that accuracy is low)
City > 500 meters and <= 2000 meters (ask user to verify city name from a list)
County > 2000 meters (prompt for manual location input)
Take into account your own unique use cases. You might completely reject any accuracy value greater than 100 meters (328 ft) if your app simply helps people find open parking lots at NFL games. You could have an app that returns a list of Dentist offices within a 5 mile (8000m) radius. Or a weather app could only need to know approximately what city you are in. These are just ideas to help get you thinking.
public void onLocationChanged(Location location) {
if(location.getAccuracy() < 100.0 && location.getSpeed() < 6.95){
//Do something
}
else{
//Continue listening for a more accurate location
}

Is it possible to get DEPTH using GPS in android [duplicate]

I need to get an accurate measurement of altitude using GPS only.
I tried Location.getAltitude(), but that is terribly inaccurate.
Any advice?
There are two issues with using altitude of a smartphone / tablet GPS:
The altitude is the altitude above the WGS84 reference ellipsoid. It is not the altitude above ground level or sea level. Here is more detail on that: http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=10915. This error can be corrected; here is a description how to do that by hand: http://www.unavco.org/edu_outreach/tutorial/geoidcorr.html. The web article links to a calculator to get the Geoid height for correction; I do not know if there is also a web service available for this computation.
The GPS altitude is terribly inaccurate for relatively cheap GPS receivers. Here is an article on that: http://gpsinformation.net/main/altitude.htm. One method to cope with this kind of inaccuracy is to filter the altitude data. I used a circular array data structure to remember the last few (I used 4) altitude readings and compute the average. This sufficed to get a relatively accurate reading of vertical speed for my application.
Another way would be parsing NMEA strings. The $GPGGA sentence already contains the corrected altitude data above sea level.
So, simply create a listener to NMEA-strings for your LocationManager and parse the messages:
private GpsStatus.NmeaListener mNmeaListener = new GpsStatus.NmeaListener() {
#Override
public void onNmeaReceived(long timestamp, String nmea) {
parseNmeaString(nmea);
}
};
public void registerLocationManager(Context context) {
mLocationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE);
mLocationManager.addNmeaListener(mNmeaListener);
}
private void parseNmeaString(String line) {
if (line.startsWith("$")) {
String[] tokens = line.split(",");
String type = tokens[0];
// Parse altitude above sea level, Detailed description of NMEA string here http://aprs.gids.nl/nmea/#gga
if (type.startsWith("$GPGGA")) {
if (!tokens[9].isEmpty()) {
mLastMslAltitude = Double.parseDouble(tokens[9]);
}
}
}
}
You can either replace the altitude of the last Location object received through a location listener, or parse the whole new location through NMEA.
Another approach is to measure the altitude from the barometer.
By using the pressure you can calculate the user's altitude. I'm uncertain of the precision level of this and whether it is more accurate than the other answer's approach.
By using the hypsometric formula you can calculate the altitude:
Variable definition:
P0: Sea-level pressure
P: Atmospheric pressure
T: Temperature
You can get the pressure in android from the environment sensors
SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE,pressure)
If the device has a barometer, then use can that to improve the relative accuracy. I don't mean to use the barometer to compute the height in relation to the sea level, as can be found in several formulas, as this is highly dependent of the weather conditions.
What you want to do is to get the GPS-altitude when you know that the device has a good fix, with high accuracy values. At that point you fetch the barometric pressure and set that pressure as a reference.
When the pressure increases around 12 hPa, you will know that your altitude decreased by around 100 m ( https://en.wikipedia.org/wiki/Atmospheric_pressure "At low altitudes above sea level, the pressure decreases by about 1.2 kPa (12 hPa) for every 100 metres." ).
Don't take that value as an exact, but variations in the altitude determined by GPS vary a lot due to trees covering the line of sight and other factors, while the barometer will remain very precise under those conditions.
The following graph is a bike ride of around one hour and 20 minutes in duration. Starting point and end point are the same at around 477 m above sea level, determined via GPS. The measured pressure is 1015.223 hPA.
The lowest point is 377 m, with a measured pressure of 1025.119 hPa. So in that case, 100 m make a difference of 10 hPa.
The highest point is 550 m, with a measured pressure of 1007.765 hPa.
Ending point is the same height, and the exact same pressure as the starting conditions (the pressure could have varied due to the weather conditions, but it didn't). The temperature dropped around 1°C, so it was all pretty constant.
The black line containing the many variations is the altitude measured via GPS, the mirrored, but clean line, is the barometric pressure. It has very little variation in it simply because the pressure doesn't vary as wild as the GPS-quality. It is a very smooth, very precise curve. This is not due to lag. This is measured with a Bosch BME280 sensor, which is capable of detecting the closing of a door, change of floor detection, elevator direction, drones, with a noise of 0.2 Pa which equals 1.7 cm and an error of 1m at 400m of height change. These kind of sensors are integrated in some Smartphones. For example, the Pixel 3 contains a Bosch BMP380.
If you mirror the pressure graph, as is shown with the dotted black line, you will see that it very closely matches the GPS altitude. It is much more precise than GPS, but the only problem is that you can't take it as an absolute value.
The samples of GPS and pressure were both taken in 1 second intervals, so there is no curve smoothing from the d3 library causing some false impressions.
So maybe readjusting the pressure around every 10-30 minutes while you have a GPS good fix will give you a good base to perform your altitude measurements by pressure in between.
There are other ways to get the altitude than by GPS. You can use the barometer but as there isn't that many devices with a barometric sensors yet (only the new ones). I will recommend to use a web service to acquire the desired data.
Here is a question which should help you through: Get altitude by longitude and latitude in Android
For newcomers I made a library that wrap LocationManager into rxjava observables and add some observable helpers to get sea level altitutde from Nmea/GPGGA mre info here
There are libraries, such as the open-source CS-Map which provide an API that do these lookups in large tables. You specify the coordinates, and it will tell you the height offset that needs to be applied to the ellipsoidal height at that location to get the "real-world" orthometric height.
Note: Having used CS-Map before, it isn't exactly straight-forward 5-minute job to plug it in. Warning you in advance that it is more complicated than calling a single API with a set of lat-long coordinates and getting back a height. I no longer work at the company where we were doing this kind of work, so unfortunately cannot look up the code to say exactly which API to call.
Doing a google search for it right now (2019), it seems CS-Map has been merged into MetaCRS in OSGeo, the "Open Source Geospatial Foundation" project. That same search led me to this old CS-Map wiki as well as the PROJ GitHub page, where PROJ seems to be similar project to CS-Map.
I would recommend using NmeaListener, as sladstaetter
suggested in his answer. However, according to NMEA documentation, "$GPGGA" is not the only sentence you can get. You should look for any "GGA" sentence ($--GGA).
Regular expressions are great for that, e.g.:
#Override
public void onNmeaReceived(final long timestamp, final String nmea) {
final Pattern pattern = Pattern.compile("\\$..GGA,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,([+-]?\\d+(.\\d+)?),[^,]*,[^,]*,[^,]*,[^,]*,[^,]*$");
final Matcher matcher = pattern.matcher(nmea);
if (matcher.find()) {
final float altitude = Float.parseFloat(matcher.group(1));
// ...enjoy the altitude!
}
}

Android: How to get accurate altitude?

I need to get an accurate measurement of altitude using GPS only.
I tried Location.getAltitude(), but that is terribly inaccurate.
Any advice?
There are two issues with using altitude of a smartphone / tablet GPS:
The altitude is the altitude above the WGS84 reference ellipsoid. It is not the altitude above ground level or sea level. Here is more detail on that: http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=10915. This error can be corrected; here is a description how to do that by hand: http://www.unavco.org/edu_outreach/tutorial/geoidcorr.html. The web article links to a calculator to get the Geoid height for correction; I do not know if there is also a web service available for this computation.
The GPS altitude is terribly inaccurate for relatively cheap GPS receivers. Here is an article on that: http://gpsinformation.net/main/altitude.htm. One method to cope with this kind of inaccuracy is to filter the altitude data. I used a circular array data structure to remember the last few (I used 4) altitude readings and compute the average. This sufficed to get a relatively accurate reading of vertical speed for my application.
Another way would be parsing NMEA strings. The $GPGGA sentence already contains the corrected altitude data above sea level.
So, simply create a listener to NMEA-strings for your LocationManager and parse the messages:
private GpsStatus.NmeaListener mNmeaListener = new GpsStatus.NmeaListener() {
#Override
public void onNmeaReceived(long timestamp, String nmea) {
parseNmeaString(nmea);
}
};
public void registerLocationManager(Context context) {
mLocationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE);
mLocationManager.addNmeaListener(mNmeaListener);
}
private void parseNmeaString(String line) {
if (line.startsWith("$")) {
String[] tokens = line.split(",");
String type = tokens[0];
// Parse altitude above sea level, Detailed description of NMEA string here http://aprs.gids.nl/nmea/#gga
if (type.startsWith("$GPGGA")) {
if (!tokens[9].isEmpty()) {
mLastMslAltitude = Double.parseDouble(tokens[9]);
}
}
}
}
You can either replace the altitude of the last Location object received through a location listener, or parse the whole new location through NMEA.
Another approach is to measure the altitude from the barometer.
By using the pressure you can calculate the user's altitude. I'm uncertain of the precision level of this and whether it is more accurate than the other answer's approach.
By using the hypsometric formula you can calculate the altitude:
Variable definition:
P0: Sea-level pressure
P: Atmospheric pressure
T: Temperature
You can get the pressure in android from the environment sensors
SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE,pressure)
If the device has a barometer, then use can that to improve the relative accuracy. I don't mean to use the barometer to compute the height in relation to the sea level, as can be found in several formulas, as this is highly dependent of the weather conditions.
What you want to do is to get the GPS-altitude when you know that the device has a good fix, with high accuracy values. At that point you fetch the barometric pressure and set that pressure as a reference.
When the pressure increases around 12 hPa, you will know that your altitude decreased by around 100 m ( https://en.wikipedia.org/wiki/Atmospheric_pressure "At low altitudes above sea level, the pressure decreases by about 1.2 kPa (12 hPa) for every 100 metres." ).
Don't take that value as an exact, but variations in the altitude determined by GPS vary a lot due to trees covering the line of sight and other factors, while the barometer will remain very precise under those conditions.
The following graph is a bike ride of around one hour and 20 minutes in duration. Starting point and end point are the same at around 477 m above sea level, determined via GPS. The measured pressure is 1015.223 hPA.
The lowest point is 377 m, with a measured pressure of 1025.119 hPa. So in that case, 100 m make a difference of 10 hPa.
The highest point is 550 m, with a measured pressure of 1007.765 hPa.
Ending point is the same height, and the exact same pressure as the starting conditions (the pressure could have varied due to the weather conditions, but it didn't). The temperature dropped around 1°C, so it was all pretty constant.
The black line containing the many variations is the altitude measured via GPS, the mirrored, but clean line, is the barometric pressure. It has very little variation in it simply because the pressure doesn't vary as wild as the GPS-quality. It is a very smooth, very precise curve. This is not due to lag. This is measured with a Bosch BME280 sensor, which is capable of detecting the closing of a door, change of floor detection, elevator direction, drones, with a noise of 0.2 Pa which equals 1.7 cm and an error of 1m at 400m of height change. These kind of sensors are integrated in some Smartphones. For example, the Pixel 3 contains a Bosch BMP380.
If you mirror the pressure graph, as is shown with the dotted black line, you will see that it very closely matches the GPS altitude. It is much more precise than GPS, but the only problem is that you can't take it as an absolute value.
The samples of GPS and pressure were both taken in 1 second intervals, so there is no curve smoothing from the d3 library causing some false impressions.
So maybe readjusting the pressure around every 10-30 minutes while you have a GPS good fix will give you a good base to perform your altitude measurements by pressure in between.
There are other ways to get the altitude than by GPS. You can use the barometer but as there isn't that many devices with a barometric sensors yet (only the new ones). I will recommend to use a web service to acquire the desired data.
Here is a question which should help you through: Get altitude by longitude and latitude in Android
For newcomers I made a library that wrap LocationManager into rxjava observables and add some observable helpers to get sea level altitutde from Nmea/GPGGA mre info here
There are libraries, such as the open-source CS-Map which provide an API that do these lookups in large tables. You specify the coordinates, and it will tell you the height offset that needs to be applied to the ellipsoidal height at that location to get the "real-world" orthometric height.
Note: Having used CS-Map before, it isn't exactly straight-forward 5-minute job to plug it in. Warning you in advance that it is more complicated than calling a single API with a set of lat-long coordinates and getting back a height. I no longer work at the company where we were doing this kind of work, so unfortunately cannot look up the code to say exactly which API to call.
Doing a google search for it right now (2019), it seems CS-Map has been merged into MetaCRS in OSGeo, the "Open Source Geospatial Foundation" project. That same search led me to this old CS-Map wiki as well as the PROJ GitHub page, where PROJ seems to be similar project to CS-Map.
I would recommend using NmeaListener, as sladstaetter
suggested in his answer. However, according to NMEA documentation, "$GPGGA" is not the only sentence you can get. You should look for any "GGA" sentence ($--GGA).
Regular expressions are great for that, e.g.:
#Override
public void onNmeaReceived(final long timestamp, final String nmea) {
final Pattern pattern = Pattern.compile("\\$..GGA,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,([+-]?\\d+(.\\d+)?),[^,]*,[^,]*,[^,]*,[^,]*,[^,]*$");
final Matcher matcher = pattern.matcher(nmea);
if (matcher.find()) {
final float altitude = Float.parseFloat(matcher.group(1));
// ...enjoy the altitude!
}
}

Categories

Resources