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)
Delaunay Triangulation Function for TSQL and SQL Server Spatial
Previously I have written an article on creating a function that implements a Delaunay triangulation for PostGIS.
This article is about creating TSQL procedures that:
- Create a Delaunay triangulation (mesh) from 3D geometry data;
- Computes the total slope (3D) area of a Delaunay triangulation (mesh).
Create Delaunay Triangulation
The procedure that follows requires R Services for SQL Server, and Rolf Turner’s DELDIR R package, to be installed.
CREATE PROCEDURE dbo.STDelaunay( @p_geom geometry, @p_facets geometry out) AS BEGIN Declare @vSrid int, @polygon nvarchar(max), @vFacets nvarchar(max); if ( @p_geom is null or @p_geom.STGeometryType() = 'Point' ) return; set @polygon = @p_geom.AsTextZM(); set @vSrid = @p_geom.STSrid; execute sp_execute_external_script @language = N'R', @script = N' library(deldir) # select the point x/y/z coordinates into a data frame points <- InputDataSet # calculate an approprate buffer distance (~10%): buffer_distance = ( ( abs(max(points$x) - min(points$x)) + abs(max(points$y) - min(points$y)) ) / 2 ) * (0.10) # the following use of deldir uses high precision and digits to prevent # slivers between the output triangles, and uses a relatively large bounding # box with four dummy points included to ensure that points in the # peripheral areas of the dataset are appropriately enveloped by their # corresponding triangles: # points$x, # points$y, # points$z, voro = deldir( list( x=points$x, y=points$y, z=points$z), digits=22, frac=0.00000000000000000000000001, list(ndx=2,ndy=2), rw=c( min(points$x) - abs(min(points$x) - max(points$x)), max(points$x) + abs(min(points$x) - max(points$x)), min(points$y) - abs(min(points$y) - max(points$y)), max(points$y) + abs(min(points$y) - max(points$y)) ) ) # Do the triangulation # triangles = triang.list(voro) # We will output all the triangles as a single Geometry Collection # geomCollection = "GEOMETRYCOLLECTION(" # Now create the output # 1. Construct each triangular facet as a polygon WKT # 2. Append to the GeometryCollection # for (i in 1:length(triangles)) { triangle = triangles[[i]] wktpoly = "POLYGON((" for (j in 1:length(triangle$x)) { wktpoly = sprintf("%s%.8f %.8f %.2f,",wktpoly,triangle$x[[j]],triangle$y[[j]],triangle$z[[j]]) } wktpoly = sprintf("%s%.8f %.8f %.2f))",wktpoly,triangle$x[[1]],triangle$y[[1]],triangle$z[[1]]) if ( i < length(triangles) ) { geomCollection = sprintf("%s%s,",geomCollection,wktpoly) } else { geomCollection = sprintf("%s%s)",geomCollection,wktpoly) } }', @input_data_1 = N'SELECT distinct A.X as x, A.Y as y, A.Z as z FROM [dbo].[STDumpPoints](geometry::STGeomFromText(@wkt,@srid)) as a', @params = N'@wkt varchar(max), @srid int, @geomCollection nvarchar(max) OUTPUT', @wkt = @polygon, @srid = @vSrid, @geomCollection = @vFacets OUTPUT; SET @p_facets = geometry::STGeomFromText(@vFacets,@p_geom.STSrid); END; GO
Example Delaunay Triangulation of Points
An example of calling this procedure is as follows:
declare @vGeom geometry = geometry::STGeomFromText( 'GEOMETRYCOLLECTION ( POINT (-90.2618913358086 33.2184179805337 403), POINT (-90.2534717690058 33.2235960587115 332), POINT (-90.2526790106307 33.2219983038141 393), POINT (-90.2548360643114 33.2220152472292 349), POINT (-90.2569931215599 33.2220321532301 375), POINT (-90.2591702280608 33.2202360124111 329), POINT (-90.2570132117573 33.2202191464654 242), POINT (-90.2548561990173 33.2202022431079 311), POINT (-90.2526991898448 33.2201853023387 296), POINT (-90.2527193654874 33.2183723003489 375), POINT (-90.2548763301557 33.2183892384722 331), POINT (-90.2570332983911 33.2184061391864 246), POINT (-90.2591902701899 33.2184230024914 292), POINT (-90.2602038624534 33.2165408049195 228), POINT (-90.2580468665807 33.2165321889039 295), POINT (-90.2558898738654 33.2165235354584 305), POINT (-90.2537328843112 33.2165148445831 396), POINT (-90.2537432444696 33.2147017821533 329), POINT (-90.255900189497 33.2147104705514 213), POINT (-90.2580571376854 33.2147191215224 218), POINT (-90.2602140890308 33.2147277350661 226), POINT (-90.2591415442112 33.2133037851107 310), POINT (-90.254879330931 33.2133111535788 228) )',4326); declare @vCollection geometry; exec dbo.STDelaunay @p_geom = @vGeom, @p_facets = @vCollection output; select @vCollection union all select @vGeom; GO
Which looks like this:
Computing 3D Area
The triangulation can be extended to allow for the computation of the 3D area of each Delaunay triangle facet (polygon), and the total area (all facets) of the whole triangulation.
First we create a function to calculate the 3D area of a single triangular facet (polygon):
CREATE FUNCTION [dbo].[STTriangleArea3D] ( @p_a geometry, @p_b geometry, @p_c geometry ) returns float as /****f* STTriangleArea3D - Computes slope area of a 3D triangle *******/ Begin Declare @ux float, @uy float, @uz float, @vx float, @vy float, @vz float, @crossx float, @crossy float, @crossz float, @absSq float, @area3D float; IF ( @p_a is null or @p_b is null or @p_c is null ) RETURN NULL; IF ( NOT ( @p_a.STGeometryType() = 'Point' and @p_b.STGeometryType() = 'Point' and @p_c.STGeometryType() = 'Point' ) ) RETURN NULL; /** * Uses the formula 1/2 * | u x v | where u,v are the side vectors of the * triangle x is the vector cross-product */ -- side vectors u and v set @ux = @p_b.STX - @p_a.STX; set @uy = @p_b.STY - @p_a.STY; set @uz = @p_b.Z - @p_a.Z; set @vx = @p_c.STX - @p_a.STX; set @vy = @p_c.STY - @p_a.STY; set @vz = @p_c.Z - @p_a.Z; -- cross-product = u x v set @crossx = @uy * @vz - @uz * @vy; set @crossy = @uz * @vx - @ux * @vz; set @crossz = @ux * @vy - @uy * @vx; -- tri area = 1/2 * | u x v | set @absSq = @crossx * @crossx + @crossy * @crossy + @crossz * @crossz; set @area3D = sqrt(@absSq) / 2.0; return @area3D; End; GO -- Example SELECT [dbo].[STTriangleArea3D] ( geometry::STGeomFromText('POINT(0 0 0)',0), geometry::STGeomFromText('POINT(1 0 1)',0), geometry::STGeomFromText('POINT(0.5 0.5 2)',0)) as Area3D; GO Area3D 0.82915619758885
Now create a simple polygon wrapper over this.
CREATE FUNCTION [dbo].[STTriangleArea3DByPolygon]( @p_geom geometry ) returns float as Begin if ( @p_geom is null or @p_geom.STGeometryType() <> 'Polygon' ) return null; if ( @p_geom.STNumPoints() <> 4 ) return null; return [dbo].[STTriangleArea3D]( @p_geom.STPointN(1), @p_geom.STPointN(2), @p_geom.STPointN(3) ); End; GO -- Example select [dbo].[STTriangleArea3DByPolygon] (geometry::STGeomFromText('POLYGON((0 0 0, 1 0 1, 0.5 0.5 2, 0 0 0))',0)) as Area3d; go Area3D 0.82915619758885
Compute 3D Area of complete Delaunay Triangulation/Mesh
Finally, we can create a function that creates a Delaunay Mesh from an input geometry and computes the total 3D area.
CREATE PROCEDURE [dbo].[STDelaunayArea3D](@p_geometry geometry, @p_area3d float out) AS BEGIN Declare @vInterior bit, @vFacetArea float, @vGeomId integer, @vNumGeoms integer, @vGeomN geometry, @vGeom geometry; -- Currently we support only MultiPoint and Polygon objects (or containing GeometryCollection). -- if ( @p_geometry is null OR @p_geometry.STGeometryType() NOT IN ('MultiPoint','Polygon','GeometryCollection' ) ) return; -- Set up Interior Check SET @vInterior = case when @p_geometry.STGeometryType() = 'Polygon' then 1 else 0 end; exec [dbo].[STDelaunay] @p_geom = @p_geometry, @p_facets = @vGeom output; set @vFacetArea = 0.0; set @vNumGeoms = @vGeom.STNumGeometries(); set @vGeomId = 1; while (@vGeomID <= @vNumGeoms) Begin SET @vGeomN = @vGeom.STGeometryN(@vGeomId); -- Check it overlaps input geometry (if polygon) -- Touches means it is inside a interiorRing or hole -- PRINT cast(@vInterior as varchar(1)) + ' ---> '+CAST(ISNULL(dbo.STDetermine(@vGeomN,@p_geometry,1),-1) as varchar(20)); if ( ( @vInterior = 1 AND @vGeomN.STDisjoint(@p_geometry)=0 AND @vGeomN.STTouches(@p_geometry)=0 ) OR @vInterior=0 ) SET @vFacetArea += [dbo].[STTriangleArea3DByPolygon](@vGeomN) SET @vGeomID += 1; End; SET @p_area3d = @vFacetArea; END; GO
Here are some examples:
declare @vArea3D float; declare @vGeom geometry = geometry::STGeomFromText ( 'MULTIPOINT( (-90.2618913358086 33.2184179805337 403), (-90.2534717690058 33.2235960587115 332), (-90.2526790106307 33.2219983038141 393), (-90.2548360643114 33.2220152472292 349), (-90.2569931215599 33.2220321532301 375), (-90.2591702280608 33.2202360124111 329), (-90.2570132117573 33.2202191464654 242), (-90.2548561990173 33.2202022431079 311), (-90.2526991898448 33.2201853023387 296), (-90.2527193654874 33.2183723003489 375), (-90.2548763301557 33.2183892384722 331), (-90.2570332983911 33.2184061391864 246), (-90.2591902701899 33.2184230024914 292), (-90.2602038624534 33.2165408049195 228), (-90.2580468665807 33.2165321889039 295), (-90.2558898738654 33.2165235354584 305), (-90.2537328843112 33.2165148445831 396), (-90.2537432444696 33.2147017821533 329), (-90.255900189497 33.2147104705514 213), (-90.2580571376854 33.2147191215224 218), (-90.2602140890308 33.2147277350661 226), (-90.2591415442112 33.2133037851107 310), (-90.254879330931 33.2133111535788 228) )',4326); exec [dbo].[STDelaunayArea3D] @p_geometry = @vGeom, @p_area3d = @vArea3D out; SELECT @vArea3D as Area3D; go Area3D 3.21589444680464 declare @vArea3D float; declare @vGeom geometry = geometry::STGeomFromText('GEOMETRYCOLLECTION(POINT(5 5 5),POLYGON((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0)))',0); exec [dbo].[STDelaunayArea3D] @p_geometry = @vGeom, @p_area3d = @vArea3D out; select @vGeom.STGeometryN(2).STArea() as Area2D, @vArea3D as Area3D; go Area2D Area3D 100 141.42135623731 declare @vArea3D float; declare @vGeom geometry = geometry::STGeomFromText('POLYGON((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0),(1 1 1,9 1 1,9 9 1,1 9 1,1 1 1))',0); exec [dbo].[STDelaunayArea3D] @p_geometry = @vGeom, @p_area3d = @vArea3D out; select @vGeom.STArea() as Area2D, @vArea3D as Area3D; go Area2D Area3D 36 50.9116882454314
I hope this is of interest 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