/// <summary> /// Creates basic filter expression for location. This filter is not precise filter, /// it will just filter out the majority of the identities that the caller is not interested it. /// </summary> /// <param name="LocationFilter">GPS location of the target area centre.</param> /// <param name="Radius">Target area radius in metres.</param> /// <returns>Filter expression for the database query.</returns> public static Expression <Func <Q, bool> > GetLocationFilterExpression <Q>(GpsLocation LocationFilter, uint Radius) where Q : IdentityBase { log.Trace("(LocationFilter:'{0:US}',Radius:{1})", LocationFilter, Radius); Expression <Func <Q, bool> > res = null; // There are several separated cases: // 1) Radius is very large - i.e. greater than 5,000 km. In this case, we do no filtering on this level at all. // 2) Distance of the target area centre to one of the poles is not larger than the radius. In this case, we calculate latitude and longitude ranges, // from which we then construct a target rectangle on the sphere that will represent our target area of interest. // 3) Distance of the target area centre to one of the poles is larger than the radius. In this case, we only set some of the boundaries, // but we will not have a full rectangle on the sphere. There are several subcases here described below. // 1) Radius is very large, no filtering. if (Radius > 5000000) { log.Trace("(-)[LARGE_RADIUS]"); return(res); } GpsLocation northPole = new GpsLocation(90.0m, 0.0m); GpsLocation southPole = new GpsLocation(-90.0m, 0.0m); double northPoleDistance = LocationFilter.DistanceTo(northPole); double southPoleDistance = LocationFilter.DistanceTo(southPole); double radius = (double)Radius; if (radius >= northPoleDistance) { // 2) Distance to pole is not larger than the radius: // a) North Pole // // In this case we go to the South from the centre to find the minimal latitude // and there will be no limit on longitude. GpsLocation minLatitudeLocation = LocationFilter.GoVector(GpsLocation.BearingSouth, radius); log.Trace("Radius >= North Pole Distance, min latitude is {0}.", minLatitudeLocation.Latitude); res = i => i.InitialLocationLatitude >= minLatitudeLocation.Latitude; } else if (radius >= southPoleDistance) { // 2) Distance to pole is not larger than the radius: // b) South Pole // // In this case we go to the North from the centre to find the maximal latitude. // and there will be no limit on longitude. GpsLocation maxLatitudeLocation = LocationFilter.GoVector(GpsLocation.BearingNorth, radius); log.Trace("Radius >= South Pole Distance, max latitude is {0}.", maxLatitudeLocation.Latitude); res = i => i.InitialLocationLatitude <= maxLatitudeLocation.Latitude; } else { // 3) Distance to poles is larger than the radius. // // In this case we create a rectangle on the sphere, in which the target identities are expected to be. // Using this square we will find latitude and longitude ranges for the database query. // Find a GPS square that contains the whole target circle area. GpsSquare square = LocationFilter.GetSquare((double)Radius); // Get latitude range - this is simple, left-top and right-top corners define the max latitude, // and left-bottom and right-bottom corners define the min latitude. decimal maxLatitude = square.MidTop.Latitude; decimal minLatitude = square.MidBottom.Latitude; log.Trace("GPS square is {0:US}, min latitude is {1}, max latitude is {2}.", square, minLatitude, maxLatitude); // Get longitude range - we have to examine all four corners here as it depends on which hemisphere they are // and there are several different cases due to possibility of crossing longitude 180. bool leftCornersSameSign = Math.Sign(square.LeftBottom.Longitude) == Math.Sign(square.LeftTop.Longitude); bool rightCornersSameSign = Math.Sign(square.RightBottom.Longitude) == Math.Sign(square.RightTop.Longitude); if (leftCornersSameSign && rightCornersSameSign && (Math.Sign(square.LeftTop.Longitude) == Math.Sign(square.RightTop.Longitude))) { // a) Square does not cross longitude 180. This case is simple, we find left most and right most longitudes // and our target profiles has to be between those two. decimal leftLongitude = Math.Min(square.LeftTop.Longitude, square.LeftBottom.Longitude); decimal rightLongitude = Math.Max(square.RightTop.Longitude, square.RightBottom.Longitude); log.Trace("Square does not cross lon 180. left longitude is {0}, right longitude is {1}.", leftLongitude, rightLongitude); res = i => (minLatitude <= i.InitialLocationLatitude) && (i.InitialLocationLatitude <= maxLatitude) && (leftLongitude <= i.InitialLocationLongitude) && (i.InitialLocationLongitude <= rightLongitude); } else { decimal leftLongitude; decimal rightLongitude; // b) Square crosses longitude 180. This is the more complicated case. One or two corners // have positive longitude and the remaining have negative longitude. if (leftCornersSameSign) { // Left top and left bottom corners are on the same side of longitude 180. // The left most corner is the one with smaller longitude value. leftLongitude = Math.Min(square.LeftTop.Longitude, square.LeftBottom.Longitude); } else { // Left top and left bottom corners are NOT on the same side of longitude 180. // The left most corner is the one with the positive value as the negative value is on the right of longitude 180. leftLongitude = square.LeftTop.Longitude > 0 ? square.LeftTop.Longitude : square.LeftBottom.Longitude; } if (rightCornersSameSign) { // Right top and right bottom corners are on the same side of longitude 180. // The right most corner is the one with higher longitude value. rightLongitude = Math.Max(square.RightTop.Longitude, square.RightBottom.Longitude); } else { // Right top and right bottom corners are NOT on the same side of longitude 180. // The right most corner is the one with the negative value as the positive value is on the left of longitude 180. rightLongitude = square.RightTop.Longitude < 0 ? square.RightTop.Longitude : square.RightBottom.Longitude; } // Note the OR operator instead of AND operator for longitude comparison. // This is because a longitude value can not be higher than e.g. 170 and lower than e.g. -150 at the same time. // The point is within the square if its longitude is 170 or more (up to 180) OR -150 or less (down to -180). log.Trace("Square crosses lon 180. left longitude is {0}, right longitude is {1}.", leftLongitude, rightLongitude); res = i => (minLatitude <= i.InitialLocationLatitude) && (i.InitialLocationLatitude <= maxLatitude) && ((leftLongitude <= i.InitialLocationLongitude) || (i.InitialLocationLongitude <= rightLongitude)); } } log.Trace("(-)"); return(res); }