I have been tracking down a bug in my code, and I've found that it's because the Microsoft c# SqlGeography 2014 library returns a slightly different result for STDistance than my regular code for calculating the distance between points.
I wrote a small console exe to demonstrate the problem, but I can't figure out why I'm getting such a different result?
static void Main(string[] args) {
double earthRadius = 6378137; // meters => from both nad83 & wgs84
var a = new { lat = 43.68151632, lng = -79.61162263 };
var b = new { lat = 43.67575602, lng = -79.59586143 };
// sql geography lib
SqlGeographyBuilder sgb;
sgb = new SqlGeographyBuilder();
sgb.SetSrid(4326);
sgb.BeginGeography(OpenGisGeographyType.Point);
sgb.BeginFigure(a.lat, a.lng);
sgb.EndFigure();
sgb.EndGeography();
SqlGeography geoA = sgb.ConstructedGeography;
sgb = new SqlGeographyBuilder();
sgb.SetSrid(4326);
sgb.BeginGeography(OpenGisGeographyType.Point);
sgb.BeginFigure(b.lat, b.lng);
sgb.EndFigure();
sgb.EndGeography();
SqlGeography geoB = sgb.ConstructedGeography;
// distance cast from SqlDouble
double geoDistance = (double)geoA.STDistance(geoB);
// math!
double d2r = Math.PI / 180; // for converting degrees to radians
double lat1 = a.lat * d2r,
lat2 = b.lat * d2r,
lng1 = a.lng * d2r,
lng2 = b.lng * d2r,
dLat = lat2 - lat1,
dLng = lng2 - lng1,
sin_dLat_half = Math.Pow(Math.Sin(dLat / 2), 2),
sin_dLng_half = Math.Pow(Math.Sin(dLng / 2), 2),
distance = sin_dLat_half + Math.Cos(lat1) * Math.Cos(lat2) * sin_dLng_half;
// math distance
double mathDistance = (2 * Math.Atan2(Math.Sqrt(distance), Math.Sqrt(1 - distance))) * earthRadius;
// haversine
double sLat1 = Math.Sin(a.lat * d2r),
sLat2 = Math.Sin(b.lat * d2r),
cLat1 = Math.Cos(a.lat * d2r),
cLat2 = Math.Cos(b.lat * d2r),
cLon = Math.Cos((a.lng * d2r) - (b.lng * d2r)),
cosD = sLat1 * sLat2 + cLat1 * cLat2 * cLon,
d = Math.Acos(cosD);
// math distance
double methDistance = d * earthRadius;
// write the outputs
Console.WriteLine("geo distance:\t" + geoDistance); // 1422.99560435875
Console.WriteLine("math distance:\t" + mathDistance); // 1421.73656776243
Console.WriteLine("meth distance:\t" + methDistance); // 1421.73656680185
Console.WriteLine("geo vs math:\t" + (geoDistance - mathDistance)); // 1.25903659632445
Console.WriteLine("haversine vs math:\t" + (methDistance - methDistance)); // ~0.00000096058011
}
Is Microsoft using a different calculation method? Being off by over 1 meter when calculating distances less than 1.5Km is a huge discrepancy.
Ok, so after much digging I found the answer, and Microsoft is more correct.
Specifically, they are using Vincenty's formulae. Accuracy is within 0.5mm (not metre, half a millimetre) instead of 0.3% with Haversine formula.
The reason is that Haversine (used by me, Google, and Bing Maps too apparently) is fast, but relies on a spherical Earth instead of an ellipsoid. Microsoft uses the ellipsoid Earth to calculate distances instead of a sphere providing more accurate results.
I implemented Vincenty's method in c# like this and it's worked so far, but is no where near production ready.
This code was converted from a JavaScript implementation I found here: https://gist.github.com/mathiasbynens/354587