360code
360code

Reputation: 45

DbGeography Intersects method not working

The System.Data.Spatial.DbGeography.Intersects method seems to always return true for me. I'm not sure why this happens. I've created a simple command line snippet below that results in the below console output

Intersects
Intersects

The point clearly is nowhere near the bounds, and therefore should not intersect.

DbGeography bounds = DbGeography.PolygonFromText("POLYGON ((146 -20,148 -20,148 -22,146 -22,146 -20))", 4326);
DbGeography point = DbGeography.PointFromText("POINT (0 0)", 4326);
if (point.Intersects(bounds) == true)
    Console.WriteLine("Intersects");
else
    Console.WriteLine("Does NOT intersect");

if (bounds.Intersects(point) == true)
    Console.WriteLine("Intersects");
else
    Console.WriteLine("Does NOT intersect");

Upvotes: 4

Views: 2494

Answers (2)

Ogglas
Ogglas

Reputation: 69958

As already mentioned this is a ring orientation error. For those that use SQL Server - It uses left-handed orientation, which means that if you are walking along the perimeter of a polygon, your left hand should be on the inside of the polygon and your right hand on the outside (counter-clockwise or anti-clockwise). I have gotten the “ring orientation” error even from government data because the polygon was drawn in the opposite direction (clockwise, or right-handed) which meant that SQL Server was treating the whole surface of the earth EXCEPT FOR my polygon as the area of the polygon.

This is a method that fixes this problem automatically:

public bool IsInside(DbGeography polygon, double longitude, double latitude)
{
    DbGeography point = DbGeography.FromText(string.Format("POINT({1} {0})", latitude.ToString().Replace(',', '.'), longitude.ToString().Replace(',', '.')), DbGeography.DefaultCoordinateSystemId);

    var wellKnownText = polygon.AsText();

    var sqlGeography =
        SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId)
            .MakeValid();

    //Now get the inversion of the above area
    var invertedSqlGeography = sqlGeography.ReorientObject();

    //Whichever of these is smaller is the enclosed polygon, so we use that one.
    if (sqlGeography.STArea() > invertedSqlGeography.STArea())
    {
        sqlGeography = invertedSqlGeography;
    }

    polygon = DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);

    return point.Intersects(polygon);
}

For those that use Entity Framework 5<:

I use this extension method to check every Polygon and Multipolygon before saving them to the database.

public static DbGeography MakePolygonValid(this DbGeography geom)
{
    var wellKnownText = geom.AsText();

    //First, get the area defined by the well-known text using left-hand rule
    var sqlGeography =
        SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId)
            .MakeValid();

    //Now get the inversion of the above area
    var invertedSqlGeography = sqlGeography.ReorientObject();

    //Whichever of these is smaller is the enclosed polygon, so we use that one.
    if (sqlGeography.STArea() > invertedSqlGeography.STArea())
    {
        sqlGeography = invertedSqlGeography;
    }
    return DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);
}

I can then use a method like this to check Intersects at database level.

public static class GeoHelper
{
    public const int SridGoogleMaps = 4326;
    public const int SridCustomMap = 3857;

    public static DbGeography PointFromLatLng(double lat, double lng)
    {
        return DbGeography.PointFromText(
            "POINT("
            + lng.ToString(CultureInfo.InvariantCulture) + " "
            + lat.ToString(CultureInfo.InvariantCulture) + ")",
            SridGoogleMaps);
    }
}

public County GetCurrentCounty(double latitude, double longitude)
{
    var point = GeoHelper.PointFromLatLng(latitude, longitude);

    var county = db.Counties.FirstOrDefault(x =>
        x.Area.Intersects(point));

    return county;
}

T-SQL generated by Entity Framework:

SELECT TOP (1) 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Code] AS [Code], 
[Extent1].[Area] AS [Area]
FROM [Election].[County] AS [Extent1]
WHERE ([Extent1].[Area].STIntersects(@p__linq__0)) = 1


-- p__linq__0: 'POINT (10.0000000 32.0000000)' (Type = Object)

Can be tested like this manually:

declare @p__linq__0 varchar(max)
set @p__linq__0 = 'POINT (10.0000000 32.0000000)' 

SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Code] AS [Code], 
    [Extent1].[Area] AS [Area]
    FROM [Election].[County] AS [Extent1]
    WHERE ([Extent1].[Area].STIntersects(@p__linq__0)) = 1

More information can be found here:

https://learn.microsoft.com/en-us/sql/t-sql/spatial-geometry/stintersects-geometry-data-type

Sources from this thread:

Check if dbgeometry dbgeometry/dbgeography point is within a polygon

Upvotes: 0

Ben Thul
Ben Thul

Reputation: 32687

The point clearly is nowhere near the bounds, and therefore should not intersect.

There is a rule: as soon as you say "clearly", prepare to be incorrect. :)

Kidding aside, you have a ring orientation problem. That is, the order in which you specify the points matters. As you've specified the corners, you've defined an area that is the entire globe with a very tiny hole in it. Try using this instead:

POLYGON ((146 -20,146 -22,148 -22,148 -20,146 -20))

So, how do you know intrinsically that you have an orientation problem? One heuristic that I like to use is that if the envelope angle for the object is large (90 degrees = one hemisphere), then you've specified the ordering incorrectly. There's an EnvelopeAngle method on the Geography datatype in the DB engine (but it looks like not in the DbGeography class in C#) to determine this. There's also a convenience method (again for sure in the DB but not in C#) for reorienting the ring called, not surprisingly ReorientObject.

Upvotes: 5

Related Questions