Implementing Cardano method for a cubic equation solution - c#

I'm trying to find real roots for a cubic equation defined by a set of four coefficients by using Cardano method as described here. The problem is, the roots found by my implementation do not actually work - testing by inserting them in the equation give a significant error (more than the required 10^-6). Is the algorithm implemented wrong, or the error is caused by something else, like rounding accuracy?
static double CubicRoot(double n)
{
return Math.Pow(Math.Abs(n), 1d / 3d) * Math.Sign(n);
}
public static List<double> SolveCubic(double A, double B = 0, double C = 0, double D = 0)
{
List<double> output = new List<double>();
if (A != 0)
{
double A1 = B / A;
double A2 = C / A;
double A3 = D / A;
double P = -((A1 * A1) / 3) + A2;
double Q = ((2.0 * A1 * A1 * A1) / 27.0) - ((A1 * A2) / 3.0) + A3;
double cubeDiscr = Q * Q / 4.0 + P * P * P / 27.0;
if (cubeDiscr > 0)
{
double u = CubicRoot(-Q / 2.0 + Math.Sqrt(cubeDiscr));
double v = CubicRoot(-Q / 2.0 - Math.Sqrt(cubeDiscr));
output.Add(u + v - (A1 / 3.0));
return output;
}
else if (cubeDiscr == 0)
{
double u = CubicRoot(-Q / 2.0);
output.Add(2u - (A1 / 3.0));
output.Add(-u - (A1 / 3.0));
}
else if (cubeDiscr < 0)
{
double r = CubicRoot(Math.Sqrt(-(P * P * P / 27.0)));
double alpha = Math.Atan(Math.Sqrt(-cubeDiscr) / (-Q / 2.0));
output.Add(r * (Math.Cos(alpha / 3.0) + Math.Cos((6 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
output.Add(r * (Math.Cos((2 * Math.PI + alpha) / 3.0) + Math.Cos((4 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
output.Add(r * (Math.Cos((4 * Math.PI + alpha) / 3.0) + Math.Cos((2 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
}
}
return output;
}

A few things
Math.Sign will return zero on zero, which happens to be what you want in this case, but perhaps you are not so lucky with code or algorithm change.
You will have rounding issues and not execute cubeDiscr == 0 branch when you should. You may have rounding issues and execute the wrong > 0 and < 0 branch for the same reason. Test within a delta of zero instead (see below).
But the cubeDiscr == 0 branch is wrong because 1) you didn't calculate v and 2) 2u is an UInt32 with a value of 2, not 2*u.
Calculating alpha is wrong (see below)
(there may be more, but that's all I saw at a quick glance)
On calculating alpha:
double alpha = Math.Atan(Math.Sqrt(-cubeDiscr) / (-Q / 2.0));
is not the same as
double alpha = Math.Atan(Math.Sqrt(-d) / q * 2.0);
if (q > 0) // if q > 0 the angle becomes PI + alpha
alpha = Math.PI + alpha;
What's wrong with using the code included from that page?
public double Xroot(double a, double x)
{
double i = 1;
if (a < 0)
i = -1;
return (i * Math.Exp( Math.Log(a*i)/x));
}
public int Calc_Cardano() // solve cubic equation according to cardano
{
double p, q, u, v;
double r, alpha;
int res;
res = 0;
if (a1 != 0)
{
a = b / a1;
b = c / a1;
c = d / a1;
p = -(a * a / 3.0) + b;
q = (2.0 / 27.0 * a * a * a) - (a * b / 3.0) + c;
d = q * q / 4.0 + p * p * p / 27.0;
if (Math.Abs(d) < Math.Pow(10.0, -11.0))
d = 0;
// 3 cases D > 0, D == 0 and D < 0
if (d > 1e-20)
{
u = Xroot(-q / 2.0 + Math.Sqrt(d), 3.0);
v = Xroot(-q / 2.0 - Math.Sqrt(d), 3.0);
x1.real = u + v - a / 3.0;
x2.real = -(u + v) / 2.0 - a / 3.0;
x2.imag = Math.Sqrt(3.0) / 2.0 * (u - v);
x3.real = x2.real;
x3.imag = -x2.imag;
res = 1;
}
if (Math.Abs(d) <= 1e-20)
{
u = Xroot(-q / 2.0, 3.0);
v = Xroot(-q / 2.0, 3.0);
x1.real = u + v - a / 3.0;
x2.real = -(u + v) / 2.0 - a / 3.0;
res = 2;
}
if (d < -1e-20)
{
r = Math.Sqrt(-p * p * p / 27.0);
alpha = Math.Atan(Math.Sqrt(-d) / q * 2.0);
if (q > 0) // if q > 0 the angle becomes PI + alpha
alpha = Math.PI + alpha;
x1.real = Xroot(r, 3.0) * (Math.Cos((6.0 * Math.PI - alpha) / 3.0) + Math.Cos(alpha / 3.0)) - a / 3.0;
x2.real = Xroot(r, 3.0) * (Math.Cos((2.0 * Math.PI + alpha) / 3.0) + Math.Cos((4.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
x3.real = Xroot(r, 3.0) * (Math.Cos((4.0 * Math.PI + alpha) / 3.0) + Math.Cos((2.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
res = 3;
}
}
else
res = 0;
return res;
}

Related

C# trigonometric functions / math functions

I'm writing a class which should calculate angles (in degrees), after long trying I don't get it to work.
When r = 45 & h = 0 sum should be around 77,14°
but it returns NaN - Why? I know it must be something with the Math.Atan-Method and a not valid value.
My code:
private int r = 0, h = 0;
public Schutzwinkelberechnung(int r, int h)
{
this.r = r;
this.h = h;
}
public double getSchutzwinkel()
{
double sum = 0;
sum = 180 / Math.PI * Math.Atan((Math.Sqrt(2 * h * r - (h * h)) - r * Math.Sin(Math.Asin((Math.Sqrt(2 * h * r)) / (2 * r)))) / (h - r + r * (Math.Cos(Math.Asin(Math.Sqrt(2 * h * r) / (2 * r))))));
return sum;
}
Does someone see my mistake? I got the formula from an excel sheet.
EDIT: Okay, my problem was that I had a parsing error while creating the object or getting the user input, apparently solved it by accident. Ofc I have to add a simple exception, as Nick Dechiara said. Thank you very much for the fast reply, I appreciate it.
EDIT2: The exception in my excel sheet is:
if(h < 2) {h = 2}
so that's explaining everything and I wasn't paying attention at all. Thanks again for all answers.
int r = 45, h = 2;
sum = 77.14°
A good approach to debugging these kinds of issues is to break the equation into smaller pieces, so it is easier to debug.
double r = 45;
double h = 0;
double sqrt2hr = Math.Sqrt(2 * h * r);
double asinsqrt2hr = Math.Asin((sqrt2hr) / (2 * r));
double a = (Math.Sqrt(2 * h * r - (h * h)) - r * Math.Sin(asinsqrt2hr));
double b = (h - r + r * (Math.Cos(asinsqrt2hr)));
double sum = 180 / Math.PI * Math.Atan(a / b);
Now if we put a breakpoint at sum and let the code run, we see that both a and b are equal to zero. This gives us a / b = 0 / 0 = NaN in the final line.
Now we can ask, why is this happening? Well in the case of b you have h - r + r which is 0 - 45 + 45, evaluates to 0, so b becomes 0. You probably have an error in your math there.
In the case of a, we have 2 * h * r - h * h, which also evaluates to 0.
You probably either A) have an error in your equation, or B) need to include a special case for when h = 0, as that is breaking your math here.
Definitely break up the expression something like
var a = Asin(Sqrt(2 * h * r) / (2 * r));
var b = Sqrt(2 * h * r - h * h) - r * Sin(a);
var c = h - r + r * Cos(a);
var sum = 180 / PI * Atan(b / c);
and you will find b=0 and c=0. You might consider changing the last expression into
var sum = 180 / PI * Atan2(b , c);
which will return a value when b=0 and c=0.
PS. Also, use using static System.Math; in the beginning of the code to shorten such math expressions.

Convert Easting/Northing to Lat Lng

I've been struggling to convert northing/eastings to lat/long without using a nuget package. I'm sure my UTM zone is 30U (Great Britain).
How would one go about doing this in C#?
When manually done using this site. It provides the correct values and locations.
Easting: 426342 Northing: 505339
Lat/Long should be: 54.44277977022131, -1.5953328509040021
I had previously found some code on stackoverflow but it's giving me the wrong values.
utmZone = "30U"
public static LatLng ToLatLon(double utmX, double utmY, string utmZone)
{
bool isNorthHemisphere = utmZone.Last() >= 'N';
var diflat = -0.00066286966871111111111111111111111111;
var diflon = -0.0003868060578;
var zone = int.Parse(utmZone.Remove(utmZone.Length - 1));
var c_sa = 6378137.000000;
var c_sb = 6356752.314245;
var e2 = Math.Pow((Math.Pow(c_sa, 2) - Math.Pow(c_sb, 2)), 0.5) / c_sb;
var e2cuadrada = Math.Pow(e2, 2);
var c = Math.Pow(c_sa, 2) / c_sb;
var x = utmX - 500000;
var y = isNorthHemisphere ? utmY : utmY - 10000000;
var s = ((zone * 6.0) - 183.0);
var lat = y / (c_sa * 0.9996);
var v = (c / Math.Pow(1 + (e2cuadrada * Math.Pow(Math.Cos(lat), 2)), 0.5)) * 0.9996;
var a = x / v;
var a1 = Math.Sin(2 * lat);
var a2 = a1 * Math.Pow((Math.Cos(lat)), 2);
var j2 = lat + (a1 / 2.0);
var j4 = ((3 * j2) + a2) / 4.0;
var j6 = ((5 * j4) + Math.Pow(a2 * (Math.Cos(lat)), 2)) / 3.0;
var alfa = (3.0 / 4.0) * e2cuadrada;
var beta = (5.0 / 3.0) * Math.Pow(alfa, 2);
var gama = (35.0 / 27.0) * Math.Pow(alfa, 3);
var bm = 0.9996 * c * (lat - alfa * j2 + beta * j4 - gama * j6);
var b = (y - bm) / v;
var epsi = ((e2cuadrada * Math.Pow(a, 2)) / 2.0) * Math.Pow((Math.Cos(lat)), 2);
var eps = a * (1 - (epsi / 3.0));
var nab = (b * (1 - epsi)) + lat;
var senoheps = (Math.Exp(eps) - Math.Exp(-eps)) / 2.0;
var delt = Math.Atan(senoheps / (Math.Cos(nab)));
var tao = Math.Atan(Math.Cos(delt) * Math.Tan(nab));
double longitude = ((delt * (180.0 / Math.PI)) + s) + diflon;
double latitude = ((lat + (1 + e2cuadrada * Math.Pow(Math.Cos(lat), 2) - (3.0 / 2.0) * e2cuadrada * Math.Sin(lat) * Math.Cos(lat) * (tao - lat)) * (tao - lat)) * (180.0 / Math.PI)) + diflat;
return new LatLng
{
Latitude = latitude,
Longitude = longitude
};
}
Refer the below code and run LatLonConversions.ConvertOSToLatLon(426342, 505339)
public class LatLonConversions
{
const double a = 6377563.396;
const double b = 6356256.91;
const double e2 = (a - b) / a;
const double n0 = -100000;
const double e0 = 400000;
const double f0 = 0.999601272;
const double phi0 = 0.855211333;
const double lambda0 = -0.034906585;
const double n = (a - b) / (a + b);
static double lat, lng;
private LatLonConversions() { }
private static double Deg2Rad(double x)
{
return x * (Math.PI / 180);
}
private static double Rad2Deg(double x)
{
return x * (180 / Math.PI);
}
private static double SinSquared(double x)
{
return Math.Sin(x) * Math.Sin(x);
}
private static double TanSquared(double x)
{
return Math.Tan(x) * Math.Tan(x);
}
private static double Sec(double x)
{
return 1.0 / Math.Cos(x);
}
private static void OSGB36ToWGS84()
{
var airy1830 = new RefEll(6377563.396, 6356256.909);
var a = airy1830.maj;
var b = airy1830.min;
var eSquared = airy1830.ecc;
var phi = Deg2Rad(lat);
var lambda = Deg2Rad(lng);
var v = a / (Math.Sqrt(1 - eSquared * SinSquared(phi)));
var H = 0; // height
var x = (v + H) * Math.Cos(phi) * Math.Cos(lambda);
var y = (v + H) * Math.Cos(phi) * Math.Sin(lambda);
var z = ((1 - eSquared) * v + H) * Math.Sin(phi);
var tx = 446.448;
var ty = -124.157;
var tz = 542.060;
var s = -0.0000204894;
var rx = Deg2Rad(0.00004172222);
var ry = Deg2Rad(0.00006861111);
var rz = Deg2Rad(0.00023391666);
var xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z);
var yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z);
var zB = tz + (-ry * x) + (rx * y) + (z * (1 + s));
var wgs84 = new RefEll(6378137.000, 6356752.3141);
a = wgs84.maj;
b = wgs84.min;
eSquared = wgs84.ecc;
var lambdaB = Rad2Deg(Math.Atan(yB / xB));
var p = Math.Sqrt((xB * xB) + (yB * yB));
var phiN = Math.Atan(zB / (p * (1 - eSquared)));
for (var i = 1; i < 10; i++)
{
v = a / (Math.Sqrt(1 - eSquared * SinSquared(phiN)));
double phiN1 = Math.Atan((zB + (eSquared * v * Math.Sin(phiN))) / p);
phiN = phiN1;
}
var phiB = Rad2Deg(phiN);
lat = phiB;
lng = lambdaB;
}
public static LatLon ConvertOSToLatLon(double easting, double northing)
{
RefEll airy1830 = new RefEll(6377563.396, 6356256.909);
double OSGB_F0 = 0.9996012717;
double N0 = -100000.0;
double E0 = 400000.0;
double phi0 = Deg2Rad(49.0);
double lambda0 = Deg2Rad(-2.0);
double a = airy1830.maj;
double b = airy1830.min;
double eSquared = airy1830.ecc;
double phi = 0.0;
double lambda = 0.0;
double E = easting;
double N = northing;
double n = (a - b) / (a + b);
double M = 0.0;
double phiPrime = ((N - N0) / (a * OSGB_F0)) + phi0;
do
{
M =
(b * OSGB_F0)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n))
* (phiPrime - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.Sin(phiPrime - phi0)
* Math.Cos(phiPrime + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.Sin(2.0 * (phiPrime - phi0))
* Math.Cos(2.0 * (phiPrime + phi0)))
- (((35.0 / 24.0) * n * n * n)
* Math.Sin(3.0 * (phiPrime - phi0))
* Math.Cos(3.0 * (phiPrime + phi0))));
phiPrime += (N - N0 - M) / (a * OSGB_F0);
} while ((N - N0 - M) >= 0.001);
var v = a * OSGB_F0 * Math.Pow(1.0 - eSquared * SinSquared(phiPrime), -0.5);
var rho =
a
* OSGB_F0
* (1.0 - eSquared)
* Math.Pow(1.0 - eSquared * SinSquared(phiPrime), -1.5);
var etaSquared = (v / rho) - 1.0;
var VII = Math.Tan(phiPrime) / (2 * rho * v);
var VIII =
(Math.Tan(phiPrime) / (24.0 * rho * Math.Pow(v, 3.0)))
* (5.0
+ (3.0 * TanSquared(phiPrime))
+ etaSquared
- (9.0 * TanSquared(phiPrime) * etaSquared));
var IX =
(Math.Tan(phiPrime) / (720.0 * rho * Math.Pow(v, 5.0)))
* (61.0
+ (90.0 * TanSquared(phiPrime))
+ (45.0 * TanSquared(phiPrime) * TanSquared(phiPrime)));
var X = Sec(phiPrime) / v;
var XI =
(Sec(phiPrime) / (6.0 * v * v * v))
* ((v / rho) + (2 * TanSquared(phiPrime)));
var XII =
(Sec(phiPrime) / (120.0 * Math.Pow(v, 5.0)))
* (5.0
+ (28.0 * TanSquared(phiPrime))
+ (24.0 * TanSquared(phiPrime) * TanSquared(phiPrime)));
var XIIA =
(Sec(phiPrime) / (5040.0 * Math.Pow(v, 7.0)))
* (61.0
+ (662.0 * TanSquared(phiPrime))
+ (1320.0 * TanSquared(phiPrime) * TanSquared(phiPrime))
+ (720.0
* TanSquared(phiPrime)
* TanSquared(phiPrime)
* TanSquared(phiPrime)));
phi =
phiPrime
- (VII * Math.Pow(E - E0, 2.0))
+ (VIII * Math.Pow(E - E0, 4.0))
- (IX * Math.Pow(E - E0, 6.0));
lambda =
lambda0
+ (X * (E - E0))
- (XI * Math.Pow(E - E0, 3.0))
+ (XII * Math.Pow(E - E0, 5.0))
- (XIIA * Math.Pow(E - E0, 7.0));
lat = Rad2Deg(phi);
lng = Rad2Deg(lambda);
// convert to WGS84
OSGB36ToWGS84();
return new LatLon(lat, lng);
}
}
public class RefEll
{
public double maj, min, ecc;
public RefEll(double major, double minor)
{
maj = major;
min = minor;
ecc = ((major * major) - (minor * minor)) / (major * major);
}
}
public class LatLon
{
public double Latitude;
public double Longitude;
public LatLon()
{
Latitude = 0;
Longitude = 0;
}
public LatLon(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
}
I ran into the same issue. Ended up using this library: https://github.com/Tronald/CoordinateSharp
This sample will return the exact position in latlon decimal.
UniversalTransverseMercator utm = new UniversalTransverseMercator("T", 30, 581177.3879, 4794824.5279);
Coordinate c = UniversalTransverseMercator.ConvertUTMtoLatLong(utm);
c.FormatOptions.Format = CoordinateFormatType.Decimal;
c.FormatOptions.Round = 7;
Debug.Log($"({c.Latitude}, {c.Longitude})");

Why is converting RGB to CMYK not showing the correct result

I am using this site as a reference: http://www.rapidtables.com/convert/color/rgb-to-cmyk.htm
My code:
public void Convert2CMYK()
{
float c, m, y, k;
if (inRed == 0 && inGreen == 0 && inBlue == 0)
{
tbCyan.Text = "0";
tbMagenta.Text = "0";
tbYellow.Text = "0";
tbBlack.Text = "1";
}
c = 1 - (inRed / 255f);
m = 1 - (inGreen / 255f);
y = 1 - (inBlue / 255f);
var minCMY = Math.Min(c, Math.Min(m, y));
c = (c - minCMY) / (1 - minCMY) * 100;
m = (m - minCMY) / (1 - minCMY) * 100;
y = (y - minCMY) / (1 - minCMY) * 100;
k = minCMY * 100;
tbCyan.Text = c.ToString();
tbMagenta.Text = m.ToString();
tbYellow.Text = y.ToString();
tbBlack.Text = k.ToString();
}
On the site, R=25, G=25, B=25 results in C=0, M=0, Y=0, K=0.902
In the app (my code), R=25, G=25, B=25 results in C=0, M=0, Y=0, K=90.19608
What do I have to modify to ensure my results are accurate.
Thanks to everyone for their help. Here are the final equations which did the trick:
c = Math.Round((c - minCMY) / (1 - minCMY), 3);
m = Math.Round((m - minCMY) / (1 - minCMY), 3);
y = Math.Round((y - minCMY) / (1 - minCMY), 3);
k = Math.Round(minCMY, 3);

Conversion from NZMG to latitude and longitude

I want to convert NEW ZEALAND MAP GRID coordinates i.e. Northing and Easting into WGS84 coordinates i.e. Latitude and Longitude.
I have searched on internet but there is no proper explanation of how to do this, or an online calculator to do this.
My final goal is to write a program in C# or JAVA , which will convert NZMG coordinates into WGS84 coordinates.
Please note: This implementation does not convert with pinpoint accuracy.
Here is a c# implementation I put together for converting from NZMG to NZGD1949 and then to datum shift from NZGD1949 to NZGD2000. NZGD2000 coordinates are apparently compatible with WGS84 source: http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/wgs84-nzgd2000. I have added a 'useStaticCorrection' parameter to the datum shift method because I found my results were consistently off by approximately 40 meters in latitude - this varies depending on the input coordinates. This may be from a mistake in the logic but, as far as i can tell, it appears all correct (as by the sources referenced).
Sources:
Constants and coefficients
http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/projection-conversions/new-zealand-map-grid
Datum info.
http://www.linz.govt.nz/regulatory/25000
NZMG -> NZGD1949 formula
http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/projection-conversions/new-zealand-map-grid
NZ1949 -> NZGD2000 differences
http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/nzgd1949-nzgd2000/three-seven
Equivilent of a Three parameter shift.
http://sas2.elte.hu/tg/eesti_datums_egs9.htm
//Lat / Long fudge amounts http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/datum-transformation-examples
public class Pair<T, U>
{
public Pair()
{
}
public Pair(T first, U second)
{
this.First = first;
this.Second = second;
}
public T First { get; set; }
public U Second { get; set; }
};
using System.Numerics;
public class NZMGConverter
{
//Constants and coefficients
//Source: http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/projection-conversions/new-zealand-map-grid
private static Double N0 = 6023150;
private static Double E0 = 2510000;
private static Double NZGD1949a = 6378388;
private static Double NZMGLatOrigin = -41;
private static Double NZMGLongOrigin = 173;
//some of these are unused but are included for completeness. (unused arrays are for converting from wgs84 / nzgd2000 -> nzmg).
private static Double[] A = new Double[] { 0.6399175073, -0.1358797613, 0.0632944090, -0.0252685300, 0.0117879000, -0.0055161000, 0.0026906000, -0.0013330000, 0.0006700000, -0.0003400000 };
private static Complex[] B = new Complex[] { new Complex(0.7557853228, 0.0), new Complex(0.249204646, 0.003371507), new Complex(-0.001541739, 0.04105856), new Complex(-0.10162907, 0.01727609), new Complex(-0.26623489, -0.36249218), new Complex(-0.6870983, -1.1651967) };
private static Complex[] C = new Complex[] { new Complex(1.3231270439, 0.0), new Complex(-0.577245789, -0.007809598), new Complex(0.508307513, -0.112208952), new Complex(-0.15094762, 0.18200602), new Complex(1.01418179, 1.64497696), new Complex(1.9660549, 2.5127645) };
private static Double[] D = new Double[] { 1.5627014243, 0.5185406398, -0.0333309800, -0.1052906000, -0.0368594000, 0.0073170000, 0.0122000000, 0.0039400000, -0.0013000000 };
//Datum info.
//Source: http://www.linz.govt.nz/regulatory/25000
private static Double NZGD1949f = 0.003367003;
//Double NZGD1949InverseFlattening = 297;
private static Double NZGD1949e2 = (2 * 0.003367003) - Math.Pow(0.003367003, 2);
private static Double NZGD2000a = 6378137;
private static Double NZGD2000f = 0.003352811;
//Double NZGD2000InverseFlattening = 298.2572221;
private static Double NZGD2000e2 = (2 * 0.003352811) - Math.Pow(0.003352811, 2);
//NZ1949 -> NZGD2000 differences
//Source: http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/nzgd1949-nzgd2000/three-seven
private static Double differenceX = 54.4;
private static Double differenceY = -20.1;
private static Double differenceZ = 183.1;
//Source: http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/projection-conversions/new-zealand-map-grid
public static Pair<Double, Double> convertToNZGD1949(Double nzmgX, Double nzmgY)
{
Pair<Double, Double> result = null;
Complex z = new Complex((nzmgY - N0) / NZGD1949a, (nzmgX - E0) / NZGD1949a);
Complex theta0 = new Complex();
for (int i = 0; i < C.Length; ++i) { theta0 += Complex.Multiply(C[i], Complex.Pow(z, i + 1)); }
Double deltaLatWtheta0 = 0;
for (int i = 0; i < D.Length; ++i) { deltaLatWtheta0 += D[i] * Math.Pow(theta0.Real, (i + 1)); }
result = new Pair<double, double>();
result.First = NZMGLatOrigin + (Math.Pow(10, 5) / 3600) * deltaLatWtheta0; //NZGD1949 lat
result.Second = NZMGLongOrigin + 180 / Math.PI * theta0.Imaginary; //NZGD1949 long
return result;
}
//Equivilent of a Three parameter shift.
//Source: http://sas2.elte.hu/tg/eesti_datums_egs9.htm
public static Pair<Double, Double> datumShiftNZGD1949toNZGD2000(Double nzgd1949Lat, Double nzgd1949Long, Boolean useStaticCorrection)
{
Pair<Double, Double> result = null;
Double latInRaidans = nzgd1949Lat * (Math.PI / 180);
Double lngInRaidans = nzgd1949Long * (Math.PI / 180);
Double n = NZGD1949a / Math.Sqrt((1 - NZGD1949e2 * Math.Pow(Math.Sin(latInRaidans), 2)) * (3 / 2));
Double m = NZGD1949a * ((1 - NZGD1949e2) / (1 - NZGD1949e2 * Math.Pow(Math.Sin(latInRaidans), 2) * (3 / 2)));
Double difF = NZGD2000f - NZGD1949f;
Double difA = NZGD2000a - NZGD1949a;
Double changeInLat = (-1 * differenceX * Math.Sin(latInRaidans) * Math.Cos(lngInRaidans)
- differenceY * Math.Sin(latInRaidans) * Math.Sin(lngInRaidans)
+ differenceZ * Math.Cos(latInRaidans) + (NZGD1949a * difF + NZGD1949f * difA) * Math.Sin(2 * latInRaidans))
/ (m * Math.Sin(1));
Double changeInLong = (-1 * differenceX * Math.Sin(lngInRaidans) + differenceY * Math.Cos(lngInRaidans)) / (n * Math.Cos(latInRaidans) * Math.Sin(1));
result = new Pair<Double, Double>();
result.First = (latInRaidans + changeInLat) * (180 / Math.PI);
result.Second = (lngInRaidans + changeInLong) * (180 / Math.PI);
if (useStaticCorrection)
{
//Difference found by 3-parameter test values # http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/datum-transformation-examples
Double staticLatCorrection = 0.00037432;
Double staticLongCorrection = -0.00003213;
result.First -= staticLatCorrection;
result.Second += staticLongCorrection;
}
return result;
}
}
NZMG to Latitude/Longitude using Swift. Thanks to DavidG's post above. Fixed 40m latitude error.
import Foundation
import Darwin
class NZMGConverter {
private let easting: Double!
private let northing: Double!
private var z: Complex! = nil
private let N0: Double = 6023150; //false Northing
private let E0: Double = 2510000; //false Easting
private let NZMGLatOrigin: Double = -41;
private let NZMGLongOrigin: Double = 173;
private let c: [Complex] = [Complex(real: 1.3231270439, imag: 0.0), Complex(real: -0.577245789, imag: -0.007809598), Complex(real: 0.508307513, imag: -0.112208952), Complex(real: -0.15094762, imag: 0.18200602), Complex(real: 1.01418179, imag: 1.64497696), Complex(real: 1.9660549, imag: 2.5127645)]
private let b: [Complex] = [Complex(real: 0.7557853228, imag: 0.0), Complex(real: 0.249204646, imag: 0.003371507), Complex(real: -0.001541739, imag: 0.04105856), Complex(real: -0.10162907, imag: 0.01727609), Complex(real: -0.26623489, imag: -0.36249218), Complex(real: -0.6870983, imag: -1.1651967)]
private let d: [Double] = [1.5627014243, 0.5185406398, -0.0333309800, -0.1052906000, -0.0368594000, 0.0073170000, 0.0122000000, 0.0039400000, -0.0013000000]
//Datum info.
private let NZGD1949f: Double = 0.003367003;
//Double NZGD1949InverseFlattening = 297;
private let NZGD1949e2: Double = (2 * 0.003367003) - pow(0.003367003, 2);
private let NZGD1949a: Double = 6378388; //Semi-major axis
private let NZGD2000a: Double = 6378137;
private let NZGD2000f: Double = 0.003352811;
//Double NZGD2000InverseFlattening = 298.2572221;
private let NZGD2000e2: Double = (2 * 0.003352811) - pow(0.003352811, 2);
//NZ1949 -> NZGD2000 differences
private let differenceX: Double = 54.4;
private let differenceY: Double = -20.1;
private let differenceZ: Double = 183.1;
init(easting: Double, northing: Double) {
self.easting = easting
self.northing = northing
}
func nzmgToNZGD1949() -> (latitude: Double, longitude: Double) {
z = Complex(real: (northing - N0) / NZGD1949a, imag: (easting - E0) / NZGD1949a)
let theta0 = thetaZero(z)
let theta: Complex = thetaSuccessive(theta0, i: 0)
let latlong = calcLatLong(theta)
print(latlong)
return datumShift(latlong.0, longitude: latlong.1)
}
func thetaZero(z: Complex) -> Complex {
var theta0 = Complex()
for var i = 0; i < c.count; i++ {
theta0 = theta0 + (c[i] * (z ^ (i + 1)))
//print(theta0)
}
return theta0
}
func thetaSuccessive(theta0: Complex, i: Int) -> Complex {
var numerator: Complex = Complex()
var denominator: Complex = Complex()
for var n = 1; n < b.count; n++ {
numerator = numerator + (Complex(real: n) * b[n] * (theta0 ^ (n + 1)))
}
numerator = z + numerator
for var n = 0; n < b.count; n++ {
denominator = denominator + (Complex(real: n + 1) * b[n] * (theta0 ^ n))
}
let theta = numerator / denominator
if i == 2 {
return theta
} else {
return thetaSuccessive(theta, i: i + 1)
}
}
func calcLatLong(theta: Complex) -> (latitude: Double, longitude: Double) {
let deltaPsi = theta.real
let deltaLambda = theta.imag
var deltaPhi: Double = 0
for var n = 0; n < d.count; n++ {
deltaPhi += d[n] * pow(deltaPsi, Double(n + 1))
}
let lat: Double = NZMGLatOrigin + ((pow(10, 5) / 3600) * deltaPhi)
let long: Double = NZMGLongOrigin + ((180 / M_PI) * deltaLambda)
return (latitude: lat, longitude: long)
}
func datumShift(latitude: Double, longitude: Double) -> (latitude: Double, longitude: Double) {
let latInRadians = latitude * (M_PI / 180)
let lngInRadians = longitude * (M_PI / 180)
let v = NZGD1949a / (sqrt(1 - (NZGD1949e2 * pow(sin(latInRadians), 2))))
var x_cart = v * cos(latInRadians) * cos(lngInRadians)
var y_cart = v * cos(latInRadians) * sin(lngInRadians)
var z_cart = (v * (1 - NZGD1949e2)) * sin(latInRadians)
x_cart += differenceX
y_cart += differenceY
z_cart += differenceZ
print(x_cart)
print(y_cart)
print(z_cart)
let p = sqrt(pow(x_cart, 2) + pow(y_cart, 2))
let r = sqrt(pow(p, 2) + pow(z_cart, 2))
let mu = atan((z_cart / p) * ((1 - NZGD2000f) + ((NZGD2000e2 * NZGD2000a) / r)))
let num = (z_cart * (1 - NZGD2000f)) + (NZGD2000e2 * NZGD2000a * pow(sin(mu), 3))
let denom = (1 - NZGD2000f) * (p - (NZGD2000e2 * NZGD2000a * pow(cos(mu), 3)))
var lat = atan(num / denom)
var long = atan(y_cart / x_cart)
print(long)
lat = (180 / M_PI) * lat
long = 180 + (180 / M_PI) * long
return (latitude: lat, longitude: long)
}
}
}
Proj4j provides this functionality:
It is one of the few universal coordinate conversions libs.
http://trac.osgeo.org/proj4j/
Then you have to find the proj4 string that defines the new zeal and datum description:
Depending if you need th eold or the new grid, you need the new zealand datum shift grids,+
which are included in proj4.
As a first step find exactly the name of the datum that you need:
NZGD49 or NZGD2000 ?

Finding the point of intersection between a line and a cubic spline

I need a way to programmatically find the point of intersection between a line f(x), which originates from the origin, and a cubic spline defined by 4 points. The line is guaranteed to intersect the center segment of the spline, between X1 and X2.
I have tried a number of approaches but cannot seem to get the expected result. I suspect my problem lies somewhere in my handling of complex numbers.
Can anyone find the problem with my code, or else suggest a different approach?
private Vector2 CubicInterpolatedIntersection(float y0, float y1,
float y2, float y3, float lineSlope, lineYOffset)
{
// f(x) = lineSlope * x + lineYOffset
// g(x) = (a0 * x^3) + (a1 * x^2) + (a2 * x) + a3
// Calculate Catmull-Rom coefficients for g(x) equation as found
// in reference (1). These
double a0, a1, a2, a3;
a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;
a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3;
a2 = -0.5 * y0 + 0.5 * y2;
a3 = y1;
// To find POI, let 'g(x) - f(x) = 0'. Simplified equation is:
// (a0 * x^3) + (a1 * x^2) + ((a2 - lineSlope) * x)
// + (a3 - lineYOffset) = 0
// Put in standard form 'ax^3 + bx^2 + cx + d = 0'
double a, b, c, d;
a = a0;
b = a1;
c = a2 - lineSlope;
d = a3 - lineYOffset;
// Solve for roots using cubic equation as found in reference (2).
// x = {q + [q^2 + (r-p^2)^3]^(1/2)}^(1/3)
// + {q - [q^2 + (r-p^2)^3]^(1/2)}^(1/3) + p
// Where...
double p, q, r;
p = -b / (3 * a);
q = p * p * p + (b * c - 3 * a * d) / (6 * a * a);
r = c / (3 * a);
//Solve the equation starting from the center.
double x, x2;
x = r - p * p;
x = x * x * x + q * q;
// Here's where I'm not sure. The cubic equation contains a square
// root. So if x is negative at this point, then we need to proceed
// with complex numbers.
if (x >= 0)
{
x = Math.Sqrt(x);
x = CubicRoot(q + x) + CubicRoot(q - x) + p;
}
else
{
x = Math.Sqrt(Math.Abs(x));
// x now represents the imaginary component of
// a complex number 'a + b*i'
// We need to take the cubic root of 'q + x' and 'q - x'
// Since x is imaginary, we have two complex numbers in
// standard form. Therefore, we take the cubic root of
// the magnitude of these complex numbers
x = CubicRoot(Math.Sqrt(q * q + x * x)) +
CubicRoot(Math.Sqrt(q * q + -x * -x)) + p;
}
// At this point, x should hold the x-value of
// the point of intersection.
// Now use it to solve g(x).
x2 = x * x;
return new Vector2((float)Math.Abs(x),
(float)Math.Abs(a0 * x * x2 + a1 * x2 + a2 * x + a3));
}
References:
http://paulbourke.net/miscellaneous/interpolation/
http://www.math.vanderbilt.edu/~schectex/courses/cubic/
The code
if (x >= 0)
{ // One real root and two imaginaries.
x = Math.Sqrt(x);
x = CubicRoot(q + x) + CubicRoot(q - x) + p;
}
else
{ // Three real roots.
x = Math.Sqrt(Math.Abs(x));
x_1 = Math.Sign(q)*2*(q*q + x*x)^(1/6)*Math.Cos(Math.Atan(x/q)/3) + p;
x_2 = Math.Sign(q)*2*(q*q + x*x)^(1/6)*Math.Cos(Math.Atan(x/q)/3 + Math.PI*2/3) + p;
x_3 = Math.Sign(q)*2*(q*q + x*x)^(1/6)*Math.Cos(Math.Atan(x/q)/3 + Math.PI*4/3) + p;
}
You can compute ( )^(1/6) with Math.Pow( , 1/6) or your Math.Sqrt(CubicRoot( )) or Math.Sqrt(Cbrt( )). See the following thread on Microsoft forum.
Be careful with q = 0. ( Atan(x/0) = pi/2 radians. Cos(Atan(x/0)/3) = Sqrt(3)/2 )
For a quadratic equation there exists atleast 1 single real root.
Use this method for finding the roots http://en.wikipedia.org/wiki/Cubic_function#General_formula_of_roots

Categories

Resources