Working with DbGeography, Points and Polygons in .NET

One very handy feature of the System.Data.Spatial namespace is the DbGeography class that allows you to work with either points on a "map" or shapes/polygons.

To create a DbGeography point object you need to know the latitude and longitude of that point. There are many services that allow lookup these values for a given place, so we can pass the string "Glasgow" and get back the values 56.033113,-4.789563 as a latitude/longitude pair.

How can we convert these values to a DbGeography point? Like this:

public static DbGeography ConvertLatLonToDbGeography(double longitude, double latitude)  
{
    var point = string.Format("POINT({1} {0})", latitude, longitude);
    return DbGeography.FromText(point);
}

This method takes the two values, which have to be defined as double, and returns the DbGeography object.

Why is this useful?

Imagine that you need to find all venues/properties/points within a certain radious from a central point? In this case, you can use the DbGeography.Distance() method to find all points of interest.

Let's put it to the test. First we create two points and then we calculate their distance. A very simple example:

var pointA = string.Format("POINT({1} {0})", latitude1, longitude1);  
var pointB = string.Format("POINT({1} {0})", latitude2, longitude2);

var distanceInMeters = pointA.Distance(pointB);  

Important: The value returned from this calculation is in meters.

Polygons

The DbGeography class has another cool method that allows you to easily create any random shape (polygon) as long as the start and end points are the same. To create a polygon you need to supply a set of latitude/longitude values and you need to ensure that the shape closes back to itself.

So, given a set of comma-separated values I would do the following:

  • Create an object (we'll name it GeoCoordinate) to hold the latitude and longitude values
  • A method to convert the comma-separated values to a list of GeoCoordinates
  • A method to create the polygon using the GeoCoordinate object list.

So, let's do it! First the GeoCoordinate class. Nothing fancy:

public class GeographicCoordinate  
{
    private const double Tolerance = 10.0 * .1;

    public GeographicCoordinate(double longitude, double latitude)
    {
        this.Longitude = longitude;
        this.Latitude = latitude;
    }

    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public static bool operator ==(GeographicCoordinate a, GeographicCoordinate b)
    {
        // If both are null, or both are same instance, return true.
        if (ReferenceEquals(a, b))
        {
            return true;
        }

        // If one is null, but not both, return false.
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        var latResult = Math.Abs(a.Latitude - b.Latitude);
        var lonResult = Math.Abs(a.Longitude - b.Longitude);
        return (latResult < Tolerance) && (lonResult < Tolerance);
    }

    public static bool operator !=(GeographicCoordinate a, GeographicCoordinate b)
    {
        return !(a == b);
    }

    public override bool Equals(object obj)
    {
        // Check for null values and compare run-time types.
        if (obj == null || GetType() != obj.GetType())
        {
            return false;
        }

        var p = (GeographicCoordinate)obj;
        var latResult = Math.Abs(this.Latitude - p.Latitude);
        var lonResult = Math.Abs(this.Longitude - p.Longitude);
        return (latResult < Tolerance) && (lonResult < Tolerance);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (this.Latitude.GetHashCode() * 397) ^ this.Longitude.GetHashCode();
        }
    }
}

Now we need a method to convert the string to a list of GeoCoordinate objects. I assume at this stage that the values are in the right order as lat,lon,lat,lon etc. and that you have at least four points. Remember that the first and last point need to be the same, so minimum of 4 points!

public static IEnumerable<GeoCoordinate> ConvertStringArrayToGeographicCoordinates(string pointString)  
{
    var points = pointString.Split(',');
    var coordinates = new List<GeoCoordinate>();

    for (var i = 0; i < points.Length / 2; i++)
    {
        var geoPoint = points.Skip(i * 2).Take(2).ToList();
        coordinates.Add(new GeoCoordinate(double.Parse(geoPoint.First()), double.Parse(geoPoint.Last())));
    }

    return coordinates;
}

A bit of LINQ to help us quickly and efficiently create our objects and then we can go ahead and create our polygon by passing the list of GeoCoordinates we just created.

public static DbGeography ConvertGeoCoordinatesToPolygon(IEnumerable<GeographicCoordinate> coordinates)  
{
    var coordinateList = coordinates.ToList();
    if (coordinateList.First() != coordinateList.Last())
    {
        throw new Exception("First and last point do not match. This is not         a valid polygon");
    }

    var count = 0;
    var sb = new StringBuilder();
    sb.Append(@"POLYGON((");
    foreach (var coordinate in coordinateList)
    {
        if (count == 0)
        {
            sb.Append(coordinate.Longitude + " " + coordinate.Latitude);
        }
        else
        {
            sb.Append("," + coordinate.Longitude + " " + coordinate.Latitude);
        }

        count++;
    }

    sb.Append(@"))");

    return DbGeography.PolygonFromText(sb.ToString(), 4326);
}

This last method will return a DbGeography polygon. Now, you can use the polygon methods to interact with other points. For example, you can try to find any object that resides within a polygon. To do this, you can call DbGeography.Intersects() method like int the example below:

var pointA = string.Format("POINT({1} {0})", latitude1, longitude1);  
if (pointA.Intersects(polygon))  
{
    // do something with pointA
}

The Intersects() method checks to see if "pointA" falls inside the area defined by the polygon.

Note if you need to check the area value of the polygon using DbGeography.Area, then be aware that this value is defined in square meters (m^2) so again you may have to do some conversations based on your application requirements.

Happy coding...


  • Share this post on
comments powered by Disqus