Converting distance (nautical miles) into degrees (Lat/Long) - java

I have entities in a simulation whose initial locations and paths are code using Java in decimal degrees. I need to scale the sensor radius (it's in nautical miles) and speed (nautical miles/hr) to match decimal degrees. The purpose is to visualize the sim in OpenMap and Google Earth.
I've seen How to convert Distance(miles) to degrees?, but the suggestions there don't work.
Any help is appreciated! I'm thinking it will involve using great circle distance formulas... but can't quite get it.

Ed Williams' Aviation Formula https://edwilliams.org/avform.htm is a good, and accessible, place to start. And I often reference http://movable-type.co.uk/scripts/latlong.html.
I am guessing that you need a vector of some sort (your question is a bit unclear).
What I use (in C, not Java) to calculate a fix-radial-distance is:
void polarToLatLong(double lat, double lon, double dist, double radial,
double *outlat, double *outlon) {
if (!dist) { // distance zero, so just return the point
*outlat = lat;
*outlon = lon;
}
else if (lat > 89.9999) { // North Pole singularity. Dist is in NM.
*outlat = 90 - dist / 60;
*outlon = fmod(radial + 180) - 180;
}
else { // normal case
double sinlat, coslon;
dist /= 3442; // = Earth's radius in nm (not WGS84!)
sinlat = Sin(lat) * cos(dist) + Cos(lat) * sin(dist) * Cos(radial);
*outlat = Arcsin(sinlat);
coslon = (cos(dist) - Sin(lat) * sinlat) / (Cos(lat) * Cos(*outlat));
*outlon = lon + (Sin(radial) >= 0 : -1 : 1) * Arccos(coslon);
}
}
In the above code Sin(), with an upper-case S, is just a wrapper of sin() for degrees:
#define CLAMP(a,x,b) MIN(MAX(a, x), b) // GTK+ GLib version slightly different
double Sin(double deg) {return sin(deg * (PI / 180));} // wrappers for degrees
double Cos(double deg) {return cos(deg * (PI / 180));}
double Arcsin(double x) {return asin(CLAMP(-1, x, 1)) * (180 / PI);}
double Arccos(double x) {return acos(CLAMP(-1, x, 1)) * (180 / PI);}

Related

How do I calculate tilt-compensated yaw?

I've been struggling for some time now on how to correctly calculate the yaw angle from an IMU, but can't get it to work. I'm using the LSM9DS1, if that matters.
I already have proper values for roll and pitch. The value for yaw is also more or less correct until I start tilting the device. Therefore I have to implement some kind of tilt compensation.
I calculate the euler angles as follows:
double weight = 0.05f;
private void calculateEulerAngles() {
// Measured angle by the accelerometer
double rollXLMeasured = Math.atan2(this.accelerometer.getX(), this.accelerometer.getZ()) / 2 / Math.PI * 360;
double pitchXLMeasured = Math.atan2(this.accelerometer.getY() / 9.81f, this.accelerometer.getZ() / 9.81f) / 2 / Math.PI * 360;
// Adding a low pass filter
double rollXLFiltered = (1 - this.weight) * rollXLFilteredOld + this.weight * rollXLMeasured;
double pitchXLFiltered = (1 - this.weight) * pitchXLFilteredOld + this.weight * pitchXLMeasured;
this.rollXLFilteredOld = rollXLFiltered;
this.pitchXLFilteredOld = pitchXLFiltered;
// Calculating deltaTime
long time = System.nanoTime();
int difference = (int) ((time - this.deltaTime) / 1000000000);
this.deltaTime = time;
// Adding a complementary filter
this.roll = ((1 - this.weight) * (this.roll + this.gyroscope.getY() * difference)) + (this.weight * rollXLMeasured);
this.pitch = ((1 - this.weight) * (this.pitch - this.gyroscope.getX() * difference)) + (this.weight * pitchXLMeasured);
// Calculating yaw using the magnetometer and applying a low pass filter
double rollRadians = this.roll / 360 * (2 * Math.PI);
double pitchRadians = this.pitch / 360 * (2 * Math.PI);
double magnetometerXCompensated = (-this.magnetometer.getX() * Math.cos(rollRadians)) - (this.magnetometer.getY() * Math.sin(pitchRadians) *
Math.sin(rollRadians)) + (this.magnetometer.getZ() * Math.cos(pitchRadians) * Math.sin(rollRadians));
double magnetometerYCompensated = (this.magnetometer.getY() * Math.cos(pitchRadians)) + (this.magnetometer.getZ() * Math.sin(pitchRadians));
double yawMeasured = Math.atan2(magnetometerYCompensated, magnetometerXCompensated) / (2 * Math.PI) * 360;
double yawFiltered = (1 - this.weight) * yawFilteredOld + this.weight * yawMeasured;
this.yawFilteredOld = yawFiltered;
this.yaw = yawFiltered;
// Print roll, pitch and yaw for debug purposes
System.out.println(this.roll + ", " + this.pitch + ", " + this.yaw);
}
I don't include the whole code I use, since I think it's clear what the above code does and this is the part thats essential to the problem, I suppose.
And again, I get correct values, just not for yaw when I tilt the device. So there has to be an error concerning the math.
Do I have to do my calculations with the raw values, like the data thats inside the IMU, or use already processed data? For example this.accelerometer.getX() actually returns x_raw * 0.061f / 1000 * 9.81f with x_raw being the value stored inside the IMU and 0.061f being some coeffiecent. I basically copied the calculation from the Adafruit library, I'm not 100% sure why you have to multiply/divide those values though.
Also, you might have noticed that when I calculate the magnetometerXCompensated value I invert the x-axis. I do this, because the magnetometer axis aren't aligned with the acceleromter/gyroscope axis, so in order to align them I have to flip the x-axis.
Does anyone have an idea on how to solve this? I'm really tired of it not working properly, since I tried for quite a while now to solve it, but I'm not getting the results I wanted.
You can get all the equations from the next picture. The complimentary/Kalman filter is there in order to get less noise.

Rotate a point at a given angle

I wrote a code that should turn a point around another point counterclockwise. But it does not work correctly.
public boolean contains(double x, double y) {
double ox = this.x.get() + (this.width.get()/2);
double oy = this.y.get() + (this.height.get()/2);
double theta = rotate.get() - (rotate.get() * 2);
double px1 = Math.cos(theta) * (x-ox) - Math.sin(theta) * (y-oy) + ox;
double py1 = Math.sin(theta) * (x-ox) + Math.cos(theta) * (y-oy) + oy;
return shape.contains(px1, py1);
}
x, y - are the coordinates of the point to be rotated.
ox,oy - is the coordinates of the point around which you want to rotate.
rotate.get() - angle to rotate
Update: Changes in the code that solved the problem, who can come in handy:
double px1 = Math.cos(Math.toRadians(theta)) * (x-ox) - Math.sin(Math.toRadians(theta)) * (y-oy) + ox;
double py1 = Math.sin(Math.toRadians(theta)) * (x-ox) + Math.cos(Math.toRadians(theta)) * (y-oy) + oy;
Please check, if your rotate.get() will give you a degrees value (e.g. 45°) or a radians value (e.g. 0.5*pi). Math.sin() and Math.cos() will only accept radians.
To convert them you could use something like angle = Math.toRadians(45)
Although this is answered, another simple way to get this done is using the built-in method of Rotate class. This way you dont need to worry about the Math stuff ;)
Rotate r = new Rotate();
r.setPivotX(ox);
r.setPivotY(oy);
r.setAngle(angleInDegrees);
Point2D point = r.transform(new Point2D(x, y));

How do you convert between polar coordinates and cartesian coordinates assuming north is zero radians?

I've been trying to make a simple physics engine for games. I am well aware that this is re-inventing the wheel but it's more of a learning exercise than anything else. I never expect it to be as complete as box2d for instance.
I'm having issues with my implementation of 2d Vectors. The issue is related to the fact that in the game world I want to represent north as being zero radians and east as being 1/2 PI Radians, or 0 and 90 degrees respectively. However in mathematics (or maybe more specifically the Math class of Java), I believe trigonometry functions like sine and cosine assume that "east" is zero radians and I think north is 1/2 PI Radians?
Anyway I've written a small version of my vector class that only demonstrates the faulty code.
public class Vector {
private final double x;
private final double y;
public Vector(double xIn, double yIn) {
x = xIn;
y = yIn;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getR() {
return Math.sqrt((x * x) + (y * y));
}
public double getTheta() {
return Math.atan(y / x);
}
public double bearingTo(Vector target) {
return (Math.atan2(target.getY() - y, target.getX() - x));
}
public static Vector fromPolar(double magnitude, double angle) {
return new Vector(magnitude * Math.cos(angle),
magnitude * Math.sin(angle));
}
}
And here is the test code to demonstrate the issue:
public class SOQuestion {
public static void main(String[] args) {
//This works just fine
Vector origin = new Vector(0, 0);
Vector target = new Vector(10, 10);
double expected = Math.PI * 0.25;
double actual = origin.bearingTo(target);
System.out.println("Expected: " + expected);
System.out.println("Actual: " + actual);
//This doesn't work
origin = new Vector(0, 0);
target = new Vector(10, 0);
expected = Math.PI * 0.5; //90 degrees, or east.
actual = origin.bearingTo(target); //Of course this is going to be zero, because in mathematics right is zero
System.out.println("Expected: " + expected);
System.out.println("Actual: " + actual);
//This doesn't work either
Vector secondTest = Vector.fromPolar(100, Math.PI * 0.5); // set the vector to the cartesian coordinates of (100,0)
System.out.println("X: " + secondTest.getX()); //X ends up being basically zero
System.out.println("Y: " + secondTest.getY()); //Y ends up being 100
} }
The requirements are:
fromPolar(magnitude,angle) should return a vector with x and y initialized to the appropriate values assuming north is at zero radians and east is at 1/2 PI radians. for example fromPolar(10,PI) should construct a vector with x: 0 and y: -1
getTheta() should return a value greater than or equal to zero and less than 2 PI. Theta is the angular component of the vector it's called on. For example a vector with x:10 and y:10 would return a value of 1/4 PI when getTheta() is called.
bearingTo(target) should return a value that is greater than or equal to zero and less than 2 PI. The value represents the bearing to another vector.
The test code demonstrates that when you try to get the bearing of one point at (0,0) to another point at (10,0), it doesn't produce the intended result, it should be 90 degrees or 1/2 PI Radians.
Likewise, trying to initialize a vector from polar coordinates sets the x and y coordinate to unexpected values. I'm trying to avoid saying "incorrect values" since it' not incorrect, it just doesn't meet the requirements.
I've messed around with the code a lot, adding fractions of PI here or taking it away there, switching sine and cosine, but all of these things only fix parts of the problem and never the whole problem.
Finally I made a version of this code that can be executed online http://tpcg.io/OYVB5Q
Typical polar coordinates 0 points to the East and they go counter-clockwise. Your coordinates start at the North and probably go clockwise. The simplest way to fix you code is to first to the conversion between angles using this formula:
flippedAngle = π/2 - originalAngle
This formula is symmetrical in that it converts both ways between "your" and "standard" coordinates. So if you change your code to:
public double bearingTo(Vector target) {
return Math.PI/2 - (Math.atan2(target.getY() - y, target.getX() - x));
}
public static Vector fromPolar(double magnitude, double angle) {
double flippedAngle = Math.PI/2 - angle;
return new Vector(magnitude * Math.cos(flippedAngle),
magnitude * Math.sin(flippedAngle));
}
It starts to work as your tests suggest. You can also apply some trigonometry knowledge to not do this Math.PI/2 - angle calculation but I'm not sure if this really makes the code clearer.
If you want your "bearing" to be in the [0, 2*π] range (i.e. always non-negative), you can use this version of bearingTo (also fixed theta):
public class Vector {
private final double x;
private final double y;
public Vector(double xIn, double yIn) {
x = xIn;
y = yIn;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getR() {
return Math.sqrt((x * x) + (y * y));
}
public double getTheta() {
return flippedAtan2(y, x);
}
public double bearingTo(Vector target) {
return flippedAtan2(target.getY() - y, target.getX() - x);
}
public static Vector fromPolar(double magnitude, double angle) {
double flippedAngle = flipAngle(angle);
return new Vector(magnitude * Math.cos(flippedAngle),
magnitude * Math.sin(flippedAngle));
}
// flip the angle between 0 is the East + counter-clockwise and 0 is the North + clockwise
// and vice versa
private static double flipAngle(double angle) {
return Math.PI / 2 - angle;
}
private static double flippedAtan2(double y, double x) {
double angle = Math.atan2(y, x);
double flippedAngle = flipAngle(angle);
// additionally put the angle into [0; 2*Pi) range from its [-pi; +pi] range
return (flippedAngle >= 0) ? flippedAngle : flippedAngle + 2 * Math.PI;
}
}

Java Operations in Math Equations

I am trying to write a program that calculates the distance of a projectile, but the distance returned is not coming out correct. I am familiar with operator precedence in Java, but I am not sure why I am not getting the correct distance. For angle = 22, velocity = 35, and height = 10 I expect to get 75.54 but instead I am getting 42.03.
Are there obvious mistakes in my code that are causing this?
public class FootballDistanceCalculator {
public static final double GRAVITATIONAL_ACCELERATION = 32.174;
/**
* Calculates the distance a projectile travels
*
* #param angle angle at which projectile is thrown in degrees
* #param velocity initial velocity of projectile in miles/hour
* #param height initial height of projectile in feet
* #return distance traveled by projectile in feet
*/
public static double calculateDistance(double angle, double velocity, double height) {
double angleRadians = Math.toRadians(angle);
double vCosineThetaOverG = (velocity * (Math.cos(angleRadians))) / GRAVITATIONAL_ACCELERATION ;
double vSinTheta = velocity * (Math.sin(angleRadians));
double vSinThetaSquared = (Math.pow(vSinTheta, 2));
double twoGravHeight = (2 * GRAVITATIONAL_ACCELERATION * height);
double radical = Math.sqrt((vSinThetaSquared + twoGravHeight));
double distance = vCosineThetaOverG * (vSinTheta + radical);
return distance;
}
}
This is the equation I am basing this program off of:
d = (v cos(θ) / g)(v sin(θ) + √(v sin(θ)2 + 2 g h))
v = velocity
g = gravitational acceleration
h = height
The problem turned out to be a units conversion issue as indicated in the comments.
I had to take my velocity parameter and multiply by feet per mile (5280) and divide by seconds per hour (3600) to get my units to match.
You are calculating in metric but your g constant is in feet per second per second.

Getting lon/lat from pixel coords in Google Static Map

I have a JAVA project to do using Google Static Maps and after hours and hours working, I can't get a thing working, I will explain everything and I hope someone will be able to help me.
I am using a static map (480pixels x 480pixels), the map's center is lat=47, lon=1.5 and the zoom level is 5.
Now what I need is being able to get lat and lon when I click a pixel on this static map. After some searches, I found that I should use Mercator Projection (right ?), I also found that each zoom level doubles the precision in both horizontal and vertical dimensions but I can't find the right formula to link pixel, zoom level and lat/lon...
My problem is only about getting lat/lon from pixel, knowing the center's coords and pixel and the zoom level...
Thank you in advance !
Use the Mercator projection.
If you project into a space of [0, 256) by [0,256]:
LatLng(47,=1.5) is Point(129.06666666666666, 90.04191318303863)
At zoom level 5, these equate to pixel coordinates:
x = 129.06666666666666 * 2^5 = 4130
y = 90.04191318303863 * 2^5 = 2881
Therefore, the top left of your map is at:
x = 4130 - 480/2 = 4070
y = 2881 - 480/2 = 2641
4070 / 2^5 = 127.1875
2641 / 2^5 = 82.53125
Finally:
Point(127.1875, 82.53125) is LatLng(53.72271667491848, -1.142578125)
Google-maps uses tiles for the map to efficient divide the world into a grid of 256^21 pixel tiles. Basically the world is made of 4 tiles in the lowest zoom. When you start to zoom you get 16 tiles and then 64 tiles and then 256 tiles. It basically a quadtree. Because such a 1d structure can only flatten a 2d you also need a mercantor projection or a conversion to WGS 84. Here is a good resource Convert long/lat to pixel x/y on a given picture. There is function in Google Maps that convert from lat-long pair to pixel. Here is a link but it says the tiles are 128x128 only: http://michal.guerquin.com/googlemaps.html.
Google Maps V3 - How to calculate the zoom level for a given bounds
http://www.physicsforums.com/showthread.php?t=455491
Based on the math in Chris Broadfoot's answer above and some other code on Stack Overflow for the Mercator Projection, I got this
public class MercatorProjection implements Projection {
private static final double DEFAULT_PROJECTION_WIDTH = 256;
private static final double DEFAULT_PROJECTION_HEIGHT = 256;
private double centerLatitude;
private double centerLongitude;
private int areaWidthPx;
private int areaHeightPx;
// the scale that we would need for the a projection to fit the given area into a world view (1 = global, expect it to be > 1)
private double areaScale;
private double projectionWidth;
private double projectionHeight;
private double pixelsPerLonDegree;
private double pixelsPerLonRadian;
private double projectionCenterPx;
private double projectionCenterPy;
public MercatorProjection(
double centerLatitude,
double centerLongitude,
int areaWidthPx,
int areaHeightPx,
double areaScale
) {
this.centerLatitude = centerLatitude;
this.centerLongitude = centerLongitude;
this.areaWidthPx = areaWidthPx;
this.areaHeightPx = areaHeightPx;
this.areaScale = areaScale;
// TODO stretch the projection to match to deformity at the center lat/lon?
this.projectionWidth = DEFAULT_PROJECTION_WIDTH;
this.projectionHeight = DEFAULT_PROJECTION_HEIGHT;
this.pixelsPerLonDegree = this.projectionWidth / 360;
this.pixelsPerLonRadian = this.projectionWidth / (2 * Math.PI);
Point centerPoint = projectLocation(this.centerLatitude, this.centerLongitude);
this.projectionCenterPx = centerPoint.x * this.areaScale;
this.projectionCenterPy = centerPoint.y * this.areaScale;
}
#Override
public Location getLocation(int px, int py) {
double x = this.projectionCenterPx + (px - this.areaWidthPx / 2);
double y = this.projectionCenterPy + (py - this.areaHeightPx / 2);
return projectPx(x / this.areaScale, y / this.areaScale);
}
#Override
public Point getPoint(double latitude, double longitude) {
Point point = projectLocation(latitude, longitude);
double x = (point.x * this.areaScale - this.projectionCenterPx) + this.areaWidthPx / 2;
double y = (point.y * this.areaScale - this.projectionCenterPy) + this.areaHeightPx / 2;
return new Point(x, y);
}
// from https://stackoverflow.com/questions/12507274/how-to-get-bounds-of-a-google-static-map
Location projectPx(double px, double py) {
final double longitude = (px - this.projectionWidth/2) / this.pixelsPerLonDegree;
final double latitudeRadians = (py - this.projectionHeight/2) / -this.pixelsPerLonRadian;
final double latitude = rad2deg(2 * Math.atan(Math.exp(latitudeRadians)) - Math.PI / 2);
return new Location() {
#Override
public double getLatitude() {
return latitude;
}
#Override
public double getLongitude() {
return longitude;
}
};
}
Point projectLocation(double latitude, double longitude) {
double px = this.projectionWidth / 2 + longitude * this.pixelsPerLonDegree;
double siny = Math.sin(deg2rad(latitude));
double py = this.projectionHeight / 2 + 0.5 * Math.log((1 + siny) / (1 - siny) ) * -this.pixelsPerLonRadian;
Point result = new org.opencv.core.Point(px, py);
return result;
}
private double rad2deg(double rad) {
return (rad * 180) / Math.PI;
}
private double deg2rad(double deg) {
return (deg * Math.PI) / 180;
}
}
Here's a unit test for the original answer
public class MercatorProjectionTest {
#Test
public void testExample() {
// tests against values in https://stackoverflow.com/questions/10442066/getting-lon-lat-from-pixel-coords-in-google-static-map
double centerLatitude = 47;
double centerLongitude = 1.5;
int areaWidth = 480;
int areaHeight = 480;
// google (static) maps zoom level
int zoom = 5;
MercatorProjection projection = new MercatorProjection(
centerLatitude,
centerLongitude,
areaWidth,
areaHeight,
Math.pow(2, zoom)
);
Point centerPoint = projection.projectLocation(centerLatitude, centerLongitude);
Assert.assertEquals(129.06666666666666, centerPoint.x, 0.001);
Assert.assertEquals(90.04191318303863, centerPoint.y, 0.001);
Location topLeftByProjection = projection.projectPx(127.1875, 82.53125);
Assert.assertEquals(53.72271667491848, topLeftByProjection.getLatitude(), 0.001);
Assert.assertEquals(-1.142578125, topLeftByProjection.getLongitude(), 0.001);
// NOTE sample has some pretty serious rounding errors
Location topLeftByPixel = projection.getLocation(0, 0);
Assert.assertEquals(53.72271667491848, topLeftByPixel.getLatitude(), 0.05);
// the math for this is wrong in the sample (see comments)
Assert.assertEquals(-9, topLeftByPixel.getLongitude(), 0.05);
Point reverseTopLeftBase = projection.projectLocation(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
Assert.assertEquals(121.5625, reverseTopLeftBase.x, 0.1);
Assert.assertEquals(82.53125, reverseTopLeftBase.y, 0.1);
Point reverseTopLeft = projection.getPoint(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
Assert.assertEquals(0, reverseTopLeft.x, 0.001);
Assert.assertEquals(0, reverseTopLeft.y, 0.001);
Location bottomRightLocation = projection.getLocation(areaWidth, areaHeight);
Point bottomRight = projection.getPoint(bottomRightLocation.getLatitude(), bottomRightLocation.getLongitude());
Assert.assertEquals(areaWidth, bottomRight.x, 0.001);
Assert.assertEquals(areaHeight, bottomRight.y, 0.001);
}
}
If you're (say) working with aerial photography, I feel like the algorithm doesn't take into account the stretching effect of the mercator projection, so it might lose accuracy if your region of interest isn't relatively close to the equator. I guess you could approximate it by multiplying your x coordinates by cos(latitude) of the center?
It seems worth mentioning that you can actually have the google maps API give you the latitudinal & longitudinal coordinates from pixel coordinates.
While it's a little convoluted in V3 here's an example of how to do it. (NOTE: This is assuming you already have a map and the pixel vertices to be converted to a lat&lng coordinate):
let overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.onAdd = function() {};
overlay.onRemove = function() {};
overlay.setMap(map);
let latlngObj = overlay.fromContainerPixelToLatLng(new google.maps.Point(pixelVertex.x, pixelVertex.y);
overlay.setMap(null); //removes the overlay
Hope that helps someone.
UPDATE: I realized that I did this two ways, both still utilizing the same way of creating the overlay (so I won't duplicate that code).
let point = new google.maps.Point(628.4160703464878, 244.02779437950872);
console.log(point);
let overlayProj = overlay.getProjection();
console.log(overlayProj);
let latLngVar = overlayProj.fromContainerPixelToLatLng(point);
console.log('the latitude is: '+latLngVar.lat()+' the longitude is: '+latLngVar.lng());

Categories

Resources