I am trying to grab all of the products in my database if they are within a certain distance of the user. For each row in my database, I want to calculate the distance between the user and the product, and return the product if and only if that value is less than the maximum distance allowed from the user.
const double PIx = Math.PI;
const double earth = 6378000.16;
const double distanceFromUser = 50; // Change this later.
double dLat = Convert.ToDouble(lat + (distanceFromUser / earth) * (180 / PIx));
double dLng = Convert.ToDouble(lng + (distanceFromUser / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));
var maximumDistance = _calculator.DistanceBetweenPlaces(lat, lng, dLat, dLng);
var paginatedProducts = products.Where(x => _calculator.DistanceBetweenPlaces(Convert.ToDouble(lng), Convert.ToDouble(lat), x.Longitude, x.Latitude) < maximumDistance )
The idea is that I:
Have the user's location (lat, lng)
Have the location of the product in a row in my table
(x.Longitude, x.Latitude)
I use
distanceBetweenPlaces
to determine if the distance between the user's location and the product's location is less than themaximumDistance
allowed.
Right now, the LINQ query says
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
How else can I write this?
Edit:
Here's the error if I try to turn it into a list:
System.InvalidOperationException: The LINQ expression 'DbSet<Product>
.Where(p => ___calculator_0.DistanceBetweenPlaces(
lon1: __ToDouble_1,
lat1: __ToDouble_2,
lon2: p.Longitude,
lat2: p.Latitude) < __calc_3)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
edit 2:
Here's the code for DistanceBetweenPlaces
and the utility function it uses:
/// <summary>
/// cos(d) = sin(φА)·sin(φB) + cos(φА)·cos(φB)·cos(λА − λB), where φА, φB are latitudes and λА, λB are longitudes Distance = d * R
/// </summary>
/// <param name="lon1"></param>
/// <param name="lat1"></param>
/// <param name="lon2"></param>
/// <param name="lat2"></param>
/// <returns></returns>
public double DistanceBetweenPlaces(double lon1, double lat1, double lon2, double lat2)
{
double R = 6371; // km
double sLat1 = Math.Sin(ConvertToRadians(lat1));
double sLat2 = Math.Sin(ConvertToRadians(lat2));
double cLat1 = Math.Cos(ConvertToRadians(lat1));
double cLat2 = Math.Cos(ConvertToRadians(lat2));
double cLon = Math.Cos(ConvertToRadians(lon1) - ConvertToRadians(lon2));
double cosD = sLat1 * sLat2 + cLat1 * cLat2 * cLon;
double d = Math.Acos(cosD);
double dist = R * d;
return dist;
}
const double PIx = Math.PI;
/// <summary>
/// Convert degrees to Radians
/// </summary>
/// <param name="x">Degrees</param>
/// <returns>The equivalent in radians</returns>
public double ConvertToRadians(double x)
{
return x * PIx / 180;
}
I ended up taking another approach for anyone this might help later -
const double PIx = Math.PI;
const double earth = 6378000.16;
const double distanceFromUser = 80467.2;
const double distanceFromUserHalved = distanceFromUser / 2; // Change this later.
// This new approach adds half the distance of the radius to find a latitude upper and lower bound, and a longitude upper and lower bound.
var TopLatBound = Convert.ToDouble(lat + (distanceFromUserHalved / earth) * (180 / PIx));
var BottomLatBound = Convert.ToDouble(lat - (distanceFromUserHalved / earth) * (180 / PIx));
var topLngBound = Convert.ToDouble(lng + (distanceFromUserHalved / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));
var bottomLngBound = Convert.ToDouble(lng - (distanceFromUserHalved / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));
var paginatedProducts = await PaginatedList<Product>.CreateAsync(products.Where(x => (x.Latitude < TopLatBound && x.Latitude > BottomLatBound) && (x.Longitude < topLngBound && x.Longitude> bottomLngBound) && x.IsArchived == false && x.Decay > 0)
.OrderByDescending(x => x.DateTime), offset ?? 1, pageSize).ConfigureAwait(false);