find nearest match to array of doubles - c#

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

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 an item in linear graph using LINQ

I have a list of positions denoted by X and Y. [{3,4}, {5,5}, {6,5},{7,8}]
public class Position {
public int X { get; set; }
public int Y { get; set; }
}
I need to find positions which will be present in the linear graph Starting
x=1 and y=0. [{1,0},{2,1},{3,2},{4,3}, ...].
I do not have the list which denotes the graph. I am looking for a way to find the positions based on starting point of the graph.
I can create the list of possibilities and find the matching positions. Before I do that I want to know is there a better approach?
The points are on a straight line. The formula for the points you mention is: y = x - 1. You can apply this formula in a where clause:
var x = new List<Position>();
...
var pointsOnLine = x.Where(p => p.Y == p.X -1);
You can do this also if you have another line or formula.

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.

Is there an algorithm to compute miles between coordinates?

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.

Sorting two separate Lists in the same order?

Just to lay down some restrictions first. Because of the environment I'm coding this in, I cannot create my own classes or methods. Just basic procedural code. It's in a CMS, and my code is executed inside a method itself.
Here's my question
On this page, I do a database query, loading all 700ish store locations. I then do a distance calculation based on the lat and lng values in the query string against the ones in the database to find the stores within 50 kilometers. Each one that is within that distance, I currently add to a List<DataRow>. I also take the distance result, round it to one decimal place, and store that into a simple list of type Double. What I want to do, is basically sort these so when i output the stores + distance value, it is sorted shortest to longest distance. I was thinking of switching the List<DataRow> and List<Double> combo to a Dictionary<Double, DataRow> and use sorting there, but there is of course certain cases where two places have the same distance value, so therefore it would not be unique. Is there another collection type I can use for this, or can you recommend a good way to sort the data?
Here's my code in case you need a visual:
The PRA object is basically the main object we use for working with the backend aspects of our CMS. In this case I'm using a short hand method to query the data, and check a couple variables in the request data. The rest is all built in .net stuff.
List<DataRow> locationsInRange = new List<DataRow>();
List<Double> distances = new List<Double>();
if(!String.IsNullOrEmpty(PRA.Request.QueryString["lat"]) && !String.IsNullOrEmpty(PRA.Request.QueryString["lng"])) {
Double earthRadius = 6371.0;
Double maxDistance = 50.0;
var locations = PRA.QueryDataRows("*", "", "{Location}", "showweb");
Double lat = Double.Parse(PRA.Request.QueryString["lat"]);
Double lng = Double.Parse(PRA.Request.QueryString["lng"]);
if(!String.IsNullOrEmpty(PRA.Request.QueryString["radius"])) {
Double temp = Double.Parse(PRA.Request.QueryString["radius"]);
if(temp > 0) {
maxDistance = temp;
}
}
bool firstError = true;
foreach(var l in locations) {
Double latSecond = 0.0;
Double lngSecond = 0.0;
try {
latSecond = Double.Parse(l["lat"].ToString());
lngSecond = Double.Parse(l["lng"].ToString());
}
catch(Exception ex) {
// do nothing. The lat and lng may not of been valid or not returned a result
}
Double dLat = Math.PI * ((lat - latSecond) / 180.0);
Double dLon = Math.PI * ((lng - lngSecond) / 180.0);
Double lat1 = Math.PI * (latSecond / 180.0);
Double lat2 = Math.PI * (lat / 180.0);
Double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Sin(dLon / 2) * Math.Sin(dLon / 2) * Math.Cos(lat1) * Math.Cos(lat2);
Double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
if(earthRadius * c <= (Double) maxDistance) {
locationsInRange.Add(l);
distances.Add(Math.Round(earthRadius * c, 1));
}
}
}
Use a list of pairs (data, distance), represented by the Tuple type...
var locations = new List<Tuple<DataRow, double>>();
locations.Add(Tuple.Create(row, distance));
locations.Sort((x, y) => x.Item2.CompareTo(y.Item2));
You can use the var keyword in the middle of the method to get the compiler to generate an anonymous class.
var combinedInstance = new {Row = new DataRow(), Distance = 0.0m};
You can use lambda notation in the middle of the method to get the compiler to generate new methods.
Func<Location, Location, decimal> getDistance = (loc1, loc2) =>
{
return 0.0m; //TODO implement
}
var combinedList = locationsInRange
.Select((row, i) => new {Row = row, Distance = distances[i]})
.OrderBy(x => x.Distance)
.ToList();
// now replace the original lists with newly ordered lists.
locationsInRange = combinedList.Select(x => x.Row).ToList();
distances = combinedList.Select(x => x.Distance).ToList();
If refactoring into a single datatype is not an option, you can sort the indices and work with a lookup table. This worked well enough for interfacing with some legacy code base.
Standalone example:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Some disjoint datasets
List<int> scores = new List<int>{3,5,2,8,4};
List<string> names = new List<string>{"three","five","two","eight","four"};
// Sequence of indices
List<int> indices = new List<int>(System.Linq.Enumerable.Range(0, scores.Count));
// Sort indices, based on corresponding score
indices.Sort(delegate(int a, int b) { return scores[a] - scores[b]; });
for(int i = 0; i < indices.Count; ++i)
{
// Use lookup table for indices
int index = indices[i];
Console.WriteLine(string.Format("Name: {0}, score: {1}", names[index], scores[index]));
}
}
}
The clean way would be to create an ICompareable class holding a DataRow and a Double, with the comparison defaulting to the comparison of the Double. You could then use a SortedList for a natural representation.
Since this clean way is impossible according to the (first overread by me) design of the app, we need a dirty way, and my suggestion is
Assuming you have SOME unique value in your shop list (location seems to be one)
create a string representation of sorts String.Format("{0:0000000000}/{1}",Math.Round(distance*10000),unique_criterium)
Store this in a SortedList
When iterating the SortedList, resolve the String back

Categories