Thursday 25 November 2010

Location Based Search with FindPagesByCriteria

This is a quick follow on from my last post on Location Based Search with EasySearch.

Proximity search using standard EPiServer functionality is a lot easier since we can do numeric range searches with FindPagesByCriteria. All we need is a helper class which does the geo-stuff for you.

First, the class:
public class GeoPoint
    {
        public enum DistanceMeasurement : int
        {
            Miles = 0,
            Kilometers = 1
        }

        public const double EarthRadiusInMiles = 3956.0;
        public const double EarthRadiusInKilometers = 6367.0;
        public static double ToRadian(double val) { return val * (Math.PI / 180); }
        public const double KILOMETERS_PER_DEGREE = 111.3171;

        private double _longitude;
        private double _latitude;

        public GeoPoint(double latitude, double longitude)
        {
            _longitude = longitude;
            _latitude = latitude;
        }

        public void GetProximityBoundingBox(double distanceInKm, ref GeoPoint topLeft, ref GeoPoint bottomRight)
        {
            double spreadOnLongitude = distanceInKm / GeoPoint.CalculateKilometersPerLongitudeDegree(this.Latitude);
            double spreadOnLatitude = distanceInKm / GeoPoint.KILOMETERS_PER_DEGREE;

            // Get top left bounding box point
            topLeft = new GeoPoint(this.Latitude - spreadOnLatitude, this.Longitude - spreadOnLongitude);

            // Get bottom right bounding box point
            bottomRight = new GeoPoint(this.Latitude + spreadOnLatitude, this.Longitude + spreadOnLongitude);
        }

        public double Longitude
        {
            get { return _longitude; }
            set { _longitude = value; }
        }

        public double Latitude
        {
            get { return _latitude; }
            set { _latitude = value; }
        }

        public static double DiffRadian(double val1, double val2) { return ToRadian(val2) - ToRadian(val1); }

        public static double CalcDistance(double lat1, double lng1, double lat2, double lng2, DistanceMeasurement m)
        {
            double radius = EarthRadiusInMiles;
            if (m == DistanceMeasurement.Kilometers) { radius = EarthRadiusInKilometers; }
            return radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt((Math.Pow(Math.Sin((DiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.Cos(ToRadian(lat1)) * Math.Cos(ToRadian(lat2)) * Math.Pow(Math.Sin((DiffRadian(lng1, lng2)) / 2.0), 2.0)))));
        }


        public double DistanceFrom(GeoPoint anotherPoint)
        {
            return CalcDistance(anotherPoint.Latitude, anotherPoint.Longitude, 
                this.Latitude, this.Longitude, DistanceMeasurement.Kilometers);
        }

        public static double CalculateKilometersPerLongitudeDegree(double latitude)
        {
            return KILOMETERS_PER_DEGREE;
        }

    }
 
Next, the search code.
GeoPoint origin = new GeoPoint(centerLatitude, centerLongitude);

            // Get top left bounding box point
            GeoPoint topLeft = null;

            // Get bottom right bounding box point
            GeoPoint bottomRight = null;

            origin.GetProximityBoundingBox(distanceKms, ref topLeft, ref bottomRight);

            int pageTypeID = PageTypeResolver.Instance.GetPageTypeID(typeof(Club)).Value;

            // Create criteria collection
            var criterias = new PropertyCriteriaCollection                    
            {                        
                // Find pages of a specific page type                        
                new PropertyCriteria()                            
                {                                
                    Name = "PageTypeID",                                
                    Condition = CompareCondition.Equal,                                
                    Required = true,                                
                    Type = PropertyDataType.PageType,                                
                    Value = pageTypeID.ToString() // Page type ID = 5                            
                },                        
                // Greater than Longitude                      
                new PropertyCriteria()                            
                {                                
                    Name = "PlaceLongitude",                                
                    Condition = CompareCondition.GreaterThan,                                
                    Required = true,                                
                    Type = PropertyDataType.FloatNumber,                                
                    Value = topLeft.Longitude.ToString() // Long - Top Left                            
                },                    
                // Less than Longitude                       
                new PropertyCriteria()                            
                {                                
                    Name = "PlaceLongitude",                                
                    Condition = CompareCondition.LessThan,                                
                    Required = true,                                
                    Type = PropertyDataType.FloatNumber,                                
                    Value = bottomRight.Longitude.ToString() // Long - Bottom Right                            
                },
                // Greater than Latitude                       
                new PropertyCriteria()                            
                {                                
                    Name = "PlaceLatitude",                                
                    Condition = CompareCondition.GreaterThan,                                
                    Required = true,                                
                    Type = PropertyDataType.FloatNumber,                                
                    Value = topLeft.Latitude.ToString() // Latitude - Top Left                            
                },
                // Less than Latitude                       
                new PropertyCriteria()                            
                {                                
                    Name = "PlaceLatitude",                                
                    Condition = CompareCondition.LessThan,                                
                    Required = true,                                
                    Type = PropertyDataType.FloatNumber,                                
                    Value = bottomRight.Latitude.ToString() // Latitude - Bottom Right                            
                }
            };

            // Search pages under the start page
            var pages = DataFactory.Instance.FindPagesWithCriteria(new PageReference(666), 
                criterias);


And finally, the sort by distance using a custom sort implementation.
pages.Sort(new DistanceFromOriginSort(origin));
    class DistanceFromOriginSort : IComparer
    {
        private GeoPoint _origin;

        public DistanceFromOriginSort(GeoPoint origin)
        {
            _origin = origin;
        }

        public int Compare(PageData x, PageData y)
        {
              double xLat = Convert.ToDouble(x.Property["PlaceLatitude"].Value);
            double xLon = Convert.ToDouble(x.Property["PlaceLongitude"].Value);

            double yLat = Convert.ToDouble(y.Property["PlaceLatitude"].Value);
            double yLon = Convert.ToDouble(y.Property["PlaceLongitude"].Value);

            GeoPoint geoPointX = new GeoPoint(xLat, xLon);
            GeoPoint geoPointY = new GeoPoint(yLat, yLon);

            double distance1 = _origin.DistanceFrom(geoPointX);
            double distance2 = _origin.DistanceFrom(geoPointY);

            if (distance1 == distance2)
            {
                return 0;
            }

            if (distance1 < distance2)
            {
                return -1;
            }

            return 1;
        }
    }
Proximity Search done.

No comments:

Post a Comment