Top 5 Recent Articles
ARTICLES CATEGORIES
- Algorithms (22)
- All (399)
- Biography (1)
- Blog (44)
- Business Requirements (1)
- Commentary (1)
- Conversion (2)
- Customers (2)
- Data Models (1)
- Education (2)
- GeoRaptor (13)
- GPS (1)
- Image Processing (2)
- Import Export (8)
- Licensing (2)
- LiDAR (1)
- Linear Referencing (4)
- Manifold GIS (3)
- Mapping (1)
- MySQL Spatial (7)
- Networking and Routing (including Optimization) (5)
- Open Source (18)
- Oracle Spatial and Locator (194)
- Partitioning (1)
- PostGIS (36)
- Projections (1)
- Published Articles (1)
- qGIS (1)
- Recommendations (1)
- Services (1)
- Software Change Log (1)
- Source Code (37)
- Space Curves (9)
- Spatial Database Functions (109)
- Spatial DB comparison (1)
- Spatial XML Processing (11)
- SQL Server Spatial (92)
- Standards (3)
- Stored Procedure (17)
- Tessellation or Gridding (10)
- Tools (2)
- Topological Relationships (1)
- Training (2)
- Triangulation (2)
STExtract: Extract elements of a geometry object
Of the functions I have written for Oracle, those that allow me to extract (sub)elements of compound (objects consisting of linestrings and circular arcs) and multipart geometries have found use time and time again in such activities as:
- Filtering Rings of Polygons;
- Extracting homogeneous objects (eg point, line or polygon) from geometry collections.
This article presents a function call STExtract that implements the extraction of the elements and sub-elements of a geometry as independent geometries.
USE [GISDB] -- Change this to your database GO DROP FUNCTION [dbo].[STExtract] GO CREATE FUNCTION [dbo].[STExtract](@p_geometry geometry, @p_sub_geom INT = 0 ) RETURNS @geometries TABLE (gid INTEGER, sid INTEGER, geom geometry) AS BEGIN DECLARE @v_GeometryType VARCHAR(1000), @v_sub_n INT, @v_geom_n INT, @geom geometry; BEGIN IF ( @p_geometry IS NULL ) RETURN; SET @v_GeometryType = @p_geometry.STGeometryType(); IF ( @v_GeometryType = 'Point' ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, 0, @p_geometry ); RETURN; END; IF ( @v_GeometryType = 'MultiPoint' ) BEGIN SET @v_geom_n = 1; WHILE ( @v_geom_n <= @p_geometry.STNumGeometries() ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( @v_geom_n, 0, @p_geometry.STGeometryN(@v_geom_n) ); SET @v_geom_n = @v_geom_n + 1; END; RETURN; END; IF ( @v_GeometryType IN ('LineString') ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, 0, @p_geometry ); RETURN; END; IF ( @v_GeometryType IN ('CircularCurve','CompoundCurve') ) BEGIN IF ( @p_sub_geom = 0 ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, 0, @p_geometry ); RETURN; END ELSE BEGIN SET @v_geom_n = 1; WHILE ( @v_geom_n <= @p_geometry.STNumCurves() ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, @v_geom_n, @p_geometry.STCurveN(@v_geom_n) ); SET @v_geom_n = @v_geom_n + 1; END; RETURN; END END; IF ( @v_GeometryType IN ('MultiLineString') ) BEGIN SET @v_geom_n = 1; WHILE ( @v_geom_n <= @p_geometry.STNumGeometries() ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) SELECT @v_geom_n, [sid], [geom] FROM dbo.STExtract(@p_geometry.STGeometryN(@v_geom_n),@p_sub_geom); SET @v_geom_n = @v_geom_n + 1; END; RETURN; END; IF ( @v_GeometryType IN ('Polygon','CurvePolygon') ) BEGIN IF ( @p_sub_geom = 1 ) BEGIN SET @v_sub_n = 0; WHILE ( @v_sub_n < ( 1 + @p_geometry.STNumInteriorRing() ) ) BEGIN IF ( @v_sub_n = 0 ) SET @geom = @p_geometry.STExteriorRing() ELSE SET @geom = @p_geometry.STInteriorRingN(@v_sub_n); IF ( @v_geometryType = 'CurvePolygon' ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) SELECT a.gid, a.sid, a.geom FROM dbo.STExtract(@geom,@p_sub_geom) AS a; END ELSE BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, @v_sub_n + 1, geometry::STGeomFromText( CASE WHEN UPPER(@geom.STAsText()) LIKE 'LINESTRING%' THEN REPLACE(REPLACE(UPPER(@geom.STAsText()),'LINESTRING (','POLYGON (('),')','))') WHEN UPPER(@geom.STAsText()) LIKE 'COMPOUNDCURVE%' THEN REPLACE(UPPER(@geom.STAsText()),'COMPOUNDCURVE','CURVEPOLYGON(COMPOUNDCURVE') + ')' ELSE @geom.STAsText() END, @p_geometry.STSrid)); END; SET @v_sub_n = @v_sub_n + 1; END; END ELSE BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( 1, 0, @p_geometry); END; RETURN; END; IF ( @v_GeometryType = 'MultiPolygon' ) BEGIN SET @v_geom_n = 1; WHILE ( @v_geom_n <= @p_geometry.STNumGeometries() ) BEGIN IF ( @p_sub_geom = 0 ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) VALUES ( @v_geom_n, 0, @p_geometry.STGeometryN(@v_geom_n)); END ELSE BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) SELECT @v_geom_n, [sid], [geom] FROM dbo.STExtract(@p_geometry.STGeometryN(@v_geom_n),@p_sub_geom); END; SET @v_geom_n = @v_geom_n + 1; END; RETURN; END; IF ( @v_GeometryType = 'GeometryCollection' ) BEGIN SET @v_geom_n = 1; WHILE ( @v_geom_n <= @p_geometry.STNumGeometries() ) BEGIN INSERT INTO @geometries ( [gid],[sid],[geom] ) SELECT @v_geom_n, [sid], [geom] FROM dbo.STExtract(@p_geometry.STGeometryN(@v_geom_n),@p_sub_geom); SET @v_geom_n = @v_geom_n + 1; END; RETURN; END; END; RETURN; END GO
Testing this we get.
SELECT 'POINT' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('POINT(0 0)',0),1) AS gElem UNION ALL SELECT 'MPONT' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('MULTIPOINT((0 0),(20 0))',0),1) AS gElem UNION ALL SELECT 'LINES' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('LINESTRING(0 0,20 0,20 20,0 20,0 0)',0),1) AS gElem UNION ALL SELECT 'MLINE' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('MULTILINESTRING((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',0),1) AS gElem UNION ALL SELECT 'POLYI' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',0),1) AS gElem UNION ALL SELECT 'MPLYO' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('MULTIPOLYGON (((80 80, 100 80, 100 100, 80 100, 80 80)), ((110 110, 150 110, 150 150, 110 150, 110 110)))',0),1) AS gElem UNION ALL SELECT 'MPLYI' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('MULTIPOLYGON (((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5)), ((80 80, 100 80, 100 100, 80 100, 80 80)), ((110 110, 150 110, 150 150, 110 150, 110 110)))',0),1) AS gElem UNION ALL SELECT 'CPLY0' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('CURVEPOLYGON(COMPOUNDCURVE((0 -23.43778, 0 23.43778),CIRCULARSTRING(0 23.43778, -45 23.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))',0),0) AS gElem UNION ALL SELECT 'CPLY1' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('CURVEPOLYGON(COMPOUNDCURVE((0 -23.43778, 0 23.43778),CIRCULARSTRING(0 23.43778, -45 23.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))',0),1) AS gElem UNION ALL SELECT 'GEOC0' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('GEOMETRYCOLLECTION(LINESTRING(0 0,20 0,20 20,0 20,0 0), CURVEPOLYGON(COMPOUNDCURVE((0 -23.43778, 0 23.43778),CIRCULARSTRING(0 23.43778, -45 23.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), COMPOUNDCURVE(CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778), (0 -23.43778, 0 23.43778)))',0),0) AS gElem UNION ALL SELECT 'GEOC1' AS gtype, gid,sid,geom.STAsText() AS geom FROM dbo.STExtract(geometry::STGeomFromText('GEOMETRYCOLLECTION(LINESTRING(0 0,20 0,20 20,0 20,0 0), CURVEPOLYGON(COMPOUNDCURVE((0 -23.43778, 0 23.43778),CIRCULARSTRING(0 23.43778, -45 23.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), COMPOUNDCURVE(CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778), (0 -23.43778, 0 23.43778)))',0),1) AS gElem;
Which results in:
GTYPE | GID | SID | GEOM |
---|---|---|---|
POINT | 1 | 0 | POINT (0 0) |
MPONT | 1 | 0 | POINT (0 0) |
MPONT | 2 | 0 | POINT (20 0) |
LINES | 1 | 0 | LINESTRING (0 0, 20 0, 20 20, 0 20, 0 0) |
MLINE | 1 | 0 | LINESTRING (0 0, 20 0, 20 20, 0 20, 0 0) |
MLINE | 2 | 0 | LINESTRING (10 10, 10 11, 11 11, 11 10, 10 10) |
MLINE | 3 | 0 | LINESTRING (5 5, 5 7, 7 7, 7 5, 5 5) |
POLYI | 1 | 1 | POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0)) |
POLYI | 1 | 2 | POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10)) |
POLYI | 1 | 3 | POLYGON ((5 5, 5 7, 7 7, 7 5, 5 5)) |
MPLYO | 1 | 1 | POLYGON ((80 80, 100 80, 100 100, 80 100, 80 80)) |
MPLYO | 2 | 1 | POLYGON ((110 110, 150 110, 150 150, 110 150, 110 110)) |
MPLYI | 1 | 1 | POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0)) |
MPLYI | 1 | 2 | POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10)) |
MPLYI | 1 | 3 | POLYGON ((5 5, 5 7, 7 7, 7 5, 5 5)) |
MPLYI | 2 | 1 | POLYGON ((80 80, 100 80, 100 100, 80 100, 80 80)) |
MPLYI | 3 | 1 | POLYGON ((110 110, 150 110, 150 150, 110 150, 110 110)) |
CPLY0 | 1 | 0 | CURVEPOLYGON (COMPOUNDCURVE ((0 -23.43778, 0 23.43778), CIRCULARSTRING (0 23.43778, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778))) |
CPLY1 | 1 | 1 | LINESTRING (0 -23.43778, 0 23.43778) |
CPLY1 | 1 | 2 | CIRCULARSTRING (0 23.43778, -45 23.43778, -90 23.43778) |
CPLY1 | 1 | 3 | LINESTRING (-90 23.43778, -90 -23.43778) |
CPLY1 | 1 | 4 | CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778) |
GEOC0 | 1 | 0 | LINESTRING (0 0, 20 0, 20 20, 0 20, 0 0) |
GEOC0 | 2 | 0 | CURVEPOLYGON (COMPOUNDCURVE ((0 -23.43778, 0 23.43778), CIRCULARSTRING (0 23.43778, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778))) |
GEOC0 | 3 | 0 | COMPOUNDCURVE (CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778), (0 -23.43778, 0 23.43778)) |
GEOC1 | 1 | 0 | LINESTRING (0 0, 20 0, 20 20, 0 20, 0 0) |
GEOC1 | 2 | 1 | LINESTRING (0 -23.43778, 0 23.43778) |
GEOC1 | 2 | 2 | CIRCULARSTRING (0 23.43778, -45 23.43778, -90 23.43778) |
GEOC1 | 2 | 3 | LINESTRING (-90 23.43778, -90 -23.43778) |
GEOC1 | 2 | 4 | CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778) |
GEOC1 | 3 | 1 | CIRCULARSTRING (-90 -23.43778, -45 -23.43778, 0 -23.43778) |
GEOC1 | 3 | 2 | LINESTRING (0 -23.43778, 0 23.43778) |
On top of STExtract we can build a function that dumps the rings of polygon geometries as separate polygon objects (with one outer ring). The function is called STDumpRings and is as follows.
DROP FUNCTION STDumpRings; -- Function: STDumpRings -- CREATE FUNCTION STDumpRings(@p_geometry geometry) RETURNS @rings TABLE (pid INTEGER, rid INTEGER, geom geometry) AS BEGIN BEGIN IF ( @p_geometry IS NULL ) RETURN; IF ( @p_geometry.STGeometryType() NOT IN ('CurvePolygon','Polygon','MultiPolygon') ) RETURN; INSERT INTO @rings ( [pid],[rid],[geom] ) SELECT [gid],[sid],[geom] FROM dbo.STExtract(@p_geometry,1); RETURN; END; END GO
Testing we get:
SELECT 'MLINE' AS gtype, pid,rid,geom.STAsText() AS geom FROM dbo.STDumpRings(geometry::STGeomFromText('MULTILINESTRING((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',0)) AS pGeom UNION ALL SELECT 'POLYI' AS gtype, pid,rid,geom.STAsText() AS geom FROM dbo.STDumpRings(geometry::STGeomFromText('POLYGON((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5))',0)) AS pGeom UNION ALL SELECT 'MPOLI' AS gtype, pid,rid,geom.STAsText() AS geom FROM dbo.STDumpRings(geometry::STGeomFromText('MULTIPOLYGON (((0 0,20 0,20 20,0 20,0 0),(10 10,10 11,11 11,11 10,10 10),(5 5,5 7,7 7,7 5,5 5)), ((80 80, 100 80, 100 100, 80 100, 80 80)), ((110 110, 150 110, 150 150, 110 150, 110 110)))',0)) AS pGeom UNION ALL SELECT 'GEOMC' AS gtype, pid,rid,geom.STAsText() AS geom FROM dbo.STDumpRings(geometry::STGeomFromText('GEOMETRYCOLLECTION(LINESTRING(0 0,20 0,20 20,0 20,0 0), CURVEPOLYGON(COMPOUNDCURVE((0 -23.43778, 0 23.43778),CIRCULARSTRING(0 23.43778, -45 23.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), COMPOUNDCURVE(CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778), (0 -23.43778, 0 23.43778)))',0)) AS pGeom ;
Resulting in:
GTYPE | PID | RID | GEOM |
---|---|---|---|
POLYI | 1 | 1 | POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0)) |
POLYI | 1 | 2 | POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10)) |
POLYI | 1 | 3 | POLYGON ((5 5, 5 7, 7 7, 7 5, 5 5)) |
MPOLI | 1 | 1 | POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0)) |
MPOLI | 1 | 2 | POLYGON ((10 10, 10 11, 11 11, 11 10, 10 10)) |
MPOLI | 1 | 3 | POLYGON ((5 5, 5 7, 7 7, 7 5, 5 5)) |
MPOLI | 2 | 1 | POLYGON ((80 80, 100 80, 100 100, 80 100, 80 80)) |
MPOLI | 3 | 1 | POLYGON ((110 110, 150 110, 150 150, 110 150, 110 110)) |
Note that the LINESTRING produced no output as expected. However, neither did the last GEOMETRYCOLLECTION even though it contains polygon objects with rings. This is simply a matter of programming. Should STDumpRings look inside GeometryCollections? Or is it up to the programmer to feed in polygons via use of dbo.Extract()? If you don’t like the coding of STDumpRings change it to how you want it to behave (the code is open source after all).
In a related article I will show you how to process the result of STIntersection between two polygons to extract only the areas of intersection.
I hope this is of use to someone out there.
Documentation
- MySQL Spatial General Functions
- Oracle LRS Objects
- Oracle Spatial Exporter (Java + pl/SQL)
- Oracle Spatial Object Functions
- Oracle Spatial Object Functions (Multi Page)
- PostGIS pl/pgSQL Functions
- SC4O Oracle Java Topology Suite (Java + pl/SQL)
- SQL Server Spatial General TSQL Functions
- SQL Server Spatial LRS TSQL Functions