예제 #1
0
        public void GetSquareTest()
        {
            List <GpsLocation> inputLocations = new List <GpsLocation>()
            {
                new GpsLocation(0.0m, 0.0m),
                new GpsLocation(86.0m, -4.0m),
            };

            List <double> inputRadiuses = new List <double>()
            {
                20000,
                300000,
            };


            List <GpsSquare> expectedSquares = new List <GpsSquare>()
            {
                new GpsSquare(
                    new GpsLocation(0.18m, -0.18m),
                    new GpsLocation(0.18m, 0.18m),
                    new GpsLocation(-0.18m, -0.18m),
                    new GpsLocation(-0.18m, 0.18m)
                    ),

                new GpsSquare(
                    new GpsLocation(87.004444m, -68.258056m),
                    new GpsLocation(87.004444m, 60.258056m),
                    new GpsLocation(82.781389m, -25.999444m),
                    new GpsLocation(82.781389m, 17.999444m)
                    ),
            };

            for (int i = 0; i < inputLocations.Count; i++)
            {
                GpsSquare square = inputLocations[i].GetSquare(inputRadiuses[i]);
                Assert.True(((double)square.LeftTop.Latitude).ApproxEqual((double)expectedSquares[i].LeftTop.Latitude, 0.002));
                Assert.True(((double)square.LeftTop.Longitude).ApproxEqual((double)expectedSquares[i].LeftTop.Longitude, 0.002));
                Assert.True(((double)square.RightTop.Latitude).ApproxEqual((double)expectedSquares[i].RightTop.Latitude, 0.002));
                Assert.True(((double)square.RightTop.Longitude).ApproxEqual((double)expectedSquares[i].RightTop.Longitude, 0.002));
                Assert.True(((double)square.LeftBottom.Latitude).ApproxEqual((double)expectedSquares[i].LeftBottom.Latitude, 0.002));
                Assert.True(((double)square.LeftBottom.Longitude).ApproxEqual((double)expectedSquares[i].LeftBottom.Longitude, 0.002));
                Assert.True(((double)square.RightBottom.Latitude).ApproxEqual((double)expectedSquares[i].RightBottom.Latitude, 0.002));
                Assert.True(((double)square.RightBottom.Longitude).ApproxEqual((double)expectedSquares[i].RightBottom.Longitude, 0.002));
            }
        }
        /// <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);
        }