Query nearby locations in table in ASP.NET Core 2 and Entity Framework Core 2

元气小坏坏 提交于 2019-11-28 10:38:50

How about if you extract the data from the database that fits in the square around your circle of desired distance, and then fine tune on the client side to just the posts in the circle?

Given your initial data

var myLat = 25.05;
var myLon = -80.3;
var radiusInMile = 50;

You can compute the bounds of the square containing that circle

var minMilePerLat = 68.703;
var milePerLon = Math.Cos(myLat) * 69.172;
var minLat = myLat - radiusInMile / minMilePerLat;
var maxLat = myLat + radiusInMile / minMilePerLat;
var minLon = myLon - radiusInMile / milePerLon;
var maxLon = myLon + radiusInMile / milePerLon;

Then you can query the database for those posts inside the square, bring them to the client and keep just the ones in the circle

var data = _context.Posts
                   .Where(p => (minLat <= p.Lat && p.Lat <= maxLat) && (minLon <= p.Lng && p.Lng <= maxLon))
                   .AsEnumerable()
                   .Select(p => new { p, Dist = distanceInMiles(myLon, myLat, p.Lng, p.Lat) })
                   .Where(p => p.Dist <= radiusInMile);

Where the distanceInMiles function is defined as

public double ToRadians(double degrees) => degrees * Math.PI / 180.0;
public double distanceInMiles(double lon1d, double lat1d, double lon2d, double lat2d) {
    var lon1 = ToRadians(lon1d);
    var lat1 = ToRadians(lat1d);
    var lon2 = ToRadians(lon2d);
    var lat2 = ToRadians(lat2d);

    var deltaLon = lon2 - lon1;
    var c = Math.Acos(Math.Sin(lat1) * Math.Sin(lat2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(deltaLon));
    var earthRadius = 3958.76;
    var distInMiles = earthRadius * c;

    return distInMiles;
}

I am using the spherical law of cosines to compute the distance, which I assume is enough for this problem. If you need more accuracy, you could upgrade the formula to something more complicated like haversine or Vincenty.

They introduced spatial data support with version 2.2 of Entity Framework Core.

You can take a look at this StackOverflow answer.

There is a flaw with NetMage's answer.

For negative Longitude / Latitude, the maxLat / maxLon will be lower than your minLat/ minLon and your query will be looking outside the bounding box.

Here is one of the correct ways for calculating the bounding box based on his answer (this way, you ensure the minimum is always lower than the maximum)

var minMilePerLat = 68.703;
var milePerLon = Math.Cos(myLat) * 69.172;
var minLat = Math.min(myLat - radiusInMile / minMilePerLat, myLat + radiusInMile / minMilePerLat);
var maxLat = Math.max(myLat - radiusInMile / minMilePerLat, myLat + radiusInMile / minMilePerLat);
var minLon = Math.min(myLon - radiusInMile / milePerLon, myLon + radiusInMile / milePerLon);
var maxLon = Math.max(myLon - radiusInMile / milePerLon, myLon + radiusInMile / milePerLon);

You can also change things when you're querying based on min and max, but I figured this is simpler.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!