i am creating an augmented reality app that simply visualices a textview when the phone is facing a Point of Interest (wich gps position is stored on the phone). The textview is painted on the point of interest location in the screen.
It works ok, the problem is that compass and accelerometer are very "variant", and the textview is constantly moving up and down left and right because the innacuracy of the sensors.
there is a way to solve it?
Our problem is same. I also had same problem when I create simple augmented reality project. The solution is to use exponential smoothing or moving average function. I recommend exponential smoothing because it only need to store one previous values. Sample implementation is available below :
private float[] exponentialSmoothing( float[] input, float[] output, float alpha ) {
if ( output == null )
return input;
for ( int i=0; i<input.length; i++ ) {
output[i] = output[i] + alpha * (input[i] - output[i]);
}
return output;
}
Alpha is smoothing factor (0<= alpha <=1). If you set alpha = 1, the output will be same as the input (no smoothing at all). If you set alpha = 0, the output will never change. To remove noise, you can simply smoothening accelerometer and magnetometer values.
In my case, I use accelerometer alpha value = 0.2 and magnetometer alpha value = 0.5. The object will be more stable and the movement is quite nice.
You should take a look at low-pass filters for you orientation data or sensor fusion if you want to a step further.
Good Luck with your app.
JQCorreia
I solved it with a simple trick. This will delay your results a bit but they surly avoid the inaccuracy of the compass and accelerometer.
Create a history of the last N values (so save the value to an array, increment index, when you reach N start with zero again). Then you simply use the arithmetic average of the stored values.
Integration of gyroscope sensor readings can give a huge improvement in the stability of the final estimation of the orientation.
Have a look at the steady compass application if your device has a gyroscope, or just have a look at the video if you do not have a gyroscope.
The integration of gyroscope can be done in a rather simple way using a complementary filter.
Related
We are trying to calculate the rise over the run of an object. We know the angle of a right triangle and we know the run. When we use the scientific Microsoft calculator we get the tangent of the angle and multiply by the run to get the rise.
angle = 7.5 tangent = 0.1316 in degrees multiply by run and ans 1.579 From this we now know how to set the X and Y coordinates of the Imageview object
We have seen all types of answers about how to do this with Java for Android none of which give the results based on the use of the MS calculator. We tried this
float T = (float) toRadians(tan(7.5));
Not even close we also tried toDegrees
So the we have two questions
How to calculate the rise knowing the run and the angle?
Is there a better way to set the X and Y value of the object so it will follow a path on a desired angle?
tan(angle) = rise/run. what you need is to rearrange this.
rise = tan(angle) * run
In PHP this is accomplished like this:
$rise = tan(deg2rad($degrees)) * $distance;
It took me a while to figure out that PHP's tan() function expects the angle to be in radians, so I had to convert to that first.
I know that's not an Android-specific response, but I'm leaving it here anyway in case it saves someone else some time.
As I know internally any GPS reciever solve equation and get X,Y,Z and VX,VY,VZ in orthogonal coordinates WGS-84 system.
But in Android API I get only VX,VY,VZ projection to north-east plane (Location::getSpeed, Location::getBearing), so
How can I get full 3d vector of speed?
Based on what getAccuracy calculated for GPS, GDOP, VDOP or what?
As I remeber, and as you can find in the android docu,
getAccuracy is related to horizontal accuracy.
From the DOP values its is more or less related to HDOP.
But the chip manufacturers, which calculate the horizontalAccuracy estimate,
do not specify how they internaly calculate that value. Nor they are willing to tell.
Your first sentence probably simplifies things. This might be valid for a
demonstration modell of a GPS receiver.
In practise speed (over ground) considers, the doppler effect, too, which then gives more precise speed than just evalution the location change per time.
"How can I get full 3d vector of speed?"
I doubt you can get it.
You can try to self caluclate by evalutaing altitude(m) change per time,
for higher vertical speeds, like airplane, that might work.
I have a very basic question.
What is Low Pass filter and High Pass filter in case of Android Accelerometer?
When I see the output from the Accelerometer Sensor, I see If I don't use any filter, (Case : I kept my cell phone idle on table) I get z Axis +ve value. Now If I think using basic physics, it gives me exact value(9.8approx) for small g i.e Acceleration due to gravity.
To get the linear acceleration, If I add any force to phone it will change the Accelerometer value, but it will be g + a that I applied. So to get a why can't I just subtract directly from the value I am getting from Accelerometer?
What is the use?
A basic definition I understand for low pass: To allow low value, High Pass : To allow high value.
If you look at the documentation you will see that SensorEvent returns an array which represents the vector of all the forces.
http://developer.android.com/reference/android/hardware/SensorEvent.html#values
This is how the components of the acceleration break down into each axis:
values[0] //acceleration on x axis
values[1] //acceleration on y axis
values[2] //acceleration on z axis
You need to find which direction gravity is operating in then decompose that into its component parts. The magnitude of the gravity force will always be 9.8 but the direction, and hence how it breaks down into the component parts, will change. Assuming that we could get the value of gravity and store that vector in an array like gravity[3]:
gravity[0] //gravity x axis
gravity[1] //gravity y axis
gravity[2] //gravity z axis
The total acceleration, T, on the phone is T = g + a. To get just a we would need a = T - g:
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
Notice how this calculates everything element by element because it's a vector operation.
The tricky part is finding gravity because there is only one accelerometer in the phone which measures both the gravity AND the other forces at the same time. We have 2 different forces that we want to find from the one sensor. If we could only look at the forces at an isolated point in time we wouldn't be able to extract the information. However we do get samples over a range of times and by looking at how the forces change over time we can extract the information.
This means we need to filter out the results from that one source based on how quickly those forces change. The magnitude of acceleration due to gravity does not change quickly because it doesn't change at all. Gravity is a constant force. However other forces will change over time. If we filter out the slow changing forces like gravity by using a high-pass filter then the remaining forces are the fast changing ones like the forces being applied to the phone. This is why the high-pass filter is used.
Low Pass Filter: passes low-frequency signals and reduces the amplitude of signals with frequencies higher than the threshold frequency
High Pass Filter: passes high-frequency signals and reduces the amplitude of signals with frequencies lower than the threshold frequency
If you look at the documentation, it says: "in order to measure the real acceleration of the device, the contribution of the force of gravity must be eliminated. This can be achieved by applying a high-pass filter. Conversely, a low-pass filter can be used to isolate the force of gravity."
You could check out this tutorial on low pass filtering:
Samir Bhide: Applying Low Pass Filter to Android Sensor's Readings
Reading the docs at http://developer.android.com/reference/android/hardware/SensorEvent.html#values, you can see that you can access the a values on all x,y,z axis by doing:
values[0] - a on x axis
values[1] - a on y axis
values[2] - a on z axis
Output of accelerometer includes noise if you subtract directly from these values that include noise. To eliminate noise it is required to implement highpass and lowpass filters.
I usually use this formula To filter the data from the accelometer sensor data coming out to linear sensor(like gyroscope) data.
Use it if you are not sure there is a built-in Gyroscopic sensor.
private float[] values;
private float[] valuesN;
private float[] prev;
private float[] prevHF;
private boolean doHPF = false;
// ind - index of three dimensions (x, y, z)
private void makeHPFf() {
for (int ind = 0; ind < 3; ind++) {
valuesN[ind] = values[ind] * 0.002f * 9.8f;
if (doHPF)
values[ind] = valuesN[ind] - prev[ind] + (prevHF[ind] * 0.8f);
prev[ind] = valuesN[ind];
prevHF[ind] = values[ind];
}
if (!doHPF)
doHPF = true;
}
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!
}
}
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!
}
}