I'm trying to make an app that points you toward a position. You press a button and it stores the gps coordinates, then calculates things like distance and the angle you need to face. Then it leads you back to that remembered position by "pointing" toward it using an onscreen compass graphic.
At least, it's supposed to. After messing with the code for hours, I've come to the conclusion that there's just a logic error somewhere due to my lack of trig practice over the past few years.
The compass and GPS position are updated fairly frequently. This is the code in my main update call for the user interface that rotates the compass and displays the distance.
public void updateUI(){
double deltaX = targetLongitude - currentLongitude;
double deltaY = targetLatitude - currentLatitude;
double distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
double rotation = Math.toDegrees(Math.atan2(deltaX,deltaY));
distanceTextView.setText(Double.toString(distance));
rotateCompass(rotation - degreesClockwiseFromNorth);
}
and the code for rotateCompass:
public void rotateCompass(double degrees){
degrees -= currentRotation; //calculates necessary rotation across updates
currentRotation += degrees;
matrix.postRotate(
(float) degrees,
compass.getDrawable().getBounds().width() / 2,
compass.getDrawable().getBounds().height() / 2);
compass.setImageMatrix(matrix);
}
I'm almost certain my rotation code works because when I replace
rotateCompass(rotation - degreesClockwiseFromNorth);
with
rotateCompass(0 - degreesClockwiseFromNorth);
it points north right alongside a real compass regardless of the direction I'm facing. But when I use the former, it points towards a consistent point, but that point seems to be nowhere near the target point.
So I've come to the conclusion my error is either in calculating the correct angle, or expecting the gps to be too precise. I haven't tested it for distances further than what I can in my backyard, but I assume that if it was a gps accuracy issue I'd see my compass jumping all over the place rather than adamantly pointing in a wrong direction.
Thanks for reading, any suggestions or corrections are appreciated.
Your math is all screwed up because the distance between 2 degrees of longitude is not the same as 2 degrees of latitude. In fact, it isn't even a constant length for longitude- its shorted by the poles and longest at the equator. Use the Location.distanceTo functions instead.
Related
I need find angle of vehicle turn measured in degrees.
Location points update with equal intervals (1 sec). Therefore device makes like 4-5 points during turn. I schematically displayed that on picture.
Is it possible to calculate the angle of turn using Location? If it is possible, how?
What I tried:
Create two geometric vectors from points 3, 4 and 1, 2 respectively and find angle between those vectors. Coordinates of vectors I calculated like Vector1 (lat2 - lat1; lon2 - lon2). Not sure this approach could be applied to Location coordinates.
Use location1.bearingTo(location2). But this doesn't give expected results. Seems like it gives "compass" results. Perhabs I could use it somehow but not sure.
Also tried few trigonometric formulas like here or here or here. They didn't give expected angle.
EDIT: Solution
The accepted answer works great. But to complete the answer I have to show that method of angleDifference. This one works for me:
public int getAngleDifference(int currentAngle){
int r = 0;
angleList.add(currentAngle);
if (angleList.size() == 4) {
int d = Math.abs(angleList.get(0) - angleList.get(3)) % 360;
r = d > 180 ? 360 - d : d;
angleList.clear();
}
return r;
}
I add points to list untill there're 4 of them and then calculate angle difference between 1st and 4th points for better results.
Hope it will help for someone!
vect1 = LatLon2 - LatLon1; // vector subtraction
vect2 = LatLon4 - LatLon3;
By definition of the dot product has the property:
vect1.vect2 = ||vect1||*||vect2||*Cos(theta)
Here's a breakdown of the notation
The term vect1.vect2 is the dot product of vect1 and vect2.
The general form of a dot product can be broken down component wise let v1 = <x1,y1> and v2=<x2,y2> for two arbitrary vectors v1 and v2 the dot product would be:
v1.v2 = x1*x2 + y1*y2
and the magnitude of some arbitrary vector v is:
||v|| = sqrt(v.v); which is a scalar.
The above is equivalent to the Euclidean distance formula with components x and y:
||v|| = sqrt(x^2 + y^2)
Getting the angle
Find a value for theta given the two vectors vect1 and vect2:
theta = Math.ArcCos(vect1.vect2/(||vect1||*||vect2||))
Approach 1 does not work as you described: Lat, Lon are not cartesian coordinates (One degree of longitude expressed in meters is not one degree of latitide, this is only valid at the equator). You would have first to transform to a (local) cartesian system.
An error is in the drawing: The angle marked with "?" is placed at the wrong side. You most probably want angle: 180 - ?
In your example the car ist turning less than 90°, altough your angle shows more than 90°.
To understand better make another drawing where the car turns left for only 10 degrees. In your drawing this would be 170°, which is wrong.
Approach 2) works better, but you need to sum up the angle differences.
You have to write yourself a method
double angleDifference(double angle1, double angle2);
This look easier than it is, although the code is only a few lines long.
Make sure that you have some test cases that tests the behaviour when crossing the 360° limit.
Example
(turn from bearing 10 to bearing 350), should either give 20 or -20, depending if you want that the method give sthe absolut evalue or the relative angle
I want to detect if the user has taken a turn on the road while driving using the sensors on the android phone. How do I code this? I am collecting data live from all the sensors(accelerometer,location,rotation,geomagnetic) and storing them on the sd card. So now i just want to know whether the user has a taken a turn and in which direction he has turned.
I assume the registration of the sensor is done properly. You can detect the direction by using the orientation sensor (deprecated) as follows:
#Override
public void onSensorChanged(SensorEvent event) {
float azimuth_angle = event.values[0];
int precision = 2;
if (prevAzimuth - azimuth_angle < precision * -1)
Log.v("->", "RIGHT");
else if (prevAzimuth - azimuth_angle > precision)
Log.v("<-", "LEFT");
prevAzimuth = azimuth_angle;
}
Note: The variable of "prevAzimuth" is declared as global. You can change "precision" value to whatever you want. We need this value because we do not want to see output after each trivial change in azimuth angle. However, too large precision gives imprecise results. To me, "2" is optimum.
If you are tracking location coordinates, you can also track shifts between the angle from previous locations.
angle = arctan((Y2 - Y1) / (X2 - X1)) * 180 / PI
See this answer for calculating x and y.
Decision to use sensor values is based on an unrealistic assumption that the device is never rotated with respect to the vehicle.
I am trying to rotate map view when the user changes his direction ie if user takes left and right turns it should rotate accordingly.I am rotating map view basing on current location bearing it is rotating correctly but it was jittering.Here is the code which i used for rotation
public void onGPSUpdate(Location location)
{
boolean check=isBetterLocation(location, tempLoc);
tempLoc=location;
if(check){
showLocation(location);
}
}
isBetterLocation method is copied from google docs for better location.
private void showLocation(Location loc){
mRotateView.rotate(-loc.getBearing());
}
I registered a location updates with time interval 0 and min distance of 10 for frequent updates.Here my problem is map view is jittering always,can any one tell me how can I smoothly rotate map view like other applications like waze maps do.Thanks...
are you trying to rotate the map in a smooth way such as by one degree at a time or just have it go from degree A to degree B on location update ?
Something like
while (oldAngle != newAngle)
{
mapView.rotate(newAngle);
// this is where you would decied to add or subtract;
newAngle ++ or -- ;
}
not sure if this would work exactly as the loop would run really quickly so maybe do this as a asynctask and add a pause in there to simulate a smooth rotation.
Double angle = Math.atan2((userstartPoint.getX() - userendPoint.getX()), userstartPoint.getY() - userendPoint.getY());
angle = Math.toDegrees(angle);
map.setRotationAngle(angle);
so basically I get the start point (new location) and then the end point (old location) and do a Math.atan2 on it as you can see. Then convert that to a degree and set it to my map rotation.
Now it does not do a smooth rotation but I don't need that. Here is where you could set up your own stepper for a smooth rotate. Unless the google maps already has one.
As the bearing values of the Location are not very exact and tend to jump a little, you should use a filter for the bearing. For example, keep the last 5 bearing-values in an array and use the average of those values as the bearing to rotate the map to. Or use the filter explained in the SensorEvent docs - it's easier to use and can be tweaked better.
This will smoothen out the rotation of the map resp. keep it more stable.
EDIT:
A version of the low-pass filter:
public static float exponentialSmoothing(float input, float output, float alpha) {
output = output + alpha * (input - output);
return output;
}
use it like so:
final static float ALPHA = 0.33; // values between 0 and 1
float bearing;
// on location/bearing changed:
bearing = exponentialSmoothing(bearing, newBearing, ALPHA);
bearing would be the value to use to actually rotate the map, newBearing would be the bearing you get from every event, and with ALPHA you can control how quickly or slowly the rotation acts to a new orientation by weighting how much of the old and the new bearing is taken into account for the result. A small value weighs the old value higher, a high value weighs the new value higher.
I hope that works out better.
To change the bearing of your map, use the Camera class. You can define a new CameraPosition with the new bearing and tell the camera to move with either GoogleMap.moveCamera or GoogleMap.animateCamera if you want a smooth movement.
I have implemented this in my app. What I basically did is that I took the last and second last LatLng of my path and calculate bearing by using
public static float getRotationAngle(LatLng secondLastLatLng, LatLng lastLatLng)
{
double x1 = secondLastLatLng.latitude;
double y1 = secondLastLatLng.longitude;
double x2 = lastLatLng.latitude;
double y2 = lastLatLng.longitude;
float xDiff = (float) (x2 - x1);
float yDiff = (float) (y2 - y1);
return (float) (Math.atan2(yDiff, xDiff) * 180.0 / Math.PI);
}
Set this angle as bearing to camera position.
Note: Sometimes (rarely) it rotates map to opposite direction. i am looking for it but if anyone got reason do reply.
I have a MapView in which I am drawing buildings on using geopoints. Currently, the app determines the closest building to you by just checking your long/lat and comparing to the buildings; but what I want to be able to do is point my device at a building and have that buildings info pop up, even if it isn't the closest building to you.
I have looked up how to accomplish this and I would be using the azimuth direction received from the ORIENTATION sensor listener. I am having trouble using that azimuth direction and determining if it is intercepting one of the buildings on the map.
Would I be using the ORIENTATION sensor and azimuth to accomplish this? How would I go about implementing this? Any help appreciated!
Figured out the solution:
I used this function(and helper function) to get the bearing from my current location to the closest buildings around me(using a set radius):
public static float calculateBearing(GeoPoint before, GeoPoint after) {
Point pBefore = location2Point(before);
Point pAfter = location2Point(after);
float res = -(float) (Math.atan2(pAfter.y - pBefore.y, pAfter.x
- pBefore.x) * 180 / Math.PI) + 90.0f;
if (res < 0)
return res + 360.0f;
else
return res;
}
public static Point location2Point(GeoPoint aLocation) {
return new Point((int) (aLocation.getLongitudeE6()),
(int) (aLocation.getLatitudeE6()));
}
Once you have the bearings of the buildings around you; you must then compare that to the bearing your phone is positioned at using a SensorEventListener for the TYPE_ORIENTATION sensor to find out which of the closest buildings your pointing at.
I have an application of augmented reality in which I have stored information such us metro, gas stations, places of interest, etc. with the corresponding latitude and longitude.
Now, according to the orientation of the device, I would show a marker for each site in the camera view of the device. Similar to Layar and Wikitude.
It takes three days searching without stopping and have not found anyone to explain how to solve this problem.
Since information on this topic is very sparse, and I recently solved this problem on the iPhone, I thought I would share my method for anyone that can make it work with Android (there's nothing really specific to iPhone in this answer except for the Math functions sin, cos, and fmod, which can be found in java.lang.Math). These are the steps I took:
Obtain your own lat/lon and your current compass heading (lat1, lon1 and heading). On the iPhone, CLLocation returns these in degrees, but for these calculations they MUST be in radians (i.e. multiply by PI/180)
Obtain lat/lon of Points of Interest (POI) in radians (lat2 and lon2).
Calculate the distance between lat1/lon1 and lat2/lon2 using formula found here: http://www.movable-type.co.uk/scripts/latlong.html
Calculate angle to lat2/lon2 in relation to north. This is also described in the link above but I had a little bit of trouble getting this to work, here is C code for this:
double latDelta = (lat2 - lat1);
double lonDelta = (lon2 - lon1);
double y = sin(lonDelta) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2)* cos(lonDelta);
double angle = atan2(y, x); //not finished here yet
double headingDeg = compass.currentHeading;
double angleDeg = angle * 180/PI;
double heading = headingDeg*PI/180;
angle = fmod(angleDeg + 360, 360) * PI/180; //normalize to 0 to 360 (instead of -180 to 180), then convert back to radians
angleDeg = angle * 180/PI;
Using standard trigonometry, I calculate x and y. Remember, these coordinates are in 3D space, so we are not finished here yet because you still have to map them to 2D:
x = sin(angle-heading) * distance;
z = cos(angle-heading) * distance; //typically, z faces into the screen, but in our 2D map, it is a y-coordinate, as if you are looking from the bottom down on the world, like Google Maps
Finally, using the projection formula, you can calculate screen x ( I didn't do y because it was not necessary for my project, but you would need to get accelerator data and figure out if the device is perpendicular to the ground). The projection formula is found here (scroll to the very bottom): http://membres.multimania.fr/amycoders/tutorials/3dbasics.html
double screenX = (x * 256) / z
Now you can use this x coordinate to move an image or a marker on your screen. Remember a few points:
Everything must be in radians
The angle from you to the POI relative to North is angleBeteweenPoints - currentHeading
(For some reason I can't properly format the code on this computer, so if anyone wants to edit this answer, feel free).