This post covers the following methods: STDisjoint, STIntersects, Filter, STTouches, STWithin, STContains, STOverlaps, STCrosses, STRelate, & STEquals
One day these examples may find their way to Books Online. For now they can live here.
You will find that these Methods exceptionally handy to find or filter out similar spatial shapes. They are extremely efficient when a suitable spatial index exists.
I'm not saying these methods can ONLY be used in a WHERE clause. It is often handy to combine them with a CASE clause in the select list. However I wanted to group sets of Methods together & this seemed an appropriate name.
These methods Return a boolean: 1 (true) or 0 (false).
Tip: They take advantage of Spatial Index to retrieve relevant rows quickly. Especially if you ask for what you want, so searching for Equality works well, but filtering on not equals, often means the index will not help you. Most methods have a opposite ie: STDisjoint is the opposite of STIntersects. So instead of using "not equals" change to the reciprocal method.
The following code sample lets you explore the GIS Methods. Just Cut n Paste into SQL Server Management Studio (SSMS) 2008 (or higher).
Note 1: If your data has POINTs then the SSMS Spatial viewer disables the ability to display labels. While this is nice if you have a few polygons with names, covers in a lot of points, is it inconvenient here. So comment out the last 3 Union Alls if you want show the labels of your shapes & lines. You can still hover over them to see ToolTip labels on points.
Note 2: Usually you would not have the @S0 shape in the set of shapes you are querying. So sometimes @S0 is in the results where normally it wouldn’t be. I've left it in so you can see the relationship with it & the object you are comparing against it.
-- ===< Samples: TSQL Spatial Conditional Methods as a filter >=== DECLARE @s0 AS GEOMETRY = 'POLYGON((4 4, 7 4, 7 7, 4 7 ,4 4))' -- Main Shape DECLARE @s1 AS GEOMETRY = 'POLYGON((5 4.2, 6 4.2, 6 5, 5 5, 5 4.2))' -- Inside Shape DECLARE @s2 AS GEOMETRY = 'POLYGON((5 3, 6 3, 6 4, 5 4, 5 3))' -- Part Edge in Common DECLARE @s3 AS GEOMETRY = 'POLYGON((7.2 4, 8 4, 8 5, 7.2 5, 7.2 4))' -- Outside Shape DECLARE @t1 AS GEOMETRY = 'POLYGON((3 4, 4 4, 4 3, 3 4))' -- 2 Corners in Common DECLARE @t2 AS GEOMETRY = 'POLYGON((3 5, 4 5.5, 3 6, 3 5))' -- Corner on an Edge DECLARE @t3 AS GEOMETRY = 'POLYGON((6.5 5, 7.5 5.5, 6.5 6, 6.5 5))' -- Overlap DECLARE @L1 AS GEOMETRY = 'LINESTRING(5 6.5, 6.5 6.5)' -- Line in Shape DECLARE @L2 AS GEOMETRY = 'LINESTRING(4.5 3, 4.5 7.5)' -- Line Thru Shape DECLARE @L3 AS GEOMETRY = 'LINESTRING(3 6.5, 4.2 6.5)' -- Line cross Edge of Shape DECLARE @P1 AS GEOMETRY = 'POINT(6.5 4.5)' -- Point in Shape DECLARE @P2 AS GEOMETRY = 'POINT(4 5)' -- Point on Edge DECLARE @P3 AS GEOMETRY = 'POINT(6.5 3.5)' -- Point off Shape SELECT * -- Make all variables appear as the rows of one table FROM ( SELECT @s0 AS 'Geo', 'S0' as 'Labels' UNION ALL SELECT @s1, 'S1' UNION ALL SELECT @s2, 'S2' UNION ALL SELECT @s3, 'S3' UNION ALL SELECT @t1, 'T1' UNION ALL SELECT @t2, 'T2' UNION ALL SELECT @T3, 'T3' UNION ALL SELECT @L1, 'L1' UNION ALL SELECT @L2, 'L2' UNION ALL SELECT @L3, 'L3' -- As Labels don't display in SSMS when you have Points in output. -- Either Comment out the next 3 lines if you want to see the labels in SSMS. -- or put a buffer around them so they display as polygons. UNION ALL SELECT @P1.STBuffer(0.1), 'P1' UNION ALL SELECT @P2.STBuffer(0.1), 'P2' UNION ALL SELECT @P3.STBuffer(0.1), 'P3' ) AS A --Uncomment ONE of the lines below to test the effect of each different where clause -- WHERE A.Geo.STTouches(@s0) = 1 -- WHERE A.Geo.STWithin(@s0) = 1 -- WHERE A.Geo.STCrosses(@s0) = 1 -- WHERE A.Geo.STContains(@S1) = 1 -- WHERE A.Geo.STOverlaps(@s0) = 1 -- WHERE A.Geo.STIntersects(@s0) = 1 -- WHERE A.Geo.STDisjoint(@s0) = 1
When you run the code all the objects display as shown in this diagram. Use this as a baseline when we look at the results of the different filters below.
Example use: Show all customers who live outside our standard delivery area WHERE A.Geo.STDisjoint(@s0) = 1
Yellow highlights all objects which are Disjoint from Shape S0
Example use: List cancer patients who live within 1km of a high tension power line. WHERE A.Geo.STIntersects(@s0) = 1 Yellow highlights all objects which Intersect with Shape S0
Example use: List cancer patients who live within 1km of a high tension power line. WHERE A.Geo.STIntersects(@s0) = 1
Yellow highlights all objects which Intersect with Shape S0
Example use: Show cancer patients who live within 1km of a high tension power line. -- ===< Sample: Using FILTER to rapidly get relevant results >==== USE someDB -- This will give you an error, reminding you not to create it in Master :-) go -- ==< Create a Table with a spatial Index >=== CREATE TABLE dbo.GeoTable (id INT PRIMARY KEY, geom GEOGRAPHY); INSERT INTO GeoTable VALUES (0, GEOGRAPHY::Point(45, -120, 4326)), (1, GEOGRAPHY::Point(45, -120.1, 4326)), (2, GEOGRAPHY::Point(45, -120.2, 4326)), (3, GEOGRAPHY::Point(45, -120.3, 4326)), (4, GEOGRAPHY::Point(45, -120.4, 4326)); CREATE SPATIAL INDEX GeoTable_idx ON dbo.GeoTable(geom); -- ==< Example: The Filter Query >== -- Remember to look in the SSMS Spatial Results tab for see the relationships hereSELECT id FROM dbo.GeoTable WHERE geom.Filter(GEOGRAPHY::Parse('POLYGON((-120.2 44.9, -119.9 44.9, -119.9 45.1, -120.1 45.1, -120.2 44.9))')) = 1; -- Show the Geo Data -- SELECT geom.STBuffer(2000) AS Geom, Cast(id AS VARCHAR(4)) AS 'Labels' FROM dbo.GeoTable UNION ALL SELECT GEOGRAPHY::Parse('POLYGON((-120.2 44.9, -119.9 44.9, -119.9 45.1, -120.1 45.1, -120.2 44.9))') AS SD, 'Poly'; go DROP TABLE dbo.GeoTable go
Example use: Show cancer patients who live within 1km of a high tension power line.
-- ===< Sample: Using FILTER to rapidly get relevant results >==== USE someDB -- This will give you an error, reminding you not to create it in Master :-) go -- ==< Create a Table with a spatial Index >=== CREATE TABLE dbo.GeoTable (id INT PRIMARY KEY, geom GEOGRAPHY); INSERT INTO GeoTable VALUES (0, GEOGRAPHY::Point(45, -120, 4326)), (1, GEOGRAPHY::Point(45, -120.1, 4326)), (2, GEOGRAPHY::Point(45, -120.2, 4326)), (3, GEOGRAPHY::Point(45, -120.3, 4326)), (4, GEOGRAPHY::Point(45, -120.4, 4326)); CREATE SPATIAL INDEX GeoTable_idx ON dbo.GeoTable(geom); -- ==< Example: The Filter Query >== -- Remember to look in the SSMS Spatial Results tab for see the relationships hereSELECT id FROM dbo.GeoTable WHERE geom.Filter(GEOGRAPHY::Parse('POLYGON((-120.2 44.9, -119.9 44.9, -119.9 45.1, -120.1 45.1, -120.2 44.9))')) = 1; -- Show the Geo Data -- SELECT geom.STBuffer(2000) AS Geom, Cast(id AS VARCHAR(4)) AS 'Labels' FROM dbo.GeoTable UNION ALL SELECT GEOGRAPHY::Parse('POLYGON((-120.2 44.9, -119.9 44.9, -119.9 45.1, -120.1 45.1, -120.2 44.9))') AS SD, 'Poly'; go DROP TABLE dbo.GeoTable go
Yellow highlights all objects which touch Shape S0
Example use: Find all homes within 3km of the Bush Fire zone. Would also need STBuffer to expand the Bush Fire Zone shape by 3km. WHERE A.Geo.STWithin(@s0) = 1 Yellow highlights all objects which are completely inside Shape S0
Example use: Find all homes within 3km of the Bush Fire zone. Would also need STBuffer to expand the Bush Fire Zone shape by 3km. WHERE A.Geo.STWithin(@s0) = 1
Yellow highlights all objects which are completely inside Shape S0
This Spatial Object is completely surrounded by another. Works with Geometry objects only. It is not a method of Geography Objects. Sister method of .STWithin, but asks the question from the other perspective. To use spatial index search on WHERE geo1.STContains ( geo2 ) = 1
Example use: Find the polygon (postcode) that the mouse cursor is currently hovering over. You'd need to convert the mouse value into a point, & see what shape contains that point. I've combined the display of 3 queries into one diagram. ie:- WHERE L1.STContains(@S0) = 1 OR S1.STContains(@S0) = 1 OR P1.STContains(@S0) = 1 Yellow highlights all objects which are contained by Shape S0 Note: This query is different, all others use the Geo Column & compare it to the shape S0. These queries use the STContains method on the shape we are interested in & compare it to shape S0
Example use: Find the polygon (postcode) that the mouse cursor is currently hovering over. You'd need to convert the mouse value into a point, & see what shape contains that point. I've combined the display of 3 queries into one diagram. ie:- WHERE L1.STContains(@S0) = 1 OR S1.STContains(@S0) = 1 OR P1.STContains(@S0) = 1
Yellow highlights all objects which are contained by Shape S0 Note: This query is different, all others use the Geo Column & compare it to the shape S0. These queries use the STContains method on the shape we are interested in & compare it to shape S0
True when this Shapes partly lie on other shapes OR lines cross. Works with Geometry objects only. It is not a method of Geography Objects. Sister method of .STCrosses, but only works when objects are the same type: ie Shapes with shapes, Lines with lines. To use spatial index search on WHERE geo1.STOverlaps ( geo2 ) = 1 Example use: Show the Sales Territories where it is not clear who "owns" a new customer. WHERE A.Geo.STOverlaps(@s0) = 1
Yellow highlights all Polygons which lie over the border of Shape S0
True when a Line overlaps a Polygon OR A Point lies on a line. ie: Something of a lesser Dimension (1D - Line or 0D - Point) Works with Geometry objects only. It is not a method of Geography Objects. Sister method of .STCrosses, but only works when objects are different dimensions. So Why not T3? Because the intersection is still a 2D Object, use STOverlaps() for this. WHERE A.Geo.STCrosses(@s0) = 1
Yellow highlights all Lines & Points which lie over the border of Shape S0 -- Sample 2: Show a Conditional method being used in a Select list --- -- Also show STCrosses() working with lines & Points DECLARE @L as GEOMETRY = GEOMETRY::STGeomFromText('LINESTRING(1 0, 5 0)',0); DECLARE @Pon as GEOMETRY = GEOMETRY::STGeomFromText('POINT(2 0)',0); DECLARE @Pend as GEOMETRY = GEOMETRY::STGeomFromText('POINT(5 0)',0); DECLARE @Poff as GEOMETRY = GEOMETRY::STGeomFromText('POINT(3 1)',0); -- Test STCrosses SELECT geo, WKT, Labels, CASE @L.STCrosses(geo) WHEN 0 THEN 'STCrosses is FALSE' WHEN 1 THEN 'STCrosses is TRUE' END AS STCrosses FROM ( SELECT @L as geo, @L.ToString() as WKT, 'L1' as 'Labels' UNION ALL SELECT @Pon.STBuffer(0.1), @Pon.ToString(), 'Point on Line' UNION ALL SELECT @Pend.STBuffer(0.1), @Pend.ToString(), 'Point on End of Line' UNION ALL SELECT @Poff.STBuffer(0.1), @Poff.ToString(), 'Point not on Line' ) AS T Results: WKT Labels STCrosses LINESTRING (1 0, 5 0) Line1 STCrosses is FALSE POINT (2 0) Point on Line1 STCrosses is TRUE POINT (5 0) Point on End of Line1 STCrosses is TRUE POINT (3 1) Point not on Line1 STCrosses is FALSE
-- Sample 2: Show a Conditional method being used in a Select list --- -- Also show STCrosses() working with lines & Points DECLARE @L as GEOMETRY = GEOMETRY::STGeomFromText('LINESTRING(1 0, 5 0)',0); DECLARE @Pon as GEOMETRY = GEOMETRY::STGeomFromText('POINT(2 0)',0); DECLARE @Pend as GEOMETRY = GEOMETRY::STGeomFromText('POINT(5 0)',0); DECLARE @Poff as GEOMETRY = GEOMETRY::STGeomFromText('POINT(3 1)',0); -- Test STCrosses SELECT geo, WKT, Labels, CASE @L.STCrosses(geo) WHEN 0 THEN 'STCrosses is FALSE' WHEN 1 THEN 'STCrosses is TRUE' END AS STCrosses FROM ( SELECT @L as geo, @L.ToString() as WKT, 'L1' as 'Labels' UNION ALL SELECT @Pon.STBuffer(0.1), @Pon.ToString(), 'Point on Line' UNION ALL SELECT @Pend.STBuffer(0.1), @Pend.ToString(), 'Point on End of Line' UNION ALL SELECT @Poff.STBuffer(0.1), @Poff.ToString(), 'Point not on Line' ) AS T
Results:
WKT
Labels
STCrosses
Map showing the 3 Points & 1 Linestring
This Spatial Object has whatever relationship you describe in the Intersection Matrix. Works with Geometry objects only. It is not a method of Geography Objects. It is a superset of all the other methods described in this blog & easily deserves a post dedicated to it. It could easily replace all the “Conditional methods” in this blog eg: .STDisjoint(), STTouches() etc
As there are already good articles available on the web, I will provide a superficial coverage here, for more detail I suggest you look at the following. ESRI's Developer Help: Understanding Spatial Relations
What is an Intersection Matrix?
The 2nd Parameter that you provide is an Intersection Matrix. An Intersection Matrix short for a Dimensionally Extended 9 Intersection Model (DE-9IM). This is a 3 * 3 grid that represents the possible intersections between 2 shapes. Each shape is divided into 3 parts; Interior, Boundary & Exterior & results in a grid below.
1. All possible intersections between 2 shapes; Shape "a" & Shape "b" Interior (b) Boundary (b) Exterior (b) Interior (a) I(a) ? I(b) I(a) ? B(b) I(a) ? E(b) Boundary (a) B(a) ? I(b) B(a) ? B(b) B(a) ? E(b) Exterior (a) E(a) ? I(b) E(a) ? B(b) E(a) ? E(b) 2. Each of the 9 possible intersections can have one of 6 possible values.
1. All possible intersections between 2 shapes; Shape "a" & Shape "b"
2. Each of the 9 possible intersections can have one of 6 possible values.
Value Comments Maximum Dimensionality T An intersection must exist dim = 0, 1, or 2 F An intersection must not exist dim = -1 * It does not matter if an intersection exists or not dim = -1, 0, 1, or 2 0 An intersection must exist and its maximum dimension must be 0 dim = 0 1 An intersection must exist and its maximum dimension must be 1 dim = 1 2 An intersection must exist and its maximum dimension must be 2 dim = 2 For example, the intersection matrix for the STWithin method would look like:-
For example, the intersection matrix for the STWithin method would look like:-
b Interior (b) Boundary (b) Exterior (b) Interior (a) T * F a Boundary (a) * * F Exterior (a) * * * ie: A must be inside B, But A's Interior & Boundary can not be external to B. This is then mapped to 9 characters representing <top line><Middle><bottom Line>. Which in this case is "T*F**F***". Experiment by changing the value in the code below, or combine it into the main code set at the top of this blog.
ie: A must be inside B, But A's Interior & Boundary can not be external to B. This is then mapped to 9 characters representing <top line><Middle><bottom Line>. Which in this case is "T*F**F***".
Experiment by changing the value in the code below, or combine it into the main code set at the top of this blog.
-- Create Shapes DECLARE @Main as GEOMETRY = GEOMETRY::STGeomFromText('POLYGON((10 10, 20 20, 10 30, 0 20, 10 10))',0); DECLARE @S_In as GEOMETRY = GEOMETRY::STGeomFromText('POLYGON((4 20, 16 20, 10 26, 4 20))',0); DECLARE @S_On as GEOMETRY = GEOMETRY::STGeomFromText('POLYGON((12 12, 12 18, 18 18, 18 12, 12 12))',0); DECLARE @S_Out as GEOMETRY = GEOMETRY::STGeomFromText('POLYGON((0 10, 8 10, 0 18, 0 10))',0); -- Intersection Matrix DECLARE @Matrix as CHAR(9) = 'T*F**F***' --'FF*FF****' -- Use STRelate in a query -- SELECT geom, Labels, CASE geom.STRelate(@Main,@Matrix) WHEN 0 THEN 'FALSE' WHEN 1 THEN 'TRUE' END AS [STRelate] FROM ( SELECT @Main as geom, 'Main' as 'Labels' UNION ALL SELECT @S_In, 'S_In' UNION ALL SELECT @S_On, 'S_On' UNION ALL SELECT @S_Out, 'S_Out' ) As Tab1;
For more info see SQL Server 2008 Books Online Geometry Methods Supported by Spatial Indexes
Please give feedback. Is this series of articles useful? Did it save you time? What was good, What could be better?, Notice any errors? What would you like me to cover? All thoughts, comments, suggestions welcome.