Reputation: 45
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
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
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