Compass - Track number of full 360 degree rotations - android

Suppose a person is using this compass, and beginning from 90 degrees they start rotating either clockwise or counterclockwise. What's the best way to keep count of how many full 360 degree rotations they complete? Assuming they'll be rotating either only clockwise or only counterclockwise from beginning to end.
I kept coming up with solutions where if the beginning bearing is, for example, 90 degrees I keep checking the next bearing when the sensor data changes, and if it's consistently moving in one direction I know they're rotating. And if they keep rotating in that direction and make it back to 90 degrees, that counts as one rotation. My way seems very convoluted and inefficient and I'm having a hard time coming up with a better way.
In this scenario, I'd be expecting multiple full rotations.
I'd appreciate any help. Thank you!
I found this related answer and am trying to put together a code sample for that. If someone has already done something similar, please post it!
#Override
public void onSensorChanged(SensorEvent event)
{
switch(event.sensor.getType())
{
case Sensor.TYPE_GRAVITY:
{
mValuesAccelerometer = lowPass(event.values.clone(), mValuesAccelerometer);
break;
}
case Sensor.TYPE_MAGNETIC_FIELD:
{
mValuesMagneticField = lowPass(event.values.clone(), mValuesMagneticField);
break;
}
}
boolean success = SensorManager.getRotationMatrix(
mMatrixR,
mMatrixI,
mValuesAccelerometer,
mValuesMagneticField);
if (success)
{
SensorManager.getOrientation(mMatrixR, mMatrixValues);
float azimuth = toDegrees(mMatrixValues[0]);
float pitch = toDegrees(mMatrixValues[1]);
float roll = toDegrees(mMatrixValues[2]);
if (azimuth < 0.0d)
{
//The bearing in degrees
azimuth += 360.0d;
}
}
}

If you're sure that they'll be moving in only 1 direction, to optimize your code you can have checkpoints for degrees instead of continuously monitoring if they're still moving in the right direction.
Here's a rough algo to do that
//You noted 90 degree as starting point
// checkpoint 1 will be 180 keep it as a boolean
// now you've reached 180 if the meter gets to 180 before going to next checkpoint
// which is 270 then make 180 false. it means they turned back.
// if they make it to 270 then wait for 0 degrees and do the same.
// if they make it back to 90 like that. You got a rotation and hopefully
// a bit of complexity is reduced as you're just checking for 4 checkpoints
I don't have any code handy at the moment.

This is a tracking problem with a reading that overflows. You need to keep track of the last reading and hope the user doesn't do more than a half turn between each reading.... (because of the Nyquist theorem)
Here is the basic pseudo code.
var totalChange = 0;
var lastAzimuth = -1000;
function CountTurns(az)
{
if (az > 180) az -= 360; // do this if your azimuth is always positive i.e. 0-360.
if (lastAzimuth == -1000)
{
lastAzimuth = az;
}
diff = az - lastAzimuth;
if (diff > 180)
diff -= 360;
if (diff < -180)
diff += 360;
lastAzimuth = az;
totalChange += diff;
return totalChange / 360;
}

Create 3 integers
int rotationCount=0
int currentDegrees=0
int previousDegrees=89
not a java programmer so i dont know how you handle the onSensorChanged event but basically perform a check within a while loop
while (currentDegrees + 90 < 360)
{
if (currentDegrees + 90 == 0)
{
if (previousDegrees == 359)
{
rotationCount = rotationCount + 1
}
}
else if (currentDegrees + 90 == 359)
{
if (previousDegrees == 0)
{
rotationCount = rotationCount - 1
}
}
previousDegrees = currentDegrees + 90
}
sorry about the syntax, this is just an example of how to do so..

Visualize what I will say and you'll definitely hit your goal in no time.
As you don't need to think of the full 360 degree, but you can take half of that and use the signs differences to your advantage.
Take a look at this figure :
We have a circle that is divided to two sides (left and right).
The left side will take negative 180 degree. (West Side).
The right side will take positive 180 degree. (East Side).
Current positing will be always 0 as (North) and positive 180 as (South).
IF the compass goes positive (meaning goes to the right direction)
Then add +1 on each turn.
IF the compass goes negative (meaning goes to the left direction).
Then subtract -1 on each turn
IF the compass hit OR is 0, then it's current position (NORTH).
IF the compass hit OR is 90, then it's (East).
IF the compass hit OR is 180, then it's (South)
IF the compass hit OR is -90, then it's (West).
This will turn out that whenever the person goes East, the counter will add +1 until it reaches 180, Then it'll change from positive to negative, which will subtract -1 on each turn until it reaches 0. That would be a full 360 rotation.

Related

How to detect left and right tilt of an android device mounted with an accelerometer?

Lets say you have the acceleration readings in all the 3 dimensions i.e X, Y and Z. How do you infer using the readings the phone was tilted left or right? The readings get generated every 20ms.
I actually want the logic of inferring the tilt from the readings. The tilt needs to be smooth.
A tilt can be detected in a sort of diferent ways. You can take into account 1 axis, 2 axis, or the 3 axis. Depending on how accurate you want it, and how much you feel like fighting with maths.
If you use only one axis, it is quite simple. Think the mobile is completely horizontal, and you move it like this:
using just one axis, lets say, axis x, will be enough, since you can detect accurately a change in that axis position, since even any small movement will do a change in the axis.
But, if your application is only reading that axis, and the user has the phone almost vertical, the difference in x axis will be really small even rotating the phone a big angle.
Anyways,for applications that only need coarse resolution, a single-axis can be used.
Referring to basic trigonometry, the projection of the gravity vector on the x-axis produces an output acceleration equal to the sine of the angle between the accelerometer x-axis and the horizon.
This means that having the values of an axis (those are acceleration values) you can calculate the angle in which the device is.
this means that the value given to you by the sensor, is = to 9,8 * sine of the angle, so doing the maths you can get the actual angle.
But don't worry, you don't even have to do this. Since the values are more or less proportional, as you can see in the table below, you can work directly with the value of the sensor, without taking much care of what angle represents, if you don't need it to be much accurate, since a change in that value means a proportional change in the angle, so with a few test, you will find out how big should be the change in order to be relevant to you.
So, if you take the value over the time, and compare to each other, you can figure out how big the rotation was. For this,
you consider just one axis. this will be axis X.
write a function to get the difference in the sensor value for that axis between one function call, and the next
Decide a maximum time and a minimum sensor difference, that you will consider a valid movement (e.g. a big rotation is good but only if it is fast enough, and a fast movement is good only if the difference in the angle is big enough)
if you detect two measurements that accomplish those conditions, you take note of half tilt done (in a boolean for instance), and start measuring again, but now, the new reference value is the value that was considered half tilt.
if the last difference was positive, now you need a negative difference, and if the last difference was negative, now you need a positive difference; this is, coming back. so start taking values comparing the new reference value with the new values coming from the sensor, and see if one accomplish what you decided in point 3.
if you find a valid value (accomplishing value difference and time conditions ), you have a tilt. But if you dont get a good value and the time is consumed, you reset everything: let your reference value be the last one, reset the timers, reset the half-tilt-done boolean to false, and keep measuring.
I hope this is good enough for you. For sure you can find some libraries or code snippets to help you out with this, but i think is good, as you say, to know the logic of inferring the tilt from the readings
The pictures was taken from this article, wich i recomend to read if you want to improve the accuracy and consider 2 o 3 axis for the tilt
The commonsware Sensor Monitor app does a pretty good job with this. It converts the sensor readouts to X, Y, Z values on each sensor reading, so it's pretty easy from there to determine which way the device is moving.
https://github.com/commonsguy/cw-omnibus/tree/master/Sensor/Monitor
Another item worth noting (from the Commonsware book):
There are four standard delay periods, defined as constants on the
SensorManager class:
SENSOR_DELAY_NORMAL, which is what most apps would use for broad changes, such as detecting a screen rotating from portrait to
landscape
SENSOR_DELAY_UI, for non-game cases where you want to update the UI continuously based upon sensor readings
SENSOR_DELAY_GAME, which is faster (less delay) than SENSOR_DELAY_UI, to try to drive a higher frame rate
SENSOR_DELAY_FASTEST, which is the “firehose” of sensor readings, without delay
You can use the accelerometer and magnetic field sensor to accomplish this. You can call this method in your OnSensorChanged method to detect if the phone was tilt upwards. This currently only works if the phone is held horizontally. Check the actual blog post for a more complete solution.
http://www.ahotbrew.com/how-to-detect-forward-and-backward-tilt/
public boolean isTiltUpward()
{
if (mGravity != null && mGeomagnetic != null)
{
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
if (success)
{
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
/*
* If the roll is positive, you're in reverse landscape (landscape right), and if the roll is negative you're in landscape (landscape left)
*
* Similarly, you can use the pitch to differentiate between portrait and reverse portrait.
* If the pitch is positive, you're in reverse portrait, and if the pitch is negative you're in portrait.
*
* orientation -> azimut, pitch and roll
*
*
*/
pitch = orientation[1];
roll = orientation[2];
inclineGravity = mGravity.clone();
double norm_Of_g = Math.sqrt(inclineGravity[0] * inclineGravity[0] + inclineGravity[1] * inclineGravity[1] + inclineGravity[2] * inclineGravity[2]);
// Normalize the accelerometer vector
inclineGravity[0] = (float) (inclineGravity[0] / norm_Of_g);
inclineGravity[1] = (float) (inclineGravity[1] / norm_Of_g);
inclineGravity[2] = (float) (inclineGravity[2] / norm_Of_g);
//Checks if device is flat on ground or not
int inclination = (int) Math.round(Math.toDegrees(Math.acos(inclineGravity[2])));
/*
* Float obj1 = new Float("10.2");
* Float obj2 = new Float("10.20");
* int retval = obj1.compareTo(obj2);
*
* if(retval > 0) {
* System.out.println("obj1 is greater than obj2");
* }
* else if(retval < 0) {
* System.out.println("obj1 is less than obj2");
* }
* else {
* System.out.println("obj1 is equal to obj2");
* }
*/
Float objPitch = new Float(pitch);
Float objZero = new Float(0.0);
Float objZeroPointTwo = new Float(0.2);
Float objZeroPointTwoNegative = new Float(-0.2);
int objPitchZeroResult = objPitch.compareTo(objZero);
int objPitchZeroPointTwoResult = objZeroPointTwo.compareTo(objPitch);
int objPitchZeroPointTwoNegativeResult = objPitch.compareTo(objZeroPointTwoNegative);
if (roll < 0 && ((objPitchZeroResult > 0 && objPitchZeroPointTwoResult > 0) || (objPitchZeroResult < 0 && objPitchZeroPointTwoNegativeResult > 0)) && (inclination > 30 && inclination < 40))
{
return true;
}
else
{
return false;
}
}
}
return false;
}
Is this what you're looking for?
public class AccelerometerHandler implements SensorEventListener
{
float accelX;
float accelY;
float accelZ;
public AccelerometerHandler(Context paramContext)
{
SensorManager localSensorManager = (SensorManager)paramContext.getSystemService("sensor");
if (localSensorManager.getSensorList(1).size() != 0)
localSensorManager.registerListener(this, (Sensor)localSensorManager.getSensorList(1).get(0), 1);
}
public float getAccelX()
{
return this.accelX;
}
public float getAccelY()
{
return this.accelY;
}
public float getAccelZ()
{
return this.accelZ;
}
public void onAccuracyChanged(Sensor paramSensor, int paramInt)
{
}
public void onSensorChanged(SensorEvent paramSensorEvent)
{
this.accelX = paramSensorEvent.values[0];
this.accelY = paramSensorEvent.values[1];
this.accelZ = paramSensorEvent.values[2];
}
}

detecting the side of a cube that is facing the camera in android opengl es

So I started creating an app to learn openGL es on adroid. First I went through a chapter that explained how to construct a cube and get it to rotate using the system timer. I then mapped each side to a different segment of one image. For development purposes each side is textured with a number. I then implemented the drag feature to allow the user to rotate the cube up/down or left/right depending how they swiped.
First here is some background on my problem:
I want to keep track of which side is facing the camera because each face is being rotated on the axis it started on. For example given a cube that has the unfolded layout as follows.
2
4
3 1 5
6
Where 1 is the side facing the screen, 2 is the opposite (or back face), 4 is up, 5 is right, 6 is down, and 3 is left.This means 3/5 are on the x-axis, 4/6 on the y-axis, and 1/2 on the z axis.
Here is my issue:
The cube rotates correctly if I only rotate around 1 axis (i.e. I only go left/righ or up/down until I go 360) but if I only go to 90 180 or 270 then the axis I should be rotating around have switched. This happens because of what is stated above about each side of the cube being stuck to the axis it started on.
If you rotate right once so 5 is facing, then the z axis from the users perspective is the x-axis of the cube. This gets even more convoluted when you start going 90 degrees left/right then 90 degrees up/down, etc.
I tried to keep track of the faces with an array of numbers listed clockwise from the top number, but depending which number you came from the new directions for the surrounding numbers have changed.
For Example:
I mapped out the numbers surrounding each number as it faces the screen if it was rotated to from 1 so
4 2 4 1 4
3 1 5 3 4 5 1 5 2 3 6 5 2 3 1
6 1 6 2 6
and 2 is a wild card because it can be gotten to from any direction so there is no real initial number layout from 1
6
3 2 5
4
So my arrays would be
sidesOfFace1[] = {4,5,6,3}
sidesOfFace2[] = {6,5,4,3}
sidesOfFace3[] = {4,1,6,2}
sidesOfFace4[] = {2,5,1,3}
sidesOfFace5[] = {4,2,6,1}
sidesOfFace6[] = {1,5,2,3}
And MOVE can have the values
UP = 1 RIGHT = 2 DOWN = 3 LEFT = 4
Then by keeping track of the previous face, curr face, and last move I tried to figure out a formula to get an offset that would help me select what the next face would be given a direction to move to a new face. Roughly I came out with this interpretation:
prevface MOVE currFace
1 -> UP(1) -> 4 -> RIGHT(2) -> 5
offset = opposite direction of MOVE - (sidesOfFace.indexOf(prevFace)+1)
So first I get
1) DOWN - sidesOfFace4.indexOf(1)+1 => 3 - 3 = 0
2) LEFT - sidesOfFace5.indexOf(4)+1 => 4 - 1 = 3
1) This tells me that the sides around 4 are in the same order as the array sidesOfFace, in clockwise starting at the top. So when the user swipes again I can know which side we are going to. This is imperative in being able to set up the right rotations of the cube since they change for the viewer as the cube gets turned.
2) This shows that there is an offset, if we look at the array the index of 4 should be 0 but the cube has been rotated such that the UP side is now at index 3, RIGHT is 0, DOWN is 1, and LEFT is 2.
Besides needing to know which side is facing the screen for other functionality in my app, I also have to know because depending on which side is facing the screen I have to rotate the cube along the correct axis. I am keeping track of the xRot and yRot but these rotations have to happen according the the camera/users view, not the cubes axis.
For Example I found that:
axis for front face up/down axis right/left axis (as seen by the camera)
1 +z +x +y
2 -z -x -y
4 +y +x +z
6 -y -x -z
5 +x +z +y
3 -x -z -y
This means that depending on which side is facing the screen I have to do the xRotations around the up/down axis and the yRotations around the right/left axis.
A friend said something about possibly checking the 4 vertices that are closest to the camera but since I am using glRotate funcs I wasn't sure where I could get that information from. But I still need to know which numbers are on which side of the front face so I can automate the rotations of the cube.
If you actually sat down and read all this I truely do appreciate it, If you could steer me in the right direction. Maybe a link, or better yet a known solution to this problem would be amazing. I have been struggling with this for a few days now and I was just wondering if this was already a problem that had a solution.
Thanks all,
Alan
I'll be honest I didn't completely read the last 3/4 of your post, it's looking way more complicated than it needs to be.
But if you just want to detect which side of the cube is nearest the camera, you should just have to do the following:
With your unrotated cube, create a vector for each direction:
vec3 left(-1, 0, 0)
vec3 right (1, 0, 0)
vec3 up(0, 1, 0)
etc...
Then acquire the current modelview matrix of the cube. If you transform the normal by the cube's modelview, you will get the resulting vectors in eye space.
vec3 leftInEyeSpace = modelView * left;
vec3 upInEyeSpace = modelView * up;
...
This will be the direction of each vector relative to your eye.
Then define a vector from the center of the cube pointing into the camera:
vec3 cubeToCamera= -normalize((modelView * vec4(0,0,0,1)).xyz);
Then you want to take the dot product of each vector with your 'cubeToCamera' vector. Because the dot product decreases as the angle between the vectors increases, the dot product with the greatest magnitude will be the one most facing the camera.
float leftDot = dot(cubeToCamera, leftInEyeSpace)
float rightDot = dot(cubeToCamera, rightInEyeSpace)
...
A bit convoluted, but could something like this work? I left out a few methods but hopefully you get my general idea, you should be able to use the boolean variables to work out which side is facing the user.
private Boolean right, left, spin;
private Boolean up, down, upsideDown;
private Boolean rightSide, leftSide;
// All false by default.
public void swipe(int swipeDirection)
{
// Swipe Direction - 0 = Right, 1 = Left, 2 = Up, 3 = Down
switch (swipeDirection)
{
case 0:
if (upsideDown)
{
swipeLeft();
}
else if (rightSide)
{
swipeDown();
}
else if (leftSide)
{
swipeUp();
}
else
{
swipeRight();
}
break;
case 1:
if (upsideDown)
{
swipeRight();
}
else if (rightSide)
{
swipeUp();
}
else if (leftSide)
{
swipeDown();
}
else
{
swipeLeft();
}
break;
case 2:
if (upsideDown)
{
swipeDown();
}
else if (rightSide)
{
swipeRight();
}
else if (leftSide)
{
swipeLeft();
}
else
{
swipeUp();
}
break;
case 3:
if (upsideDown)
{
swipeUp();
}
else if (rightSide)
{
swipeLeft();
}
else if (leftSide)
{
swipeRight();
}
else
{
swipeDown();
}
break;
}
}
private void swipeRight()
{
if (right)
{
right = false;
spin = true;
}
else if (left)
{
left = false;
}
else if (spin)
{
spin = false;
left = true;
}
else if (up)
{
up = false;
if (rightSide)
{
rightSide = false;
upsideDown = true;
}
else if (leftSide)
{
leftSide = false;
}
else
{
rightSide = true;
}
}
else if (down)
{
down = false;
if (leftSide)
{
leftSide = false;
upsideDown = true;
}
else if (rightSide)
{
rightSide = false;
}
else
{
leftSide = true;
}
}
else
{
right = true;
}
}
private void swipeUp()
{
if (down)
{
down = false;
}
else if (up)
{
upsideDown = !upsideDown;
}
else if (upsideDown)
{
upsideDown = false;
up = true;
}
}

how do I convert Sensor data to degrees

I am getting various sensor readings from my device (programing for android) and i am looking to get the roll (which seems to be a number 1-100) converted into an angle in degrees and also convert the magnetometer heading into degrees..
any simple equations would be appreciated.. my geometry is a fleeting memory..
public void onSensorChanged(SensorEvent evt) {
int type=evt.sensor.getType();
if(type == Sensor.TYPE_ORIENTATION){
azimuth = evt.values[0]; // azimuth rotation around the z-axis
pitch = evt.values[1]; // pitch rotation around the x-axis
roll = evt.values[2]; // roll rotation around the y-axis
}
//Smoothing the sensor data a bit seems like a good idea.
if (type == Sensor.TYPE_MAGNETIC_FIELD) {
orientation[0]=(orientation[0]*1+evt.values[0])*0.5f;
orientation[1]=(orientation[1]*1+evt.values[1])*0.5f;
orientation[2]=(orientation[2]*1+evt.values[2])*0.5f;
} else if (type == Sensor.TYPE_ACCELEROMETER) {
acceleration[0]=(acceleration[0]*2+evt.values[0])*0.33334f;
acceleration[1]=(acceleration[1]*2+evt.values[1])*0.33334f;
acceleration[2]=(acceleration[2]*2+evt.values[2])*0.33334f;
}
if ((type==Sensor.TYPE_MAGNETIC_FIELD) || (type==Sensor.TYPE_ACCELEROMETER)) {
float newMat[]=new float[16];
//Toast toast = Toast.makeText(ctx.getApplicationContext(), "accel", Toast.LENGTH_SHORT);
//toast.show();
SensorManager.getRotationMatrix(newMat, null, acceleration, orientation);
if(displayOri==0||displayOri==2){
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_X*-1, SensorManager.AXIS_MINUS_Y*-1,newMat);
}else{
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,newMat);
}
matrix=newMat;
}
}
I should add that i am not sure i just want roll.. my app locks in landscape mode but obviously the user can turn their phone to any angle on any access.. so i probably need all three of the above to get the angle im looking for.
the angle in question is as if the user is looking through their phone, no matter how they are holding it, at the real world and i want the angle they are looking off the horizon..
for instance if they are looking at the horizon i want 90degrees returned, if they are looking straight up in the sky i should get 180, straight down -180.. then i will also want the degrees from magnetic north that they are looking.. using the magnetometer
values[2] should allready contain degree-value, that's mentioned in a reference:
values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive
values when the z-axis moves toward the x-axis.
Update
Take a look at this picture: http://developer.android.com/images/axis_device.png
Here you see blue axis - Y axis. When your phone turns around it, it's called "rolling". The angle of the rotation will be contained in values[2].

Tile based collision works perfect except against blocks with position.y == 0

I'm working on a simple platformer and I've been through a couple of collision systems before finally finding the solution that has thus far been as stable as I could hope for. That is until the player collides with a block whose CENTER position in the y axis equals 0. It's very wierd although I suspect it's only in the y axis because I check the x movement/collision first. I have no idea.
I do a simple AABB type collision check/response where I apply the x velocity first then if overlap, reposition player so that right player bound = left block bound bringing them out of overlap. I then go through the same with the y axis taking the player vertical direction to work out whether player has hit bottom side or top side of block. The only controls are jump as the player has an acceleration force in positive x hence the player will never travel left.
The problem is that the player moves off the blue dotted block but when it hits the red dotted one a collision is detected in the Y axis thus the player gets moved up out of overlap but then when the next frame executes the player's velocity in x is added as usual but a collision gets registered and it then positions the player to the left of the red block. The next frame detects a collision with the blue block and thus it positions the player on top of it as shown below.
The setup up below makes the player loop this sequence over
Info:
player.centerPosition = (2, 2)
player.width = 0.5f
player.height = 0.8f
blueBlock.centerPosition = (1, 1)
redBlock.centerPosition = (4, 0)
block.width = 3
block.height = 1
private void checkBlockCollision(float deltaTime) {
List<GameObject> colliders = grid.getPotentialColliders(player);
int len = colliders.size();
for (int axis=0;axis<2;axis++) { // 0 = X-axis, 1 = Y-axis
if (axis == 0) {
player.position.add(player.velocity.x*deltaTime, 0);
player.updateBounds();
} else {
player.position.add(0, player.velocity.y*deltaTime);
player.updateBounds();
}
for (int i=0;i<len;i++) { // Cycle through all blocks found in broad phase
GameObject collider = colliders.get(i);
if (OverlapTester.overlapRectangles(player.bounds, collider.bounds)) {
if (axis == 0) {
player.position.x = collider.position.x - (Player.PLAYER_WIDTH + collider.bounds.width)/2;
player.velocity.x = 0f;
Log.d("TAG", "Move player LEFT");
} else {
if (player.velocity.y > 0) {
player.position.y = collider.position.y - (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = -player.velocity.y*0.333f;
Log.d("TAG", "Move player DOWN");
} else {
player.position.y = collider.position.y + (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = 0;
player.state = Player.PLAYER_STATE_GROUNDED;
Log.d("TAG", "Move player UP");
}
}
}
} // end for loop colliders
} // end for loop axis
} // END METHOD
If anyone can shed some light on what the truck is going on here that would be amazing.
Thanks for reading and I can provide any further info or source to anyone interested.
Marios Kalogerou
SOLUTION:
I found a quick and dirty fix to the my problem. I just simply moved the player up an extra 0.001 units and this actually seperated the objects. Strange that since other blocks worked fine. Thanks again if you read through that and I hope my solution helps anyone with similar issues.

Android Compass orientation on unreliable (Low pass filter)

Im creating an application where i need to position a ImageView depending on the Orientation of the device.
I use the values from a MagneticField and Accelerometer Sensors to calculate the device orientation with
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticFieldValues)
SensorManager.getOrientation(rotationMatrix, values);
double degrees = Math.toDegrees(values[0]);
My problem is that the positioning of the ImageView is very sensitive to changes in the orientation. Making the imageview constantly jumping around the screen. (because the degrees change)
I read that this can be because my device is close to things that can affect the magneticfield readings. But this is not the only reason it seems.
I tried downloading some applications and found that the "3D compass" and "Compass" remains extremely steady in its readings (when setting the noise filter up), i would like the same behavior in my application.
I read that i can tweak the "noise" of my readings by adding a "Low pass filter", but i have no idea how to implement this (because of my lack of Math).
Im hoping someone can help me creating a more steady reading on my device, Where a little movement to the device wont affect the current orientation.
Right now i do a small
if (Math.abs(lastReadingDegrees - newReadingDegrees) > 1) { updatePosition() }
To filter abit of the noise. But its not working very well :)
Though I havn't used the compass on Android, the basic processing shown below (in JavaScript) will probably work for you.
It's based on the low pass filter on the accelerometer that's recommended by the Windows Phone team with modifications to suit a compass (the cyclic behavior every 360").
I assume the compass reading is in degrees, a float between 0-360, and the output should be similar.
You want to accomplish 2 things in the filter:
If the change is small, to prevent gitter, gradually turn to that direction.
If the change is big, to prevent lag, turn to that direction immediatly (and it can be canceled if you want the compass to move only in a smooth way).
For that we will have 2 constants:
The easing float that defines how smooth the movement will be (1 is no smoothing and 0 is never updating, my default is 0.5). We will call it SmoothFactorCompass.
The threshold in which the distance is big enough to turn immediatly (0 is jump always, 360 is never jumping, my default is 30). We will call it SmoothThresholdCompass.
We have one variable saved across the calls, a float called oldCompass and it is the result of the algorithm.
So the variable defenition is:
var SmoothFactorCompass = 0.5;
var SmoothThresholdCompass = 30.0;
var oldCompass = 0.0;
and the function recieves newCompass, and returns oldCompass as the result.
if (Math.abs(newCompass - oldCompass) < 180) {
if (Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
oldCompass = newCompass;
}
else {
oldCompass = oldCompass + SmoothFactorCompass * (newCompass - oldCompass);
}
}
else {
if (360.0 - Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
oldCompass = newCompass;
}
else {
if (oldCompass > newCompass) {
oldCompass = (oldCompass + SmoothFactorCompass * ((360 + newCompass - oldCompass) % 360) + 360) % 360;
}
else {
oldCompass = (oldCompass - SmoothFactorCompass * ((360 - newCompass + oldCompass) % 360) + 360) % 360;
}
}
}
I see that the issue was opened 5 months ago and probably isn't relevant anymore, but I'm sure other programmers might find it useful.
Oded Elyada.
This lowpass filter works for angles in radians. Use the add function for each compass reading, then call average to get the average.
public class AngleLowpassFilter {
private final int LENGTH = 10;
private float sumSin, sumCos;
private ArrayDeque<Float> queue = new ArrayDeque<Float>();
public void add(float radians){
sumSin += (float) Math.sin(radians);
sumCos += (float) Math.cos(radians);
queue.add(radians);
if(queue.size() > LENGTH){
float old = queue.poll();
sumSin -= Math.sin(old);
sumCos -= Math.cos(old);
}
}
public float average(){
int size = queue.size();
return (float) Math.atan2(sumSin / size, sumCos / size);
}
}
Use Math.toDegrees() or Math.toRadians() to convert.
Keep in mind that, for example the average of 350 and 10 is not 180. My solution:
int difference = 0;
for(int i= 1;i <numberOfAngles;i++){
difference += ( (angles[i]- angles[0] + 180 + 360 ) % 360 ) - 180;
}
averageAngle = (360 + angles[0] + ( difference / numberOfAngles ) ) % 360;
A low pass filter (LPF) blocks fast changing signals and
allows only slow changes in the signals. This means any small
sudden changes will be ignored.
The standard way to implement this in software is to take a running average
of the last N samples and report that value. Start with N as small as 3 and
keep increasing N until you find sufficient smoothed out response in your app.
Do keep in mind that the higher you make N, slower the response of the system.
See my answer to this related question: Smoothing data from a sensor
A software low pass filter is basically a modified version of that. Indeed, in that answer I even provided this link to another related question: Low pass filter software?

Categories

Resources