SpatialDB Advisor
I gave a presentation on some work I did last year for a customer on providing a sophisticated spatial/textual search capability against over 700 tables of spatial data stored in SQL Server 2008 Spatial. The searching combined Spatial and Free Text index based searching.
The basic idea is that the main thing in static spatial metadata (eg ANZLIC) that is of any use in dynamic systems is the Minimum Bounding Rectangle (MBR) of the data. But the problem is that it is static: populated as an after thought to the transactional update of the actual data.
What is needed is dynamic access to MBR data so that primary filtering can be achieved easily (ie my search area is inside/outside the actual extent of the spatial data). Some database vendors do this very well in their spatial index searches, but such searches are also expensive to execute. What one would like is to be able to filter out spatial data based on an efficient search of a single data source.
The only available offering from the Open GeoSpatial Consortium – the GEOMETRY_COLUMNS table (which SQL Server 2008 Spatial does not support) – for the database community doesn’t support MBRs (though Oracle does in its own proprietary metadata).
The presentation shows how a variant of the OGC GEOMETRY_COLUMNS table can be created (with an appropriately standards compliant View) that gives efficient access to the critical MBR data. (This MBR data can be kept up to date with the actual transactional data via appropriate triggers or scheduled tasks.)
The use of the bundled Free Text searching is a clever way to use something provided at no cost to deliver clever end user search requirements:
“To find all spatial objects for whom any field contains a particular piece of text within the current display MBR”
See the presentation here


















Sometimes, some things are simple and prosaic rather than clever and complicated.
Building solutions in TSQL requires one to build a prior collection of handy functions before one can be really productive.
Here is a function I use called isGeo which if provided the name of a table/view and a column name returns 1 (true) if the column exists and is of type geometry or geography.
USE [GISDB] /* Replace this with the name of your database */
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
schema_name nvarchar(128),
object_name nvarchar(128),
column_name nvarchar(128))
RETURNS int
WITH EXECUTE AS CALLER,
RETURNS NULL ON NULL INPUT
AS
BEGIN
DECLARE v_result int;
SELECT v_result=1
FROM (SELECT sc.name as schema_name,
ta.name as object_name,
co.name as column_name,
ty.name as column_type,
'TABLE' as object_type
FROM sys.tables ta
JOIN sys.schemas sc ON sc.schema_id = ta.schema_id
JOIN sys.columns co ON ta.object_id = co.object_id
JOIN sys.types ty ON co.user_type_id = ty.user_type_id
WHERE ty.name IN ('geography','geometry')
UNION ALL
SELECT sc.name as schema_name,
va.name as object_name,
co.name as column_name,
ty.name as column_type,
'VIEW' as object_type
FROM sys.views va
JOIN sys.schemas sc ON sc.schema_id = va.schema_id
JOIN sys.columns co ON va.object_id = co.object_id
JOIN sys.types ty ON co.user_type_id = ty.user_type_id
WHERE ty.name IN ('geography','geometry')
) a
WHERE a.schema_name = schema_name
AND a.object_name = object_name
AND a.column_name = column_name ;
Return case when v_result is null then 0 else v_result end;
END;
Now, some tests…
-- Check an existing geometry column in a physical table
select dbo.isGeo('dbo','ADMIN','GEOM') as Result;
Result
1
-- Check an existing geography column in a View
select dbo.isGeo('dbo','vw_terminal','shape') as Result;
Result
1
-- Try again with a non-existing schema
select dbo.isGeo('simon','ADMIN','GEOM') as Result;
Result
0
-- Check action of "RETURNS NULL ON NULL INPUT" function option
select dbo.isGeo(NULL,'ADMIN','GEOM') as Result;
Result
NULL
-- Check again with connected schema provided by appropriate function
select dbo.isGeo(SCHEMA_NAME(),'ADMIN','GEOM') as Result;
Result
1
I hope this helps someone.


















Ever needed to extract a textual description of all geometry types in a single geometry/geography in SQL Server 2008?
Recently I had cause to do so and created the following function that implements this need.
/*********************************************************************************
** @function : Geometry_Type
** @precis : Returns geometry type of passed in geometry as string.
** @description : Returns list of geometry types (from OGC STGeometryType function)
** that describe the contents of the passed in geometry.
** Any GeometryCollection is "exploded" to examine the geometry types
** within it.
** Geography objects can be processed by converting to geometry using
** dbo.toGeometry() function.
** @example : select dbo.geometry_type(geometry::STGeomFromText('POINT(0 1 2)',0));
** @param : p_geom : the geometry object to be processed.
** @param_type : p_geom : geometry
** @history : Simon Greener, The Spatial DB Advisor, September 2009
** @copyright : Licensed under a Creative Commons Attribution-Share Alike 2.5 Australia License.
** (http://creativecommons.org/licenses/by-sa/2.5/au/)
**/
Now let’s test it against a selection of all possible geometry types
select dbo.geometry_type(geometry::STGeomFromText('
POINT(0 1 2)',0)) as gtype
union all select dbo.geometry_type(geometry::STGeomFromText('
MULTIPOINT((0 0 0),
(1 1 1),
(2 2 2),
(3 3 3))',0)) as gtype
union all select dbo.geometry_type(geometry::STGeomFromText('
LINESTRING(0 0, 1 1)',0)) as gtype
union all select dbo.geometry_type(geometry::STGeomFromText('
MULTILINESTRING(
(0 0,1 1,1 2),
(2 3,3 2,5 4))',0)) as gtype
union all select dbo.geometry_type(geometry::STGeomFromText('
POLYGON(
(326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0))',0)) as gtype
union all SELECT dbo.geometry_type(geometry::STGeomFromText('
MULTIPOLYGON(
((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0)),
((321000.0 5450000.0,322000.0 5450000.0,321500.0 5451000.0,321000.0 5450000.0)))',0)) as gtype
union all select dbo.geometry_type(geometry::STGeomFromText('
GEOMETRYCOLLECTION(
POINT(2 3 4),
LINESTRING(2 3 4,3 4 5),
POLYGON((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0)),
POINT(4 5))',0));
| gtype |
|---|
| Point |
| MultiPoint |
| LineString |
| MultiLineString |
| Polygon |
| MultiPolygon |
| GeometryCollection:Point:LineString:Polygon |
But what if I want to create a single string that describes all the geometries in a particular table?
Here is one way to do this via XML aggregation.
WITH geometry_Types AS
(
SELECT distinct dbo.geometry_type(a.geom) as gtype
FROM (select geometry::STGeomFromText('
POINT(0 1 2)',0) as geom
union all select geometry::STGeomFromText('
MULTIPOINT((0 0 0),
(1 1 1),
(2 2 2),
(3 3 3))',0) as geom
union all select geometry::STGeomFromText('
LINESTRING(0 0, 1 1)',0) as geom
union all select geometry::STGeomFromText('
MULTILINESTRING(
(0 0,1 1,1 2),
(2 3,3 2,5 4))',0) as geom
union all select geometry::STGeomFromText('
POLYGON(
(326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0))',0) as geom
union all SELECT geometry::STGeomFromText('
MULTIPOLYGON(
((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0)),
((321000.0 5450000.0,322000.0 5450000.0,321500.0 5451000.0,321000.0 5450000.0)))',0) as geom
union all select geometry::STGeomFromText('
GEOMETRYCOLLECTION(
POINT(2 3 4),
LINESTRING(2 3 4,3 4 5),
POLYGON((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0)),
POINT(4 5))',0) as geom
) a
)
SELECT (STUFF((SELECT DISTINCT ':' + a.gtype
FROM geometry_types a
ORDER BY ':' + a.gtype
FOR XML PATH(''), TYPE, ROOT).value('root[1]','nvarchar(max)'),1,1,'')
) as GeometryTypes;
| GeometryTypes |
|---|
| GeometryCollection:Point:LineString:Polygon:LineString:MultiLineString:MultiPoint:MultiPolygon:Point:Polygon |
Notice how the list is not unique. Here is one method of making it unique.
WITH geometries AS
(
select geometry::STGeomFromText('
POINT(0 1 2)',0) as geom
union all select geometry::STGeomFromText('
MULTIPOINT((0 0 0),
(1 1 1),
(2 2 2),
(3 3 3))',0) as geom
union all select geometry::STGeomFromText('
LINESTRING(0 0, 1 1)',0) as geom
union all select geometry::STGeomFromText('
MULTILINESTRING(
(0 0,1 1,1 2),
(2 3,3 2,5 4))',0) as geom
union all select geometry::STGeomFromText('
POLYGON(
(326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0))',0) as geom
union all SELECT geometry::STGeomFromText('
MULTIPOLYGON(
((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0)),
((321000.0 5450000.0,322000.0 5450000.0,321500.0 5451000.0,321000.0 5450000.0)))',0) as geom
union all select geometry::STGeomFromText('
GEOMETRYCOLLECTION(
POINT(2 3 4),
LINESTRING(2 3 4,3 4 5),
POLYGON((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0)),
POINT(4 5))',0) as geom
)
SELECT (STUFF((SELECT DISTINCT ':' + a.gtype
FROM ( select distinct t.token as gtype
from geometries a
cross apply
dbo.Tokenizer(dbo.geometry_type(a.geom),':') as t
) a
ORDER BY ':' + a.gtype
FOR XML PATH(''), TYPE, ROOT).value('root[1]','nvarchar(max)'),1,1,'')
) as GeometryTypes;
| GeometryTypes |
|---|
| GeometryCollection:LineString:MultiLineString:MultiPoint:MultiPolygon:Point:Polygon |
Finally, show how we can use the function with geography type data.
select dbo.Geometry_Type(
dbo.MBR2GEOMETRY(147,-44,148,-43,4326).STDifference(
dbo.MBR2GEOMETRY(147.4,-43.6,147.6,-43.2,4326) ).STUnion(
dbo.MBR2GEOMETRY(150,-44,151,-43,4326) ) ) as geogtypes;
| geogtypes |
|---|
| MultiPolygon |
I hope this function is useful to someone.


















I demonstrated in this article the usefulness of having a vectorising function in one’s spatial database kitbag (there are GetVector functions in my CENTROID and GEOM packages – in particular the GetVector function is vital to the implementation of the algorithm in my CENTROID.SDO_CENTROID function).
So, I have also added one to my SQL Server 2008 kitbag.
This function, as against the PostGIS and Oracle implementation, does not use any object types due to limitations in the SQL Server 2008 TSQL system (my implementation deliberately does not use the CLR system).
USE [GISDB] -- Change this to your database
GO
Testing
Here’s a bunch of tests.
select v.*
from dbo.GetVector(geometry::STGeomFromText(
'LINESTRING(0 0 10 10, 1 1 10 20, 2 2 10 30, 3 3 10 40)',0)) as v;
| sx | sy | sz | sm | ex | ey | ez | em |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 10 | 10 | 1 | 1 | 10 | 20 |
| 1 | 1 | 10 | 20 | 2 | 2 | 10 | 30 |
| 2 | 2 | 10 | 30 | 3 | 3 | 10 | 40 |
select v.*
from dbo.GetVector(geometry::STGeomFromText(
'MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))',0)) as v;
| sx | sy | sz | sm | ex | ey | ez | em |
|---|---|---|---|---|---|---|---|
| 0 | 0 | NULL | NULL | 1 | 1 | NULL | NULL |
| 1 | 1 | NULL | NULL | 1 | 2 | NULL | NULL |
| 2 | 3 | NULL | NULL | 3 | 2 | NULL | NULL |
| 3 | 2 | NULL | NULL | 5 | 4 | NULL | NULL |
-- Ordinary polygon
--
select v.*
from dbo.GetVector(geometry::STGeomFromText(
'POLYGON((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0))',0)) as v;
| sx | sy | sz | sm | ex | ey | ez | em |
|---|---|---|---|---|---|---|---|
| 326000 | 5455000 | NULL | NULL | 327000 | 5455000 | NULL | NULL |
| 327000 | 5455000 | NULL | NULL | 326500 | 5456000 | NULL | NULL |
| 326500 | 5456000 | NULL | NULL | 326000 | 5455000 | NULL | NULL |
-- Polygon with a hole
--
select v.*
from dbo.GetVector(geometry::STGeomFromText(
'POLYGON((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0))',0)) as v;
| sx | sy | sz | sm | ex | ey | ez | em |
|---|---|---|---|---|---|---|---|
| 326000 | 5455000 | NULL | NULL | 327000 | 5455000 | NULL | NULL |
| 327000 | 5455000 | NULL | NULL | 326500 | 5456000 | NULL | NULL |
| 326500 | 5456000 | NULL | NULL | 326000 | 5455000 | NULL | NULL |
| 326500 | 5455500 | NULL | NULL | 326550 | 5455200 | NULL | NULL |
| 326550 | 5455200 | NULL | NULL | 326450 | 5455200 | NULL | NULL |
| 326450 | 5455200 | NULL | NULL | 326500 | 5455500 | NULL | NULL |
-- MultiPolygon With a hole
--
select v.*
from dbo.GetVector(geometry::STGeomFromText(
'MULTIPOLYGON(((326000.0 5455000.0,327000.0 5455000.0,326500.0 5456000.0,326000.0 5455000.0),
(326500.0 5455500.0,326550.0 5455200.0,326450.0 5455200.0,326500.0 5455500.0)),
((321000.0 5450000.0,322000.0 5450000.0,321500.0 5451000.0,321000.0 5450000.0)))',0)) as v;
| sx | sy | sz | sm | ex | ey | ez | em |
|---|---|---|---|---|---|---|---|
| 326000 | 5455000 | NULL | NULL | 327000 | 5455000 | NULL | NULL |
| 327000 | 5455000 | NULL | NULL | 326500 | 5456000 | NULL | NULL |
| 326500 | 5456000 | NULL | NULL | 326000 | 5455000 | NULL | NULL |
| 326500 | 5455500 | NULL | NULL | 326550 | 5455200 | NULL | NULL |
| 326550 | 5455200 | NULL | NULL | 326450 | 5455200 | NULL | NULL |
| 326450 | 5455200 | NULL | NULL | 326500 | 5455500 | NULL | NULL |
| 321000 | 5450000 | NULL | NULL | 322000 | 5450000 | NULL | NULL |
| 322000 | 5450000 | NULL | NULL | 321500 | 5451000 | NULL | NULL |
| 321500 | 5451000 | NULL | NULL | 321000 | 5450000 | NULL | NULL |
But what if you want the start and end of the vector to be geometry points?
-- Get as points
--
select geometry::Point(v.sx,v.sy,0).STAsText() as startPoint,
geometry::Point(v.ex,v.ey,0).STAsText() as endPoint
from dbo.GetVector(geometry::STGeomFromText(
'LINESTRING(0 0 10 10, 1 1 10 20, 2 2 10 30, 3 3 10 40)',0)) as v;
| startPoint | endPoint |
|---|---|
| POINT (0 0) | POINT (1 1) |
| POINT (1 1) | POINT (2 2) |
| POINT (2 2) | POINT (3 3) |
Finally, what if you want each vector to be represented as a single linestring and not a individual ordinates (sx,sy, …. ,ez,em)?
-- Get as linestring
--
select geometry::STGeomFromText(
'LINESTRING(' +
CAST(v.sx as varchar(10)) + ' ' + CAST(v.sy as varchar(10)) + ',' +
CAST(v.ex as varchar(10)) +' ' + CAST(v.ey as varchar(10)) + ')',0).STAsText() as vLine
from dbo.GetVector(geometry::STGeomFromText(
'LINESTRING(0 0 10 10, 1 1 10 20, 2 2 10 30, 3 3 10 40)',0)) as v;
| vLine |
|---|
| LINESTRING (0 0, 1 1) |
| LINESTRING (1 1, 2 2) |
| LINESTRING (2 2, 3 3) |
I hope this is useful to someone.

















