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)
Snapping a Point to an Oracle Spatial SDO_GEOMETRY LineString
Introduction
A common function in geoprocessing is the need to snap a point to a linear (line or polygon) geometry.
Up until the release of the MDSYS.SDO_GEOM.SDO_CLOSEST_POINTS() procedures there were no “out of the box” solution to this problem for Oracle Locator users other than custom or open source (eg JTS) implementations. Just such custom implementations are available in the database stored procedures for TSQL and PL/SQL available from my shop.
However the making available of all of the Spatial functionality for all users (cf the demise of Locator), the LRS function SDO_LRS.PROJECT_PT can be used to snap a point to a line.
This article shows how to use MDSYS.SDO_GEOM.SDO_CLOSEST_POINTS(), and SDO_LRS.PROJECT_PT, and a little wrapper code, to achieve snapping.
MDSYS.SDO_GEOM.SDO_CLOSEST_POINTS
The power of SDO_CLOSEST_POINTS is that it actually computes the point on each geometry which is nearest to the other. If one of the geometries is a point and the other a linestring then the resultant closest point on the linestring is the snap point. (The nearest point on the point is the point, by definition!) While this article is only interested in the point snapping case, it is useful that this function can compute the nearest point a linestring (say) comes close to another linestring or polygon (say).
Earlier versions up to and including 19c of Oracle declared MDSYS.SDO_GEOM.SDO_CLOSEST_POINTS to be a procedure.
SDO_GEOM.SDO_CLOSEST_POINTS( geom1 IN SDO_GEOMETRY, geom2 IN SDO_GEOMETRY, tolerance IN NUMBER, unit IN VARCHAR2, dist OUT NUMBER, geoma OUT SDO_GEOMETRY, geomb OUT SDO_GEOMETRY);
Note that is returns the computed data via three output parameters.
Calling the procedure requires an anonymous PL/SQL block:
set serveroutput on size unlimited DECLARE v_out_geom1 MDSYS.SDO_GEOMETRY; v_out_geom2 MDSYS.SDO_GEOMETRY; v_out_distance number; BEGIN SDO_GEOM.SDO_CLOSEST_POINTS ( geom1 => sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)), geom2 => sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL), tolerance => 0.0005, unit => null, dist => v_out_distance, geoma => v_out_geom1, geomb => v_out_geom2 ); dbms_output.put_line(v_out_distance); dbms_output.put_line(v_out_geom1.get_wkt()); dbms_output.put_line(v_out_geom2.get_wkt()); END; / show errors .104536101718726 POINT (1.09053574604252 0.995893206467704) POINT (1.1 1.1) PL/SQL procedure successfully completed.
Function instead of Procedure
Using the procedure it is easy to program a function (with a more meaningful name) that snaps a point to a linestring:
Create or Replace Function ST_Snap ( p_line in sdo_geometry, p_point in sdo_geometry, p_tolerance in number := 0.005 ) return mdsys.sdo_geometry As v_tolerance number := NVL(p_tolerance,0.05); v_distance number; v_point_on_line mdsys.sdo_geometry; v_point_on_point mdsys.sdo_geometry; Begin if ( p_line is null or p_point is null ) Then return p_point; End if; If ( p_point.get_gtype() <> 1 ) Then return p_point; End If; MDSYS.SDO_GEOM.SDO_CLOSEST_POINTS( geom1 => p_line, geom2 => p_point, tolerance => v_tolerance, unit => NULL, dist => v_distance, geoma => v_point_on_line, geomb => v_point_on_point ); IF ( v_point_on_line is not null ) THEN RETURN v_point_on_line; ELSE RETURN p_point; END IF; End ST_Snap; /
Tests
The ST_Snap function can be called as follows.
select ST_Snap(sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,1),sdo_ordinate_array(0,0,0.9999,1.001)), sdo_geometry(2001,null,sdo_point_type(0.9998,1.0011,null),null,null), 0.0005) as geom from dual; GEOM --------------------------------------------------------------------------- SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(0.9998, 1.0011, NULL), NULL, NULL) select ST_Snap(sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,1),sdo_ordinate_array(0,0,1.0,0.0)), sdo_geometry(2001,null,sdo_point_type(0.98,0.005,null),null,null), 0.0005) as point from dual; POINT(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES) ---------------------------------------------------------------------------- SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(0.98, 0, NULL), NULL, NULL) select ST_Snap(sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,1),sdo_ordinate_array(0,0,1.0,0.0)), sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,0.0,NULL),NULL,NULL), 0.0005) as point from dual; POINT(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES) ---------------------------------------------------------------------------- SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(1, 0, NULL), NULL, NULL) select ST_Snap(sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)), sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL), 0.0005) as point from dual; POINT(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES) ---------------------------------------------------------------------------------- SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(1.09053575, 0.99589321, NULL), NULL, NULL)
SDO_CLOSEST_POINTS as function
From version 21c onwards SDO_CLOSEST_POINTS is now a function.
SDO_GEOM.SDO_CLOSEST_POINTS( geom1 IN SDO_GEOMETRY, geom2 IN SDO_GEOMETRY, tolerance IN NUMBER, unit IN VARCHAR2 [, ellipsoidal IN VARCHAR2 DEFAULT NULL] ) RETURN SDO_CLOSEST_POINTS_TYPE;
Where SDO_CLOSEST_POINTS_TYPE is:
CREATE TYPE sdo_closest_points_type AS OBJECT ( dist NUMBER, geoma SDO_GEOMETRY, geomb SDO_GEOMETRY );
Two examples
-- 1. Raw use SELECT sdo_geom.sdo_closest_points ( geom1 => sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)), geom2 => sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL), tolerance => 0.005, unit => null) cp FROM dual; CP(DIST, GEOMA(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES), GEOMB(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES)) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SDO_CLOSEST_POINTS_TYPE( .104536101718726, SDO_GEOMETRY(2001,NULL,SDO_POINT_TYPE(1.09053574604252 0.995893206467704,NULL),NULL,NULL), SDO_GEOMETRY(2001,NULL,SDO_POINT_TYPE(1.1 1.1, NULL),NULL,NULL) ) -- 2.0 Selecting just the snap point SELECT f.cp.geoma as snap_point FROM (SELECT sdo_geom.sdo_closest_points ( geom1 => sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)), geom2 => sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL), tolerance => 0.005, unit => null) cp FROM dual ) f; SNAP_POINT -----------------------------------------------------------------------------------------SDO_GEOMETRY(2001,NULL,SDO_POINT_TYPE(1.09053574604252 0.995893206467704,NULL),NULL,NULL)
SDO_LRS.PROJECT_PT
SDO_LRS.PROJECT_PT is defined as follows:
SDO_LRS.PROJECT_PT( geom_segment IN SDO_GEOMETRY, point IN SDO_GEOMETRY, tolerance IN NUMBER DEFAULT 1.0e-8 [, offset OUT NUMBER] ) RETURN SDO_GEOMETRY;
SDO_LRS.PROJECT_PT’s geom_segment must be measured (it cannot be 2D), so the ancillary function, SDO_LRS.CONVERT_TO_LRS_GEOM must be called on any non-LRS inputs. So, it can be used as follows:
SELECT SDO_LRS.PROJECT_PT( SDO_LRS.CONVERT_TO_LRS_GEOM( sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)) ), sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL)) as point FROM dual; POINT -------------------------------------------------------------------------------------------------------------------- SDO_GEOMETRY(3301, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(1.09053575, 0.99589321, 1.66145621))
Wrapper function to hide use of multiple SDO_LRS calls
If you want to hide the CONVERT_TO_LRS_GEOM call, encapsulating the call inside another function will be needed:
create or replace Function ST_Snap ( p_line in sdo_geometry, p_point in sdo_geometry, p_tolerance in number := 0.005 ) return mdsys.sdo_geometry As v_tolerance number := NVL(p_tolerance,0.05); v_line mdsys.sdo_geometry; v_point mdsys.sdo_geometry; Begin if ( p_line is null or p_point is null ) Then return p_point; End if; If ( p_line.get_gtype() not in (2,6) ) Then return p_point; End If; If ( p_point.get_gtype() <> 1 ) Then return p_point; End If; v_line := p_line; IF ( p_line.Get_LRS_Dim() = 0 ) THEN v_line := MDSYS.SDO_LRS.CONVERT_TO_LRS_GEOM(p_line); END IF; v_point := MDSYS.SDO_LRS.PROJECT_PT(v_line,p_point,v_tolerance); IF ( p_line.get_lrs_dim() = 0 ) THEN RETURN MDSYS.SDO_LRS.CONVERT_TO_STD_GEOM(v_point); ELSE RETURN v_point; END IF; End ST_Snap; /
ST_Snap Example
SELECT ST_Snap(sdo_geometry(2002,null,null,sdo_elem_info_array(1,2,2),sdo_ordinate_array(0,0,1.0,1.0,2,0)), sdo_geometry(2001,NULL,SDO_POINT_TYPE(1.1,1.1,NULL),NULL,NULL), 0.005) as point FROM dual; POINT ---------------------------------------------------------------------------------- SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(1.09053575, 0.99589321, NULL), NULL, NULL)
NOTE: This function returns a point with the same dimensionality of the input line.
Conclusion
There is much powerful functionality buried inside the Oracle Spatial API which, with a little but of coding can be repackaged for purposes other than originally intended.
I hope this little article is of use to someone.
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