Optimization of a distance calculation function - c#

In my code I have to do a lot of distance calculation between pairs of lat/long values.
the code looks like this:
double result = Math.Acos(Math.Sin(lat2rad) * Math.Sin(lat1rad)
+ Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad));
(lat2rad e.g. is latitude converted to radians).
I have identified this function as the performance bottleneck of my application. Is there any way to improve this?
(I cannot use look-up tables since the coordinates are varying). I have also looked at this question where a lookup scheme like a grid is suggested, which might be a possibility.
Thanks for your time! ;-)

If your goal is to rank (compare) distances, then approximations (sin and cos table lookups) could drastically reduce your amount of computations required (implement quick reject.)
Your goal is to only proceed with the actual trigonometric computation if the difference between the approximated distances (to be ranked or compared) falls below a certain threshold.
E.g. using lookup tables with 1000 samples (i.e. sin and cos sampled every 2*pi/1000), the lookup uncertainty is at most 0.006284. Using uncertainty calculation for the parameter to ACos, the cumulated uncertainty, also be the threshold uncertainty, will be at most 0.018731.
So, if evaluating Math.Sin(lat2rad) * Math.Sin(lat1rad)
+ Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad) using sin and cos lookup tables for two coordinate-set pairs (distances) yields a certain ranking (one distance appears greater than the other based on the approximation), and the difference's modulus is greater than the threshold above, then the approximation is valid. Otherwise proceed with the actual trigonometric calculation.

Would the CORDIC algorithm work for you (in regards to speed/accuracy)?

Using inspiration from #Brann I think you can reduce the calculation a bit (Warning its a long time since I did any of this and it will need to be verified). Some sort of lookup of precalculated values probably the fastest though
You have :
1: ACOS( SIN A SIN B + COS A COS B COS(A-B) )
but 2: COS(A-B) = SIN A SIN B + COS A COS B
which is rewritten as 3: SIN A SIN B = COS(A-B) - COS A COS B
replace SIN A SIN B in 1. you have :
4: ACOS( COS(A-B) - COS A COS B + COS A COS B COS(A-B) )
You pre-calculate X = COS(A-B) and Y = COS A COS B and you put the values into 4
to give:
ACOS( X - Y + XY )
4 trig calculations instead of 6 !

Change the way you store long/lat:
struct LongLat
{
float
long,
lat,
x,y,z;
}
When creating a long/lat, also compute the (x,y,z) 3D point that represents the equivalent position on a unit sphere centred at the origin.
Now, to determine if point B is nearer to point A than point C, do the following:
// is B nearer to A than C?
bool IsNearer (LongLat A, LongLat B, LongLat C)
{
return (A.x * B.x + A.y * B.y + A.z * B.z) < (A.x * C.x + A.y * C.y + A.z * C.z);
}
and to get the distance between two points:
float Distance (LongLat A, LongLat B)
{
// radius is the size of sphere your mapping long/lats onto
return radius * acos (A.x * B.x + A.y * B.y + A.z * B.z);
}
You could remove the 'radius' term, effectively normalising the distances.

Switching to lookup tables for sin/cos/acos. Will be faster, there are alot of c/c++ fixed point libraries that also include those.
Here is code from someone else on Memoization. Which might work if the actual values used are more clustered.
Here is an SO question on Fixed Point.

What is the bottle neck? Is the the sine/cosine function calls or the arcsine call?
If your sine/cosine calls are slow, you could use the following theorem to prevent so many calls:
1 = sin(x)^2 + cos(x)^2
cos(x) = sqrt(1 - sin(x)^2)
But I like the mapping idea so that you don't have to recompute values you've already computed. Although be careful as the map could get very large very quickly.

How exact do you need the values to be?
If you round your values a bit then you could store the result of all lookups and check if thay have been used befor each calculation?

Well, since lat and lon are garenteed to be within a certain range, you could try using some form of a lookup table for you Math.* method calls. Say, a Dictionary<double,double>

I would argue that you may want to re-examine how you found that function to be the bottleneck. (IE did you profile the application?)
The equation to me seems very light weight and shouldn't cause any trouble.
Granted, I don't know your application and you say you do a lot of these calculations.
Nevertheless it is something to consider.

As someone else pointed out, are you sure this is your bottleneck?
I've done some performance testing of a similar application I'm building where I call a simple method to return a distance between two points using standard trig. 20,000 calls to it shoves it right at the top of the profiling output, yet there's no way I can make it faster... It's just the shear # of calls.
In this case, I need to reduce the # calls to it... Not that this is the bottleneck.

I use a different algorithm for calculating distance between 2 lati/longi positions, it could be lighter than yours since it only does 1 Cos call and 1 Sqrt call.
public static double GetDistanceBetweenTwoPos(double lat1, double long1, double lat2, double long2)
{
double distance = 0;
double x = 0;
double y = 0;
x = 69.1 * (lat1 - lat2);
y = 69.1 * (long1 - long2) * System.Math.Cos(lat2 / 57.3);
//calculation base : Miles
distance = System.Math.Sqrt(x * x + y * y);
//Distance calculated in Kilometres
return distance * 1.609;
}

someone has already mentioned memoisation and this is a bit similar. if you comparing the same point to many other points then it is better to precalculate parts of that equation.
instead of
double result = Math.Acos(Math.Sin(lat2rad) * Math.Sin(lat1rad)
+ Math.Cos(lat2rad) * Math.Cos(lat1rad) * Math.Cos(lon2rad - lon1rad));
have:
double result = Math.Acos(lat2rad.sin * lat1rad.sin
+ lat2rad.cos * lat1rad.cos * (lon2rad.cos * lon1rad.cos + lon1rad.sin * lon2rad.sin));
and i think that's the same formula as someone else has posted because part of the equation will disappear when you expand the brackets:)

Related

Why is my angle of 2 vectors function return NaN even though i follow the formula

I'm making a function that calculates the angle between 2 given vectors for my unity game using the dot product formula:
vector(a)*vector(b)=|vector(a)|*|vector(b)|*cos(the angle)
so I figured that the angle would equals
acos((vector(a)*vector(b))/(|vector(a)|*|vector(b)|))
Anyway here's my code:
float rotateAngle(Vector2 a,Vector2 b)
{
return Mathf.Acos((a.x * b.x + a.y * b.y) / ((Mathf.Sqrt(a.x * a.x + a.y * a.y)) * (Mathf.Sqrt(b.x * b.x + b.y * b.y)))) * (180 / Mathf.PI);
}
But when i played it the console showed NaN. I've tried and reviewed the code and the formula but returned empty-handed.
Can someone help me? Thank you in advanced!!
float.NaN is the result of undefined (for real numbers) mathematical operations such as 0 / 0 (note from the docs that x / 0 where x != 0 rather returns positive or negative infinity) or the square root of a negative value. As soon as one operant in an operation already is NaN then also the entire operation returns again NaN.
The second (square root of a negative value) can not happen here since you are using squared values so most probably your vectors have a magnitude of 0.
If you look at the Vector2 source code you will find their implementation of Vector2.Angle or Vector2.SignedAngle (which you should rather use btw as they are tested and way more efficient).
public static float Angle(Vector2 from, Vector2 to)
{
// sqrt(a) * sqrt(b) = sqrt(a * b) -- valid for real numbers
float denominator = (float)Math.Sqrt(from.sqrMagnitude * to.sqrMagnitude);
if (denominator < kEpsilonNormalSqrt)
return 0F;
float dot = Mathf.Clamp(Dot(from, to) / denominator, -1F, 1F);
return (float)Math.Acos(dot) * Mathf.Rad2Deg;
}
// Returns the signed angle in degrees between /from/ and /to/. Always returns the smallest possible angle
public static float SignedAngle(Vector2 from, Vector2 to)
{
float unsigned_angle = Angle(from, to);
float sign = Mathf.Sign(from.x * to.y - from.y * to.x);
return unsigned_angle * sign;
}
There you will find that the first thing they check is
float denominator = (float)Math.Sqrt(from.sqrMagnitude * to.sqrMagnitude);
if (denominator < kEpsilonNormalSqrt)
return 0F;
which basically makes exactly sure that both given vectors have a "big enough" magnitude, in particular one that is not 0 ;)
Long story short: Don't reinvent the wheel and rather use already built-in Vector2.Angle or Vector2.SignedAngle
NaN are typically the result of invalid mathematical operations on floating point numbers. A common source is division by zero, so my guess would be that the vector is 0,0.
I would also recommend using the built in functions for computing the normalization, Length/Magnitude, Dot etc. that will make the code much easier to read, and the compiler should be fairly good at optimizing that kind of code. If you need to do any additional optimization, only do so after you have done some measurements.

Calculate a specific curve with specific rotation, c#

I am trying to code for a game I am working on a specific curve with a specific rotation. I am not a great mathematician... At all... Tried searching for solutions for a few hours, but I'm affraid I do not find any solution.
So, a small picture to illustrate first:
This is an eighth of a circle, radius of 9, beggining is (0,0)
The end is now at about 6.364, -2.636. But I need this same curve, with a 45° direction at the end, but ending at aexactly 6.0,-3.0.
Could any of you show me how to do this? I need to be able to calculate precisly any point on this curve & its exact length. I would suppose using some kind of eliptical math could be a solution? I admit my math class are reaaaly far now and have now good clue for now...
Thank for any possible help
I think I found a quadratic curve which sastisfies your requirement:
f(x) = -1/12 x^2 + 9
Copy the following into https://www.desmos.com/calculator to see it:
-\frac{1}{12}x^2+9
f'(x) would be -1/6x, so when x=6, the derivative would be -1, which corresponds to a -45° inclination. There are probably infinite curves that satisfy your requirement but if my calculus isn't too rusty this is one of them.
I tried to fit an ellipse with foci starting at y=6 here and starting at y=9 here to your points but the slope doesn't look like 45°.
Also starting at any height k, here doesn't seem to work.
I don't think you've fully understood the question I asked in the comments about the "inclination" angle. So I will give a general case solution, where you have an explicit tangent vector for the end of the curve. (You can calculate this using the inclination angle; if we clarify what you mean by it then I will be happy to edit with a formula to calculate the tangent vector if necessary)
Let's draw a diagram of how the setup can look:
(Not 100% accurate)
A and B are your fixed points. T is the unit tangent vector. r and C are the radius and center of the arc we need to calculate.
The angle θ is given by the angle between BA and T minus π/2 radians (90 degrees). We can calculate it using the dot product:
The (signed) distance from the center of AB to C is given by:
Note that this is negative for the case on the right, and positive for the left. The radius is given by:
(You can simplify by substituting and using a cosine addition rule, but I prefer to keep things in terms of variables in the diagram). To obtain the point C, we need the perpendicular vector to AB (call it n):
Now that we have the radius and center of the circular arc, we still need to determine which direction we are moving in, i.e. whether we are moving clockwise or anti-clockwise when going from A to B. This is a simple test, using the cross-product:
If this is negative, then T is as in the diagram, and we need to move clockwise, and vice versa. The length of the arc l, and the angular displacement γ when we move by a distance x along the arc:
Nearly there! Just one more step - we need to work out how to rotate the point A by angle γ around point C, to get the point we want (call it D):
(Adapted from this Wikipedia page)
Now for some code, in case the above was confusing (it probably was!):
public Vector2 getPointOnArc(Vector2 A, Vector2 B, Vector2 T, double x)
{
// calculate preliminaries
Vector2 BA = B - A;
double d = BA.Length();
double theta = Math.Acos(Vector2.DotProduct(BA, T) / d) - Math.PI * 0.5;
// calculate radius
double r = d / (2.0 * Math.Cos(theta));
// calculate center
Vector2 n = new Vector2(BA.y, -BA.x);
Vector2 C = 0.5 * (A + B + n * Math.Tan(theta));
// calculate displacement angle from point A
double l = (Math.PI - 2.0 * theta) * r;
double gamma = (2.0 * Math.PI * x) / l;
// sign change as discussed
double cross = T.x * BA.y - T.y * BA.x;
if (cross < 0.0) gamma = -gamma;
// finally return the point we want
Vector2 disp = A - C;
double c_g = Math.Cos(gamma), s_g = Math.Sin(gamma);
return new Vector2(disp.X * c_g + disp.Y * s_g + C.X,
disp.Y * c_g - disp.X * s_g + C.Y);
}

How to compute the angles between 2 vectors, to proceed to a rotation?

I have 2 vectors with the same origin, and I would simply like to rotate one to match the other. However, I just can't find the math to be able to compute the x angle, the y angle and the z angle (World coordinates) between the two.
It would also work to get this angles in Local coordinates, I don't know if it helps but the rotation around the Forward vector (Local Y) can be anything it doesn't matter. I only need my object to be facing the right direction.
How could I do ?
There are many ways to approach this. Let me first suggest the cross product, as it is easier to understand than most alternatives (like Quaternions) and might Point you in the right direction.
Basically the cross product between two vectors (a and b) results in a third vector (c)which is perpendicular to both. It is also interesting to note, that the length of this third vector is exactly length(a)*length(b)*sin(Theta), where Theta is the angle between a and b.
Here is what it looks like:
c.x = a.y * b.z - a.z * b.y
c.y = a.z * b.x - a.x * b.z
c.z = a.x * b.y - a.y * b.x
A very simple formula.
Now the trick is, to normalize a and b. Which means to set their length to 1. This is done by taking the vector and dividing each component by it's length
length_a = sqrt(a_old.x * a_old.x + a_old.y * a_old.y + a_old.z * a_old.z)
a.x = a_old.x / length_a
a.y = a_old.y / length_a
a.z = a_old.z / length_a
Using both normalized vectors as Input for the cross product will result in c's length being 1 * 1 * sin(Theta) or just sin(Theta).
(As an alternative, you can also do this with a dot product, as Dmitry Bychenko pointed out in the comments)
What can you do next? You can now freely rotate either a or b around vector c using a Rotation Matrix. Do note however, that you would need to normalize c again to do this.

Logarithmic Spiral - Is Point on Spiral (cartesian coordinates

Lets Say I have a 3d Cartesian grid. Lets also assume that there are one or more log spirals emanating from the origin on the horizontal plane.
If I then have a point in the grid I want to test if that point is in one of the spirals. I acutally want to test if it within a certain range of the spirals but determining if it is on the point is a good start.
So I guess the question has a couple parts.
How to generate the arms from parameters (direction, tightness)
How to tell if a point in the grid is in one of the spiral arms
Any ideas? I have been googling all day and don't feel I am any closer to a solution than when I started.
Here is a bit more information that might help:
I don't actually need to render the spirals. I want to set the pitch and rotation and then pass a point to a method that can tell me if the point I passed is within the spiral (within a given range of any point on the spiral). Based on the value returned (true or false) my program will make a decision on whether or not something exists at the point in space.
How to parametrically define the log spirals (pitch and rotation and ??)
Test if a point (x, y, z) is withing a given range of any point on the spiral.
Note: Both of the above would be just on the horizontal plane
These are two functions defining an anti-clockwise spiral:
PolarPlot[{
Exp[(t + 10)/100],
Exp[t/100]},
{t, 0, 100 Pi}]
Output:
These are two functions defining a clockwise spiral:
PolarPlot[{
- Exp[(t + 10)/100],
- Exp[t/100]},
{t, 0, 100 Pi}]
Output:
Cartesian coordinates
The conversion Cartesian <-> Polar is
(1) Ro = Sqrt[x^2+y^2]
t = ArcTan[y/x]
(2) x = Ro Cos[t]
y = Ro Sin[t]
So, If you have a point in Cartesian Coords (x,y) you transform it to your equivalent polar coordinates using (1). Then you use the forula for the spiral function (any of the four mentinoned above the plots, or similar ones) putting in there the value for t, and obtaining Ro. The last step is to compare this Ro with the one we got from the coordinates converion. If they are equal, the point is on the spiral.
Edit Answering your comment
For a Log spiral is almost the same, but with multiple spirals you need to take care of the logs not going to negative values. That's why I used exponentials ...
Example:
PolarPlot[{
Log[t],
If[t > 3, Log[ t - 2], 0],
If[t > 5, Log[ t - 4], 0]
}, {t, 1, 10}]
Output:
Not sure this is what you want, but you can reverse the log function (or "any" other for that matter).
Say you have ln A = B, to get A from B you do e^B = A.
So you get your point and pass it as B, you'll get A. Then you just need to check if that A (with a certain +- range) is in the values you first passed on to ln to generate the spiral.
I think this might work...
Unfortunately, you will need to know some mathematics notation anyway - this is a good read about the logarithmic sprial.
http://en.wikipedia.org/wiki/Logarithmic_spiral
we will only need the top 4 equations.
For your question 1
- to control the tightness, you tune the parameter 'a' as in the wiki page.
- to control the direction, you offset theta by a certain amount.
For your question 2
In floating point arithmetic, you will never get absolute precision, which mean there will be no point falling exactly on the sprial. On the screen, however, you will know which pixel get rendered, and you can test whether you are hitting a point that is rendered.
To render a curve, you usually render it as a sequence of line segments, short enough so that overall it looks like a curve. If you want to know whether a point lies within certain distance from the spiral, you can render the curve (on a off-screen buffer if you wish) by having thicker lines.
here a C++ code drawing any spiral passing where the mouse here
(sorry for my English)
int cx = pWin->vue.right / 2;
int cy = pWin->vue.bottom / 2;
double theta_mouse = atan2((double)(pWin->y_mouse - cy),(double)(pWin->x_mouse - cx));
double square_d_mouse = (double)(pWin->y_mouse - cy)*(double)(pWin->y_mouse - cy)+
(double)(pWin->x_mouse - cx)*(double)(pWin->x_mouse - cx);
double d_mouse = sqrt(square_d_mouse);
double theta_t = log( d_mouse / 3.0 ) / log( 1.19 );
int x = cx + (3 * cos(theta_mouse));
int y = cy + (3 * sin(theta_mouse));
MoveToEx(hdc,x,y,NULL);
for(double theta=0.0;theta < PI2*5.0;theta+=0.1)
{
double d = pow( 1.19 , theta ) * 3.0;
x = cx + (d * cos(theta-theta_t+theta_mouse));
y = cy + (d * sin(theta-theta_t+theta_mouse));
LineTo(hdc,x,y);
}
Ok now the parameter of spiral is 1.19 (slope) and 3.0 (radius at center)
Just compare the points where theta is a mutiple of 2 PI = PI2 = 6,283185307179586476925286766559
if any points is near of a non rotated spiral like
x = cx + (d * cos(theta));
y = cy + (d * sin(theta));
then your mouse is ON the spiral... I searched this tonight and i googled your past question

Shorten this code that determines distance between two lat/longs in C#?

public static double Distance(LatLong from, LatLong to)
{
double lat1 = from.Latitude * (Math.PI / 180.0);
double lat2 = to.Latitude * (Math.PI / 180.0);
return
Math.Acos((Math.Sin(lat1) * Math.Sin(lat2)) +
(Math.Cos(lat1) * Math.Cos(lat2) *
Math.Cos((Math.PI / 180.0) * (to.Longitude - from.Longitude)))) * 3958.760;
}
Can you shorten this code any stretch? I'm just wondering ...
That's the standard spherical law of cosines formula. You won't get it any simpler than that. At best, you could clean up the code a little:
public static double Distance(LatLong from, LatLong to)
{
double deg = Math.PI / 180.0; // One degree in radians
double lat1 = from.Latitude * deg;
double lat2 = to.Latitude * deg;
double dLng = (to.Longitude - from.Longitude) * deg;
double R = 3958.760;
return Math.Acos(Math.Sin(lat1) * Math.Sin(lat2) +
Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(dLng)) * R;
}
No, but I can offer a shorter, faster way, but far less accurate way to obtain relative distances:
public static double RelativeDistance(LatLong from, LatLong to)
{
return (from.Latitude - to.Latitude) * (from.Latitude - to.Latitude) + (from.Longitude - to.Longitude) * (from.Longitude - to.Longitude);
}
This returns a value relative to the square of the distance in terms of the projection of coordinates unto a square 2D grid (as if the world were a 2:1 rectangle). It's so useless for real distances that I wouldn't even bother to take a square root to bring it back to being proportional to the projection (since the projection is silly), but what it can serve for is rapidly sorting by relative distances within such a small area (and far enough from the poles) that the gross inaccuracy doesn't matter much.
Hence, it won't help you calculate your fuel costs, but it will help you work out which pub is (probably) nearest. If you wanted to sort by relative distance to a given point, it could serve well and its speed be a boon. Outside of that use, it's pointless.
The formula looks like it is computing the distance along the surface of a sphere, and would thus be reasonably accurate even for points that were practically on opposite sides of the world. If the distances will be very close together, you could approximate it by projecting the points onto the surface of a cylinder (coaxial with the Earth) passing through one of the points; scale the cylinder so north/south and east/west distances on the cylinder match those on the Earth. This will simply require taking the cosine of one of the latitudes. Note that if the points are far enough apart that it matters which point's latitude you use, they are too far apart for this to be a good approximation, but for small distances this approach is quick and easy.
Note, btw, that something like a conical projection will be accurate over wider distances, but will also require more calculation; if one is going to that much trouble, one may as well use the 'right' calculations.

Categories

Resources