Is there an algorithm to compute miles between coordinates? - c#

I want to be able to display a Bing map in a Windows 8/Store app with an array of pushpins/waypoints at a zoom setting that will show every location, but no more than that - IOW, I want as much detail as possible while still showing all of the locations/coordinates.
I have this pseudocode:
public static int GetMapZoomSettingForCoordinates(List<String> coordinatesList)
{
string furthestNorth = GetFurthestNorth(coordinatesList);
string furthestSouth = GetFurthestSouth(coordinatesList);
string furthestEast = GetFurthestEast(coordinatesList);
string furthestWest = GetFurthestWest(coordinatesList);
int milesBetweenNorthAndSouthExtremes = GetMilesBetween(furthestNorth, furthestSouth);
int milesBetweenEastAndWestExtremes = GetMilesBetween(furthestEast, furthestWest);
int greaterCardinalDistance = Math.Max(milesBetweenNorthAndSouthExtremes, milesBetweenEastAndWestExtremes);
return GetZoomSettingForDistance(greaterCardinalDistance);
}
...but the "sticking point" (the hard part) are the "milesBetween" functions. Is there an existing algorithm for computing the miles between two coordinates?
I do realize this is a U.S.-centric bunch of code for now (miles vs. kilometers); that is, for now, as designed.
UPDATE
This is my new pseudocode (actual compiling code, but untested):
public static int GetMapZoomSettingForCoordinates(List<string> coordinatePairsList)
{
List<double> LatsList = new List<double>();
List<double> LongsList = new List<double>();
List<string> tempList = new List<string>();
foreach (string s in coordinatePairsList)
{
tempList.AddRange(s.Split(';'));
double dLat;
double.TryParse(tempList[0], out dLat);
double dLong;
double.TryParse(tempList[0], out dLong);
LatsList.Add(dLat);
LongsList.Add(dLong);
tempList.Clear();
}
double furthestNorth = GetFurthestNorth(LatsList);
double furthestSouth = GetFurthestSouth(LatsList);
double furthestEast = GetFurthestEast(LongsList);
double furthestWest = GetFurthestWest(LongsList);
int milesToDisplay =
HaversineInMiles(furthestWest, furthestNorth, furthestEast, furthestSouth);
return GetZoomSettingForDistance(milesToDisplay);
}
private static double GetFurthestNorth(List<double> longitudesList)
{
double northernmostVal = 0.0;
foreach (double d in longitudesList)
{
if (d > northernmostVal)
{
northernmostVal = d;
}
}
return northernmostVal;
}
...I still don't know what GetZoomSettingForDistance() should be/do, though...
UPDATE 2
This is "more better":
public static int GetMapZoomSettingForCoordinates(List<Tuple<double, double>> coordinatePairsList)
{
var LatsList = new List<double>();
var LongsList = new List<double>();
foreach (Tuple<double,double> tupDub in coordinatePairsList)
{
LatsList.Add(tupDub.Item1);
LongsList.Add(tupDub.Item2);
}
double furthestNorth = GetFurthestNorth(LongsList);
double furthestSouth = GetFurthestSouth(LongsList);
double furthestEast = GetFurthestEast(LatsList);
double furthestWest = GetFurthestWest(LatsList);
int milesToDisplay =
HaversineInMiles(furthestWest, furthestNorth, furthestEast, furthestSouth);
return GetZoomSettingForDistance(milesToDisplay);
}
UPDATE 3
I realized that my logic was backwards, or wrong, at any rate, regarding meridians of longitude and parallels of latitude. While it's true that meridians of longitude are the vertical lines ("drawn" North-to-South or vice versa) and that parallels of latitude are the horizontal lines ("drawn" East-to-West), points along those line represent the North-South location based on parallels of latitude, and represent East-West locations based on meridians of longitude. This seemed backwards in my mind until I visualized the lines spinning across (longitude) and up and over (latitude) the earth, rather than simply circling the earth like the rings of Saturn do; what also helped get my perception right was reminding myself that it is the values of the meridians of longitude that determine in which time zone one finds themselves. SO, the code above should change to pass latitudes to determine furthest North and furthest South, and conversely pass longitudes to determine furthest East and furthest West.

You can use the Haversine formula to compute the distance along the surface of a sphere.
Here's a C++ function to compute the distance using the Earth as the size of the sphere. It would easily be convertible to C#.
Note that the formula can be simplified if you want to just find the distance either latitudinally or longitudinally (which it sounds like you are trying to do).

To get the straight line distance you use the Pythagorean Theorem to find the hypotenuse.
d = ((delta x)^2 + (delta y)^2)^.5
Basically square both the changes in the x direction and the y direction, add them, then take the square root.
in your pseudo code it looks like you could have many points and you want to find a maximum distance that should encompass all of them, which makes sense if you are trying to figure out a scale for the zoom of the map. The same formula should work, just use milesBetweenEastAndWestExtremes for delta x, and milesBetweenNorthAndSouthExtremes for delta y. You may opt to add a fixed amount to this just to make sure you don't have points right on the very edge of the map.

Related

Google map show different distance between two dastination

I am creating a window form application, which calculate the distance between two locations by their Lat-Long using google API. This works fine, But i notice that some time api give different result (distance) for same locations.
Example :-
Location-A => Mumbai (19.075983 , 72.877655)
Location-B => Delhi (28.704060 , 77.102493)
When i calculate the distance from location-A To location-B
decimal Distance = EmployeeTaskWorkLogManager.getDistance(string.Concat(19.075983, ", ", 72.877655), string.Concat(28.704060, ", ", 77.102493));
This shows
Distance = 1423.297
And when i calculate the distance from location-B To location-A
decimal Distance = EmployeeTaskWorkLogManager.getDistance(string.Concat(28.704060, ", ", 77.102493), string.Concat(19.075983, ", ", 72.877655));
This shows
Distance = 1415.239
This the code that i am using to calculate distance between two locations,
public static decimal getDistance(string origin, string destination)
{
decimal num;
Thread.Sleep(1000);
decimal num1 = new decimal(0);
string[] strArrays = new string[] { "https://maps.googleapis.com/maps/api/distancematrix/json?origins=", origin, "&destinations=", destination, "&key=****" };
string str = string.Concat(strArrays);
JObject jObjects = JObject.Parse(EmployeeTaskWorkLogManager.fileGetContents(str));
try
{
num1 = (decimal)jObjects.SelectToken("rows[0].elements[0].distance.value");
num = num1 / new decimal(1000);
}
catch
{
num = num1;
}
return num;
}
I Checked each line of my code, but unable to find why this difference has occurs.
The Google Maps Distance Matrix API does not calculate a straight-line or big-circle distance between two points. Instead it returns the distance according to the mode of travel (which you haven't specified in your API call, so it defaults to driving).
Think about it: there is no way that you will travel the exact distance in both directions, and the difference will become more marked the longer the journey and the more route changes it requires. It will depend on freeway off ramps (not available at particular intersections in both directions), one way streets, etc etc.
Therefore, the result is as expected.
Source: Google Maps Distance Matrix API

Finding nearest coordinate to other coordinate

Given an array of geo coordinates and another geo coordinate, I would like to find the nearest coordinate(s) to it.
For example, given the array:
lat long
52.525782 13.316927
52.526409 13.319083
52.525678 13.320317
And the point: 52.525730, 13.314556, then the first point 52.525782, 13.316927 will be returned, as it is the closest one.
Is the only way of acheiving it is looping through all the array and finding the distance between the points? What happens if the array contains too much coordinates?
You can try it using LINQ, but the inner workings of LINQ would still loop over your collection. For example:
//Your list with coordinates
List<GeoCoordinate> coords = new List<GeoCoordinate>();
//The coord you want to compare
GeoCoordinate crd = new GeoCoordinate();
//Sorts the list based on the proximity of the coords to crd
var sortedCoords = coords.OrderBy(x => x.GetDistanceTo(crd)).ToList();
I know it doesn't use an array, but I find using lists is easier.
I think that should work, let me know if it does!
struct coord
{
public double lat;
public double lon;
}
public void Main(coord coord)
{
var coords = new[]{ new coord(){lat=1, lon=1} };
var closest = coords.Min(p => Math.Abs(p.lat - coord.lat) + Math.Abs(p.lon - coord.lon));
}

Searching XML for coordinates using LINQ

I am not looking for any code, just advice on a particular aspect of a project.
I have an XML file that contains coordinates and the building name and I want to be able to capture the coordinates of where the person is (I'm going to be creating a mobile application so this is ok).
Is the senario above possible using a LINQ statement in C#? If so, is it possible to get a close match? i.e. if the person isn't exactly in the coordinates, show him/her the nearest match.
I'm NOT specifically looking for any code just any hints, tips, or advanced tutorials on LINQ would be helpful.
Thanks
You can use System.Device.Location.GeoCoordinate class for this
List<GeoCoordinate> listTakenFromXml = ......
double lat = ......
double lon = ........
var nearest = new GeoCoordinate(lat, lon).NearestPoint(listTakenFromXml);
public static class SoExtensions
{
public static GeoCoordinate NearestPoint(this GeoCoordinate loc, IEnumerable<GeoCoordinate> coords)
{
GeoCoordinate minLoc = null;
double minDist = double.MaxValue;
foreach (var c in coords)
{
var dist = c.GetDistanceTo(loc);
if ( dist < minDist)
{
minDist = dist;
minLoc = c;
}
}
return minLoc;
}
}
See Calculate distance, bearing and more between Latitude/Longitude points and use the method which bests suits your situation. Once you have the calculation if you have a set of points in a list, then Linq to Object's extensions can help you with determining your logic as needed.

find nearest match to array of doubles

Given the code below, how do I compare a List of objects's values with a test value?
I'm building a geolocation application. I'll be passing in longitude and latitude and would like to have the service answer back with the location closest to those values.
I started down the path of converting to a string, and formatting the values down to two decimal places, but that seemed a bit too ghetto, and I'm looking for a more elegant solution.
public class Location : IEnumerable
{
public string label { get; set; }
public double lat { get; set; }
public double lon { get; set; }
//Implement IEnumerable
public IEnumerator GetEnumerator()
{
return (IEnumerator)this;
}
}
[HandleError]
public class HomeController : Controller
{
private List<Location> myList = new List<Location>
{
new Location {
label="Atlanta Midtown",
lon=33.657674,
lat=-84.423130},
new Location {
label="Atlanta Airport",
lon=33.794151,
lat=-84.387228},
new Location {
label="Stamford, CT",
lon=41.053758,
lat=-73.530979}, ...
}
public static int Main(String[] args)
{
string inLat = "-80.987654";
double dblInLat = double.Parse(inLat);
// here's where I would like to find the closest location to the inLat
// once I figure out this, I'll implement the Longitude, and I'll be set
}
You're going to want to use the correct distance formula for this if you don't want to end up with weird results:
double CalculateDistance(double lat1, double lon1, double lat2, double lon2)
{
const double R = 6371;
return Math.Acos(
Math.Sin(lat1) * Math.Sin(lat2) +
Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(lon2 - lon1)) * R;
}
I hope that's the right formula, my math might be a little rusty here. All of the parameters need to be in rads, so if you're taking inputs in degrees, write a utility method as well:
double DegToRad(double deg)
{
return deg * Math.PI / 180.0;
}
Anyway, after that, you can figure out the shortest distance as:
Location GetClosestLocation(Location origin)
{
double olatr = DegToRad(origin.Lat);
double olonr = DegToRad(origin.Lon);
return
(from l in locations
let latr = DegToRad(l.Lat)
let lonr = DegToRad(l.Lon)
orderby CalculateDistance(latr, lonr, olatr, olonr))
.FirstOrDefault();
}
This isn't technically the most performant solution, since it has to do a sort, but there's no nice-looking Linq extension method to do min with a projection. If you want that, you'll have to write your own foreach loop:
Location GetClosestLocation(Location origin)
{
double olatr = DegToRad(origin.Lat);
double olonr = DegToRad(origin.Lon);
Location closest = null;
double minDistance = double.MaxValue;
foreach (Location l in locations)
{
double latr = DegToRad(l.Lat);
double lonr = DegToRad(l.Lon);
double dist = CalculateDistance(latr, lonr, olatr, olonr));
if (dist < minDistance)
{
minDistance = dist;
closest = l;
}
}
return closest;
}
How accurate do you need to be? It's called the Great Circle Distance.
See for example http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
I found this which someone created that calculates distances between two distances across the globe using one of several different methods. I had to convert the .NET project to the updated VS2008, but that seemed to work fine. Then I just added this project to my solution and made a reference to it.
My code then became:
string inLat = "-80.987654";
string inLon = "33.521478";
var miles = GetNearestLocation(inLat, inLon);
public double GetNearestLocation(string lat, string lon)
{
double dblInLat = double.Parse(lat);
double dblInLon = double.Parse(lon);
// instantiate the calculator
GeodeticCalculator geoCalc = new GeodeticCalculator();
// select a reference elllipsoid
Ellipsoid reference = Ellipsoid.WGS84;
// set user's current coordinates
GlobalCoordinates userLocation;
userLocation = new GlobalCoordinates(
new Angle(dblInLon), new Angle(dblInLat)
);
// set example coordinates- when fully fleshed out,
// this would be passed into this method
GlobalCoordinates testLocation;
testLocation= new GlobalCoordinates(
new Angle(41.88253), new Angle(-87.624207) // lon, then lat
);
// calculate the geodetic curve
GeodeticCurve geoCurve = geoCalc.CalculateGeodeticCurve(reference, userLocation, testLocation);
double ellipseKilometers = geoCurve.EllipsoidalDistance / 1000.0;
double ellipseMiles = ellipseKilometers * 0.621371192;
/*
Console.WriteLine("2-D path from input location to test location using WGS84");
Console.WriteLine(" Ellipsoidal Distance: {0:0.00} kilometers ({1:0.00} miles)", ellipseKilometers, ellipseMiles);
Console.WriteLine(" Azimuth: {0:0.00} degrees", geoCurve.Azimuth.Degrees);
Console.WriteLine(" Reverse Azimuth: {0:0.00} degrees", geoCurve.ReverseAzimuth.Degrees);
*/
return ellipseMiles;
}
I think the easiest would be to do the following. But not most performant :)
Iterate through the list and calculate the distance between each location and your reference location. At each step, check to see if this is the shortest distance you have seen so far and store that. Once you get the the end of the list, you will have the closest location in your stored variable.
If you are talking about a very large number of locations and you plan to do many spacial queries of this nature you might consider setting up a quadtree index on the data.
Here is a link I found after doing a quick 'Bing', it should help with the distance calculation, I hope. Please refer this Link:
http://www.delphiforfun.org/Programs/Math_Topics/Lat-Long%20Distance.htm

How to get levels for Fry Graph readability formula?

I'm working in an application (C#) that applies some readability formulas to a text, like Gunning-Fog, Precise SMOG, Flesh-Kincaid.
Now, I need to implement the Fry-based Grade formula in my program, I understand the formula's logic, pretty much you take 3 100-words samples and calculate the average on sentences per 100-words and syllables per 100-words, and then, you use a graph to plot the values.
Here is a more detailed explanation on how this formula works.
I already have the averages, but I have no idea on how can I tell my program to "go check the graph and plot the values and give me a level." I don't have to show the graph to the user, I only have to show him the level.
I was thinking that maybe I can have all the values in memory, divided into levels, for example:
Level 1: values whose sentence average are between 10.0 and 25+, and whose syllables average are between 108 and 132.
Level 2: values whose sentence average are between 7.7 and 10.0, and .... so on
But the problem is that so far, the only place in which I have found the values that define a level, are in the graph itself, and they aren't too much accurate, so if I apply the approach commented above, trying to take the values from the graph, my level estimations would be too much imprecise, thus, the Fry-based Grade will not be accurate.
So, maybe any of you knows about some place where I can find exact values for the different levels of the Fry-based Grade, or maybe any of you can help me think in a way to workaround this.
Thanks
Well, I'm not sure about this being the most efficient solution, neither the best one, but at least it does the job.
I gave up to the idea of having like a math formula to get the levels, maybe there is such a formula, but I couldn't find it.
So I took the Fry's graph, with all the levels, and I painted each level of a different color, them I loaded the image on my program using:
Bitmap image = new Bitmap(#"C:\FryGraph.png");
image.GetPixel(int x, int y);
As you can see, after loading the image I use the GetPixel method to get the color at the specified coordinates. I had to do some conversion, to get the equivalent pixels for a given value on the graph, since the scale of the graph is not the equivalent to the pixels of the image.
In the end, I compare the color returned by GetPixel to see which was the Fry readability level of the text.
I hope this may be of any help for someone who faces the same problem.
Cheers.
You simply need to determine the formula for the graph. That is, a formula that accepts the number of sentences and number of syllables, and returns the level.
If you can't find the formula, you can determine it yourself. Estimate the linear equation for each of the lines on the graph. Also estimate the 'out-of-bounds' areas in the 'long words' and 'long sentences' areas.
Now for each point, just determine the region in which it resides; which lines it is above and which lines it is below. This is fairly simple algebra, unfortunately this is the best link I can find to describe how to do that.
I have made a first pass at solving this that I thought I would share in case someone else is looking sometime in the future. I built on the answer above and created a generic list of linear equations that one can use to determine an approximate grade level. First had to correct the values to make it more linear. This does not take into account the invalid areas, but I may revisit that.
The equation class:
public class GradeLineEquation
{
// using form y = mx+b
// or y=Slope(x)=yIntercept
public int GradeLevel { get; set; }
public float Slope { get; set; }
public float yIntercept { get; set; }
public float GetYGivenX(float x)
{
float result = 0;
result = (Slope * x) + yIntercept;
return result;
}
public GradeLineEquation(int gradelevel,float slope,float yintercept)
{
this.GradeLevel = gradelevel;
this.Slope = slope;
this.yIntercept = yintercept;
}
}
Here is the FryCalculator:
public class FryCalculator
{
//this class normalizes the plot on the Fry readability graph the same way a person would, by choosing points on the graph based on values even though
//the y-axis is non-linear and neither axis starts at 0. Just picking a relative point on each axis to plot the intercept of the zero and infinite scope lines
private List<GradeLineEquation> linedefs = new List<GradeLineEquation>();
public FryCalculator()
{
LoadLevelEquations();
}
private void LoadLevelEquations()
{
// load the estimated linear equations for each line with the
// grade level, Slope, and y-intercept
linedefs.Add(new NLPTest.GradeLineEquation(1, (float)0.5, (float)22.5));
linedefs.Add(new NLPTest.GradeLineEquation(2, (float)0.5, (float)20.5));
linedefs.Add(new NLPTest.GradeLineEquation(3, (float)0.6, (float)17.4));
linedefs.Add(new NLPTest.GradeLineEquation(4, (float)0.6, (float)15.4));
linedefs.Add(new NLPTest.GradeLineEquation(5, (float)0.625, (float)13.125));
linedefs.Add(new NLPTest.GradeLineEquation(6, (float)0.833, (float)7.333));
linedefs.Add(new NLPTest.GradeLineEquation(7, (float)1.05, (float)-1.15));
linedefs.Add(new NLPTest.GradeLineEquation(8, (float)1.25, (float)-8.75));
linedefs.Add(new NLPTest.GradeLineEquation(9, (float)1.75, (float)-24.25));
linedefs.Add(new NLPTest.GradeLineEquation(10, (float)2, (float)-35));
linedefs.Add(new NLPTest.GradeLineEquation(11, (float)2, (float)-40));
linedefs.Add(new NLPTest.GradeLineEquation(12, (float)2.5, (float)-58.5));
linedefs.Add(new NLPTest.GradeLineEquation(13, (float)3.5, (float)-93));
linedefs.Add(new NLPTest.GradeLineEquation(14, (float)5.5, (float)-163));
}
public int GetGradeLevel(float avgSylls,float avgSentences)
{
// first normalize the values given to cartesion positions on the graph
float x = NormalizeX(avgSylls);
float y = NormalizeY(avgSentences);
// given x find the first grade level equation that produces a lower y at that x
return linedefs.Find(a => a.GetYGivenX(x) < y).GradeLevel;
}
private float NormalizeY(float avgSentenceCount)
{
float result = 0;
int lower = -1;
int upper = -1;
// load the list of y axis line intervalse
List<double> intervals = new List<double> {2.0, 2.5, 3.0, 3.3, 3.5, 3.6, 3.7, 3.8, 4.0, 4.2, 4.3, 4.5, 4.8, 5.0, 5.2, 5.6, 5.9, 6.3, 6.7, 7.1, 7.7, 8.3, 9.1, 10.0, 11.1, 12.5, 14.3, 16.7, 20.0, 25.0 };
// find the first line lower or equal to the number we have
lower = intervals.FindLastIndex(a => ((double)avgSentenceCount) >= a);
// if we are not over the top or on the line grab the next higher line value
if(lower > -1 && lower < intervals.Count-1 && ((float) intervals[lower] != avgSentenceCount))
upper = lower + 1;
// set the integer portion of the respons
result = (float)lower;
// if we have an upper limit calculate the percentage above the lower line (to two decimal places) and add it to the result
if(upper != -1)
result += (float)Math.Round((((avgSentenceCount - intervals[lower])/(intervals[upper] - intervals[lower]))),2);
return result;
}
private float NormalizeX(float avgSyllableCount)
{
// the x axis is MUCH simpler. Subtract 108 and divide by 2 to get the x position relative to a 0 origin.
float result = (avgSyllableCount - 108) / 2;
return result;
}
}

Categories