I have two functions that are intended to contain angles between (-180,180] and (-π,π]. The intent is that given any angle from -inf to +inf it will retain the equivalent angle in the intervals specified. For example the angle for 1550° is 110°.
public double WrapBetween180(double angle)
{
return angle - 360d * Math.Round(angle / 360d, MidpointRounding.AwayFromZero);
}
public double WrapBetweenPI(double angle)
{
const double twopi = 2d * Math.PI;
return angle - twopi * Math.Round(angle / twopi, MidpointRounding.AwayFromZero);
}
which yields the following results
WrapBetween180(-180) = -180
WrapBetween180( 180) = 180
WrapBetweenPI(-Math.PI) = Math.PI
WrapBetweenPI( Math.PI) = -Math.PI
none of which is what I want. What I wanted is:
WrapBetween180(-180) = 180
WrapBetween180( 180) = 180
WrapBetweenPI(-Math.PI) = Math.PI
WrapBetweenPI( Math.PI) = Math.PI
I tryied playing around with the rounding methods, but still cannot get the desired results. The problem is pronounced because sometimes the angles I deal with are only approximately close to -π or π and I am getting discontinuities it my results.
Any suggestions on how to best implement angle wrapping functions with non-inclusive low limit and inclusive high limits?
For the angle in degrees, if x is between -180 and 180, then 180 - x is between 0 and 360. What you want is equivalent to asking that 180 - x is between 0 (inclusive), and 360 (exclusive). So, as soon as 180 - x reaches 360, we want to add 360 to the angle. This gives us:
return angle + 360d * Math.Floor((180d - angle) / 360d);
Same thing for the angle in radians:
return angle + twopi * Math.Floor((Math.PI - angle) / twopi);
It does not address the rounding issue, but here is how I would to what you want to do :
private static double ConvertAngle(double angle)
{
bool isNegative = angle < 0;
if (isNegative)
angle *= -1;
angle = angle % 360;
if (isNegative)
angle = -1 * angle + 360;
if (angle > 180)
angle = (angle - 360);
return angle;
}
Note: This way supposes you want "behind" to be 180 degrees, not -180 degrees.
Isn't this a case for a modulo operation?
private double Wrap180(double value)
{
// exact rounding of corner values
if (value == 180) return 180.0;
if (value == -180) return 180.0;
// "shift" by 180 and use module, then shift back.
double wrapped = ((Math.Abs(value) + 180.0) % 360.0) - 180.0;
// handle negative values correctly
if (value < 0) return -wrapped;
return wrapped;
}
It passes this tests
Assert.AreEqual(170.0, wrap(-190.0));
Assert.AreEqual(180.0, wrap(-180.0));
Assert.AreEqual(-170.0, wrap(-170.0));
Assert.AreEqual(0.0, wrap(0.0));
Assert.AreEqual(10.0, wrap(10.0));
Assert.AreEqual(170.0, wrap(170.0));
Assert.AreEqual(180.0, wrap(180.0));
Assert.AreEqual(-170.0, wrap(190.0));
Related
I want to calculate bearing between 2 GPS positions, I foollowed this page recommandations for my algorythm:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(pt1.Latitude) * Math.Sin(pt2.Latitude) - Math.Sin(pt1.Latitude) * Math.Cos(pt2.Latitude) * Math.Cos(pt2.Longitude - pt1.Longitude);
double y = Math.Sin(pt2.Longitude - pt1.Longitude) * Math.Cos(pt2.Latitude);
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2)%(Math.PI * 2);
}
Then I transform my value in degrees using this method
public static double RadiansToDegrees(double angle)
{
return (angle * 180.0) / Math.PI;
}
I have the following test sample:
Point1 (lat, long) = 43.6373638888888888888888888888889, 1.35762222222222222222222222222222
Point2 (lat, long) = 43.6156444444444444444444444444444,1.380225
Expected bearing = 323°
However, I obtain a bearing of 315.5° (5.5062235835910762 rad). If i calculate the expected radian value, i get 5.637413 which leaves no doubt that my problem lies in my bearing method.
I already implemented other computation methods using .Net Math package (including Cos, Sin, Tan and ATan methods) and my unit tests pass with 1e-12 precision. What am I missing?
PS: I also tryied to reimplement the Atan2 method in case there is a lack of precision in it. I obtain the very same result
edit: My Latitude and Longitude are double as per the following interface
public interface IPointGps
{
double Latitude { get; }
double Longitude { get; }
}
Math.Sin() and all similar methods expect argument in radians, but your latitudes and longitudes are in degrees. You have to convert IPointGps to radians before you calculate bearing, or modify Bearing calculation, e.g.:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(DegreesToRadians(pt1.Latitude)) * Math.Sin(DegreesToRadians(pt2.Latitude)) - Math.Sin(DegreesToRadians(pt1.Latitude)) * Math.Cos(DegreesToRadians(pt2.Latitude)) * Math.Cos(DegreesToRadians(pt2.Longitude - pt1.Longitude));
double y = Math.Sin(DegreesToRadians(pt2.Longitude - pt1.Longitude)) * Math.Cos(DegreesToRadians(pt2.Latitude));
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2) % (Math.PI * 2);
}
public static double DegreesToRadians(double angle)
{
return angle * Math.PI / 180.0d;
}
returns bearing 5.637716736134105.
It looks like your latitude and longitude variables are float (single precision). If that is the case, then your are facing a precision error.
I want to calculate bearing between 2 GPS positions, I foollowed this page recommandations for my algorythm:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(pt1.Latitude) * Math.Sin(pt2.Latitude) - Math.Sin(pt1.Latitude) * Math.Cos(pt2.Latitude) * Math.Cos(pt2.Longitude - pt1.Longitude);
double y = Math.Sin(pt2.Longitude - pt1.Longitude) * Math.Cos(pt2.Latitude);
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2)%(Math.PI * 2);
}
Then I transform my value in degrees using this method
public static double RadiansToDegrees(double angle)
{
return (angle * 180.0) / Math.PI;
}
I have the following test sample:
Point1 (lat, long) = 43.6373638888888888888888888888889, 1.35762222222222222222222222222222
Point2 (lat, long) = 43.6156444444444444444444444444444,1.380225
Expected bearing = 323°
However, I obtain a bearing of 315.5° (5.5062235835910762 rad). If i calculate the expected radian value, i get 5.637413 which leaves no doubt that my problem lies in my bearing method.
I already implemented other computation methods using .Net Math package (including Cos, Sin, Tan and ATan methods) and my unit tests pass with 1e-12 precision. What am I missing?
PS: I also tryied to reimplement the Atan2 method in case there is a lack of precision in it. I obtain the very same result
edit: My Latitude and Longitude are double as per the following interface
public interface IPointGps
{
double Latitude { get; }
double Longitude { get; }
}
Math.Sin() and all similar methods expect argument in radians, but your latitudes and longitudes are in degrees. You have to convert IPointGps to radians before you calculate bearing, or modify Bearing calculation, e.g.:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(DegreesToRadians(pt1.Latitude)) * Math.Sin(DegreesToRadians(pt2.Latitude)) - Math.Sin(DegreesToRadians(pt1.Latitude)) * Math.Cos(DegreesToRadians(pt2.Latitude)) * Math.Cos(DegreesToRadians(pt2.Longitude - pt1.Longitude));
double y = Math.Sin(DegreesToRadians(pt2.Longitude - pt1.Longitude)) * Math.Cos(DegreesToRadians(pt2.Latitude));
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2) % (Math.PI * 2);
}
public static double DegreesToRadians(double angle)
{
return angle * Math.PI / 180.0d;
}
returns bearing 5.637716736134105.
It looks like your latitude and longitude variables are float (single precision). If that is the case, then your are facing a precision error.
I'm calculating the angle using 2 points of a square.
The angle works well but it gives me values from -180 to 180 and it's hard for me to code the direction of my robot. I wanted the angle only with position values ex: 0 - 360;
var deltay = pontos_quadrado[0].Y - pontos_quadrado[1].Y;
var deltax = pontos_quadrado[1].X - pontos_quadrado[0].X;
angulo = Math.Atan2(deltay, deltax) * 180 / Math.PI;
angulo = Math.Round(angulo, 0);
You can force the angle to be in the range of 0 to 360 by adding 360 and taking the remainder modulo 360.
var deltay = pontos_quadrado[0].Y - pontos_quadrado[1].Y;
var deltax = pontos_quadrado[1].X - pontos_quadrado[0].X;
angulo = Math.Atan2(deltay, deltax) * 180 / Math.PI;
angulo = (angulo + 360) % 360; // note this line
angulo = Math.Round(angulo, 0);
I have a ellipse with center point is at origin(0,0)
double dHalfwidthEllipse = 10;
double dHalfheightEllipse = 20;
double dAngle = 30;//Its in degree
PointF ptfPoint = new PointF();//To be found
PointF ptfOrigin = new PointF(0, 0);//Origin
Angle of point with respect to origin = 30 degree;
How to get the point now given the above values using C#?
See http://www.mathopenref.com/coordparamellipse.html
The parametric equation for an ellipse with center point at the origin, half width a and half height b is
x(t) = a cos t,
y(t) = b sin t
If you simply wish to draw an ellipse, given
double dHalfwidthEllipse = 10; // a
double dHalfheightEllipse = 20; // b
PointF ptfOrigin = new PointF(0, 0); // Origin
all you need is
PointF ptfPoint =
new PointF(ptfOrigin.X + dHalfwidthEllipse * Math.Cos(t * Math.Pi/180.0),
ptfOrigin.Y + dHalfheightEllipse * Math.Sin(t * Math.Pi/180.0) );
with t varying between -180 and 180 degrees.
However, as #Sebastian points out, if you wish to compute the exact intersection with a line through the center with angle theta, it gets a bit more complicated, since we need to find a t that corresponds to theta:
y(t)/x(t) = tan θ
b sin t / (a cos t) = tan θ
b/a tan t = tan θ
t= arctan(a tan θ / b) + n * π
So if we add
double dAngle = 30; // theta, between -90 and 90 degrees
We can compute t and ptfPoint:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse);
PointF ptfPoint =
new PointF(ptfOrigin.X + dHalfwidthEllipse * Math.Cos(t),
ptfOrigin.Y + dHalfheightEllipse * Math.Sin(t) );
This works fine for the area around the positive x axis.
For theta between 90 and 180 degrees, add π:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse) + Math.Pi;
For theta between -180 and -90 degrees, subtract π:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse) - Math.Pi;
As you get close to the y axis, x(t) approaches zero and the above calculation divides by zero, but there you can use the opposite:
x(t)/y(t) = tan (90 - θ)
a cos t / (b sin t) = tan (90 - θ)
a/b tan t = tan (90 - θ)
t = arctan ( b tan (90 - θ) / a ) + n * π
I am having some trouble trying to implement drawing an arc. Basically it's supposed to be drawn exactly like how you can draw a 3 point arc in AutoCAD. I can get the arc to draw with start and end angles from from -180 degrees to 360, but if I try to go more than -180, the arc flips around to +180 degrees. My problem is that I need the arc to continue when the angle passes -180. I have everything right in the code (I think) except for calculating the start and end angles. Thanks in advance for any help. Here is my code:
private void DrawArc()
{
//fyi linept is of type List<Point3D>();
Point3D currentPoint = (Point3D)GetPoints(e);
double rad;
Point3D center = GetCenterOfCircle(linept.ElementAt(0), linept.ElementAt(1), currentPoint, out rad);
PieSliceVisual3D circle = new PieSliceVisual3D();
circle.Center = center;
circle.OuterRadius = rad;
circle.InnerRadius = circle.OuterRadius + 3;
circle.StartAngle = (Math.Atan2(linept.ElementAt(0).Y - center.Y, linept.ElementAt(0).X - center.X) * 180 / Math.PI);
circle.EndAngle = (Math.Atan2(currentPoint.Y - center.Y, currentPoint.X - center.X) * 180 / Math.PI);
//I've also tried these next 4 lines to no avail
double startAngle = (Math.Atan2(linept.ElementAt(0).Y - center.Y, linept.ElementAt(0).X - center.X) * 180 / Math.PI);
circle.StartAngle = (startAngle > 0.0 ? startAngle : (360.0 + startAngle));
double endAngle = (Math.Atan2(currentPoint.Y - center.Y, currentPoint.X - center.X) * 180 / Math.PI);
circle.EndAngle = (endAngle > 0.0 ? endAngle : (360.0 + endAngle));
}