34

I am working on a application where I need to get nearby location, my web service will receive 2 parameters (decimal longitude, decimal latitude )

I have a table where the locations are saved in database with longitude and latitude fields,

I want to retrieve the nearest locations.

Can anyone help?

Here is my code:

 var locations = from l in locations

     select l

Here are further details about this : i have a 2 fields (decimal(18, 2) null) 1 latitude, 2 longitude inside a database table,

and i have a method

public List<Locations>  GetLocation(decimal? Long, decimal? lat) 
{
var Loc = from l in Locations
  //// now here is how to get nearest location ? how to query?
  //// i have also tried Math.Abs(l.Lat - lat) its giving error about nullable decimal always hence i have seted decimal to nullable or converted to nullable
 //// also i have tried where (l.lat - Lat) * (l.lon - Long)  this is also giving error about can not convert decimal to bool
return Loc.ToList();
}
Mahmoud Gamal
  • 78,257
  • 17
  • 139
  • 164
Ashfaque Ali Solangi
  • 1,883
  • 3
  • 22
  • 34

6 Answers6

63

You could first convert the location data in database to System.Device.Location.GeoCoordinate, then use LINQ to find the nearest one.

var coord = new GeoCoordinate(latitude, longitude);
var nearest = locations.Select(x => new GeoCoordinate(x.Latitude, x.Longitude))
                       .OrderBy(x => x.GetDistanceTo(coord))
                       .First();
Fung
  • 3,508
  • 2
  • 26
  • 33
  • 4
    +1 Nice find! I had to add reference to `System.Device` though, but works great! nice code thanks! – JP Hellemons Jul 29 '13 at 11:35
  • Brilliant answer, this. +1. –  Feb 19 '14 at 14:13
  • 1
    Does locations need to be in memory? in other words: is it possible to do this in pure Linq-To-Entities (translated to SQL)? – sports May 26 '14 at 22:26
  • 4
    @sports No, I don't think the type `GeoCoordinate` and method `GetDistanceTo` is supported by the LINQ to Entities provider. I read about EF6 now support spatial type, [DbGeography.Distance](http://msdn.microsoft.com/en-us/library/system.data.entity.spatial.dbgeography.distance(v=vs.113).aspx) may be what you're looking for. – Fung May 27 '14 at 01:35
  • Is this query still functional as today? Im getting a this error: The LINQ expression 'First()' could not be translated and will be evaluated locally. @Fung – user2737948 Feb 09 '18 at 22:56
  • 2
    According to [this answer](https://stackoverflow.com/a/32130640/3200493), should be called `AsEnuemrable()` method before `OrderBy`, and it really works :D – Phi Jun 01 '18 at 05:36
  • 1
    Where do you find the System.Device library [dll] to install in VS 2017? – Joseph Poirier Apr 20 '19 at 20:54
  • Love this approach! – Pratik Bhattacharya Sep 23 '19 at 13:27
  • How do I get this work on .NE Core 3.1, we don't have System.Device.Location in .NET Core 3.1 – HaBo Jan 06 '20 at 19:46
  • 2
    ended up using System.Device.Location.Portable 1.0.0 – HaBo Jan 06 '20 at 19:53
  • is `Locations` a datatable ? And what reference I need to use `GetDistanceTo´ ? – GuidoG Feb 26 '21 at 07:04
  • This does not compile `Cannot convert lambda expression to type 'string' because it is not a delegate type` on the line `locations.Select(x => new GeoCoordinate(x.Latitude, x.Longitude))` – GuidoG Feb 26 '21 at 07:09
10

To elaborate on the comment by @Fung, if you are using Entity Framework / LINQ to Entities, if you try to use the GeoCoordinate.GetDistanceTo method in a LINQ query, you'll get a runtime NotSupportedException with the message:

LINQ to Entities does not recognize the method 'Double GetDistanceTo(System.Device.Location.GeoCoordinate)' method, and this method cannot be translated into a store expression.

With Entity Framework version 5 or 6, an alternative is to use the System.Data.Spatial.DbGeography class. For example:

DbGeography searchLocation = DbGeography.FromText(String.Format("POINT({0} {1})", longitude, latitude));

var nearbyLocations = 
    (from location in _context.Locations
     where  // (Additional filtering criteria here...)
     select new 
     {
         LocationID = location.ID,
         Address1 = location.Address1,
         City = location.City,
         State = location.State,
         Zip = location.Zip,
         Latitude = location.Latitude,
         Longitude = location.Longitude,
         Distance = searchLocation.Distance(
             DbGeography.FromText("POINT(" + location.Longitude + " " + location.Latitude + ")"))
     })
    .OrderBy(location => location.Distance)
    .ToList();

_context in this example is your previously-instantiated DbContext instance.

Although it's currently undocumented in MSDN, the units returned by the DbGeography.Distance method appear to be meters. See: System.Data.Spatial DbGeography.Distance units?

Community
  • 1
  • 1
Jon Schneider
  • 25,758
  • 23
  • 142
  • 170
  • 1
    The note about the distance type being in meters, instead of miles seems to be a really good bit of info. Thanks for noting it. – Joseph Poirier Apr 20 '19 at 18:09
5

Here is Solution

var constValue = 57.2957795130823D

var constValue2 = 3958.75586574D;

var searchWithin = 20;

double latitude = ConversionHelper.SafeConvertToDoubleCultureInd(Latitude, 0),
                    longitude = ConversionHelper.SafeConvertToDoubleCultureInd(Longitude, 0);
var loc = (from l in DB.locations
let temp = Math.Sin(Convert.ToDouble(l.Latitude) / constValue) *  Math.Sin(Convert.ToDouble(latitude) / constValue) +
                                 Math.Cos(Convert.ToDouble(l.Latitude) / constValue) *
                                 Math.Cos(Convert.ToDouble(latitude) / constValue) *
                                 Math.Cos((Convert.ToDouble(longitude) / constValue) - (Convert.ToDouble(l.Longitude) / constValue))
                             let calMiles = (constValue2 * Math.Acos(temp > 1 ? 1 : (temp < -1 ? -1 : temp)))
                             where (l.Latitude > 0 && l.Longitude > 0)
                             orderby calMiles

select new location
  {
     Name = l.name
  });
  return loc .ToList();
Ashfaque Ali Solangi
  • 1,883
  • 3
  • 22
  • 34
2

Do you have a valid range, outside of which the "hit" is not really relevant? If so, use

from l in locations where ((l.lat - point.lat) * (l.lat - point.lat)) + ((l.lng - point.lng) * (l.lng - point.lng)) < (range * range) select l

then find the hit with the smallest squared distance value within a loop of those results.

Kirk B.
  • 456
  • 2
  • 6
  • I don't think Euclidean distance comparison applies to longitude/latitude. – Fung Oct 11 '12 at 09:11
  • 1
    @sasjaq - True, but if we're talking about lat and lng points on this planet, and the distance being considered is short between two points such that the straight distance and the distance that wraps with the curvature of the Earth has a difference of say 1%, the flat approximation is good enough to perform the query for the closest well known point to a query point. – Kirk B. Oct 11 '12 at 10:13
  • how to get the Range here for 500 meters? – user1553857 Jun 21 '15 at 04:04
1
var objAllListing = (from listing in _listingWithLanguageRepository.GetAll().Where(z => z.IsActive == true)
                                     let distance = 12742 * SqlFunctions.Asin(SqlFunctions.SquareRoot(SqlFunctions.Sin(((SqlFunctions.Pi() / 180) * (listing.Listings.Latitude - sourceLatitude)) / 2) * SqlFunctions.Sin(((SqlFunctions.Pi() / 180) * (listing.Listings.Latitude - sourceLatitude)) / 2) +
                                                        SqlFunctions.Cos((SqlFunctions.Pi() / 180) * sourceLatitude) * SqlFunctions.Cos((SqlFunctions.Pi() / 180) * (listing.Listings.Latitude)) *
                                                        SqlFunctions.Sin(((SqlFunctions.Pi() / 180) * (listing.Listings.Longitude - sourceLongitude)) / 2) * SqlFunctions.Sin(((SqlFunctions.Pi() / 180) * (listing.Listings.Longitude - sourceLongitude)) / 2)))
                                     where distance <= input.Distance

                                     select new ListingFinalResult { ListingDetail = listing, Distance = distance }).ToList();//.Take(5).OrderBy(x => x.distance).ToList();
1

A netcore friendly solution. Refactor as needed.

public static System.Drawing.PointF getClosestPoint(System.Drawing.PointF[] points, System.Drawing.PointF query) {
    return points.OrderBy(x => distance(query, x)).First();
}

public static double distance(System.Drawing.PointF pt1, System.Drawing.PointF pt2) {
    return Math.Sqrt((pt2.Y - pt1.Y) * (pt2.Y - pt1.Y) + (pt2.X - pt1.X) * (pt2.X - pt1.X));
}
maeneak
  • 573
  • 6
  • 10