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)
Implementing SDO_VertexUpdate/ST_VertexUpdate for Oracle
I have implemented an SDO_SetPoint/ST_SetPoint (PostGIS) function for Oracle. But in so doing I discovered the SE_VertexUpdate(geom ST_LineString,old ST_Point, new ST_Point) function that IBM has implemented.
The interesting difference is that the ST_SetPoint function requires you to supply the original geometry, the new point value and the position of the point in the existing geometry that you want to update: the VertexUpdate version simply requires you to provide the before and after point values.
The problem with the PostGIS approach is apparent and explains why PostGIS has only implemented the function for LineString geometries. The reaons is that if you want to update a polygon geometry and can only supply a single point position, you can end up with a polygon that has its start and end points with different values. An example from my article shows this:
-- How to set the first and last points in a single outer shelled compound polygon polygon
select Geom.SDO_SetPoint(
Geom.SDO_SetPoint(
MDSYS.SDO_Geometry(2003,null,null,
SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,2,5,2,1),
SDO_ORDINATE_ARRAY(-0.175,9.998,-0.349,9.994,-0.523,9.986,0,0,-0.175,9.998)),
MDSYS.Vertex_Type(1,1,null,null,1),
NULL),
MDSYS.Vertex_Type(1,1,null,null,1),
1) as setGeom
from dual;
SETGEOM
--------------------------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,2,5,2,1),MDSYS.SDO_ORDINATE_ARRAY(1,1,-0.349,9.994,-0.523,9.986,0,0,1,1))
1 rows selected
Note that you have to call the function twice AND know the position of the first and last coordinate.
The alternate approach, implemented in the IBM function, is to provide actual values of the existing point and its replacement and to replace all possible old values with new values wherever they occur.
My coding of such a function for Oracle is as follows.
First, the declarations:
/** ----------------------------------------------------------------------------------------
* function : SDO_VertexUpdate
* precis : Replace all points of geometry with new point where they match (including Z and M)
* version : 1.0
* usage : FUNCTION SDO_VertexUpdate(p_geometry in mdsys.sdo_geometry,
* p_old_point in mdsys.vertex_type,
* p_new_point in mdsys.vertex_type )
* RETURN mdsys.sdo_geometry DETERMINISTIC;
* param : p_geometry : Original geometry object
* paramtype : p_geometry : mdsys.sdo_geometry
* param : p_old_point : Actual point coordinates of an existing point
* paramtype : p_old_point : mdsys.vertex_type
* param : p_new_point : Actual point coordinates of replacement point
* paramtype : p_new_point : mdsys.vertex_type
* return : input geometry with changed point.
* returnType : mdsys.sdo_geometry
* history : Simon Greener - February 2009 - Original coding.
* copyright : Licensed under a Creative Commons Attribution-Share Alike 2.5 Australia License. (http://creativecommons.org/licenses/by-sa/2.5/au/)
**/ Function SDO_VertexUpdate(p_geometry IN MDSYS.SDO_Geometry,
p_old_point IN MDSYS.Vertex_Type,
p_new_point IN MDSYS.Vertex_Type)
Return MDSYS.SDO_Geometry Deterministic;
/* ST_* Wrapper */
Function ST_VertexUpdate(p_geometry IN MDSYS.ST_Geometry,
p_old_point IN MDSYS.ST_Point,
p_new_point IN MDSYS.ST_Point)
Return MDSYS.ST_Geometry Deterministic;
And the body:
Function SDO_VertexUpdate(p_geometry IN MDSYS.SDO_Geometry,
p_old_point IN MDSYS.Vertex_Type,
p_new_point IN MDSYS.Vertex_Type)
Return MDSYS.SDO_Geometry
Is
v_ordinates MDSYS.SDO_Ordinate_Array;
v_dims Number;
v_gtype PLS_Integer;
v_sdo_point Mdsys.SDO_Point_Type;
v_measure_posn Number;
NULL_GEOMETRY EXCEPTION;
NULL_POINT EXCEPTION;
INVALID_POSITION EXCEPTION;
Begin
If ( p_geometry is NULL ) Then
raise NULL_GEOMETRY;
End If;
If ( p_old_point is null or
p_new_point is null ) Then
RAISE NULL_POINT;
End If;
v_dims := TRUNC(p_geometry.sdo_gtype/1000,0);
v_gtype := Mod(p_geometry.sdo_gtype,10);
v_measure_posn := MOD(trunc(p_geometry.sdo_gtype/100),10);
v_sdo_point := p_geometry.sdo_point;
v_ordinates := p_geometry.sdo_ordinates;
-- If sdo_geometry is a single point coded in sdo_point, then update it
If ( p_geometry.sdo_point is not null ) Then
If ( ( p_old_point.x = v_sdo_point.x
or
( v_sdo_point.x is null And p_old_point.x is null )
)
and
( p_old_point.y = v_sdo_point.y
or
( v_sdo_point.y is null And p_old_point.y is null )
) and
( v_Dims = 2
or
( v_Dims = 3
and
( p_old_point.z = v_sdo_point.z
or
( v_sdo_point.z is null And p_old_point.z is null )
)
)
)
) Then
v_sdo_point.X := p_new_point.X;
v_sdo_point.Y := p_new_point.Y;
v_sdo_point.Z := p_new_point.Z;
End If;
End If;
If ( v_ordinates is not null ) Then
-- Update the point in the ordinate array
SELECT CASE e.rin
WHEN 1 THEN e.x
WHEN 2 THEN e.y
WHEN 3 THEN CASE v_measure_posn
WHEN 0 THEN e.z
WHEN 3 THEN e.w
END
WHEN 4 THEN e.w
END as ord
BULK COLLECT INTO v_ordinates
FROM (SELECT d.cin, a.rin, d.x, d.y, d.z, d.w
FROM (SELECT LEVEL as rin
FROM DUAL
CONNECT BY LEVEL <= v_dims) a,
(SELECT cin,
case when xm = 1 and ym = 1
and ( zexists = 0 or (zm = 1 and zexists = 1))
and ( wexists = 0 or (wm = 1 and wexists = 1 ))
then p_new_point.x else c.x end as x,
case when xm = 1 and ym = 1
and ( zexists = 0 or (zm = 1 and zexists = 1))
and ( wexists = 0 or (wm = 1 and wexists = 1 ))
then p_new_point.y else c.y end as y, case when xm = 1 and ym = 1
and ( zexists = 0 or (zm = 1 and zexists = 1))
and ( wexists = 0 or (wm = 1 and wexists = 1 ))
then p_new_point.z else c.z end as z,
case when xm = 1 and ym = 1
and ( zexists = 0 or (zm = 1 and zexists = 1))
and ( wexists = 0 or (wm = 1 and wexists = 1 ))
then p_new_point.w else c.w end as w
FROM (SELECT rownum as cin,
b.x,b.y,b.z,b.w,
DECODE(b.x,p_old_point.x,1,0) as xm,
DECODE(b.y,p_old_point.y,1,0) as ym,
DECODE(b.z,p_old_point.z,1,0) as zm,
DECODE(b.w,p_old_point.w,1,0) as wm,
CASE WHEN (( v_dims >= 3 And (v_measure_posn <> 3) ) ) THEN 1 ELSE 0 END as zexists,
CASE WHEN (( v_dims = 3 And (v_measure_posn = 3) ) Or (v_dims = 4) ) THEN 1 ELSE 0 END as wexists
FROM (SELECT v.x,
v.y,
CASE WHEN v_measure_posn <> 3 /* If measured geometry and measure position is not 3 then Z is coded in this position */
THEN v.z
ELSE NULL
END as z,
CASE WHEN v_measure_posn = 3 /* If measured geometry and measure position is 3 then Z has been coded with W so move it */
THEN v.z
ELSE v.w
END as w
FROM TABLE(mdsys.sdo_util.GetVertices(p_geometry)) v
) b
) c
) d
) e
order by e.cin, e.rin;
End If;
-- Return the updated geometry
Return MDSYS.SDO_Geometry(p_geometry.sdo_gtype,
p_geometry.sdo_srid,
v_sdo_point,
p_geometry.sdo_elem_info,
v_ordinates);
EXCEPTION
WHEN NULL_GEOMETRY Then
raise_application_error(CODESYS.CONSTANTS.c_i_null_geometry,
CODESYS.CONSTANTS.c_s_null_geometry,TRUE);
RETURN p_geometry;
WHEN NULL_POINT THEN
raise_application_error(-20001,'p_point is null',true);
RETURN p_geometry;
WHEN INVALID_POSITION THEN
raise_application_error(-20001,'invalid p_position value',true);
RETURN p_geometry;
End SDO_VertexUpdate;
-- Replace point (p_position) of linestring with given point. Index is 1-based.
Function ST_VertexUpdate(p_geometry IN MDSYS.ST_Geometry,
p_old_point IN MDSYS.ST_Point,
p_new_point IN MDSYS.ST_Point )
Return MDSYS.ST_Geometry
Is
Begin
Return MDSYS.ST_GEOMETRY.FROM_SDO_GEOM(
CODESYS.GEOM.SDO_VertexUpdate(p_geometry.Get_Sdo_Geom(),
mdsys.vertex_type(p_old_point.ST_X(),
p_old_point.ST_Y(),
null, /*p_old_point.ST_Z(),*/
null, /*p_old_point.ST_M(),*/
null),
mdsys.vertex_type(p_new_point.ST_X(),
p_new_point.ST_Y(),
null, /*p_new_point.ST_Z(),*/
null, /*p_new_point.ST_M(),*/
null)));
End ST_VertexUpdate;
(At 10gR2 a point does not have Z and M values.)
Note that I continue to make extensive use of SQL in my functions. I find using SQL makes it easy to develop solutions in SQLPlus/SQL Developer and transfer into PL/SQL. It is most likely more efficient (in terms of processing resources) to manipulate the sdo_ordinate array directly in PL/SQL but the speed of development (and understanding to ordinary users) is most likely less than via straight SQL. I will investigate this aspect of algorithm development in a later article.
Now, let’s run through some examples.
-- Update 2D null point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(2001,null,sdo_point_type(null,null,null),null,null),
mdsys.vertex_type(null,null,null,null,1),
mdsys.vertex_type(4.555,4.666,null,null,1)
) as point
from dual;
POINT
------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2001,null,MDSYS.SDO_POINT_TYPE(4.555,4.666,null),null,null)
1 rows selected
-- Update 3D null point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(2001,null,sdo_point_type(null,null,null),null,null),
mdsys.vertex_type(null,null,null,null,1),
mdsys.vertex_type(4.555,4.666,10,null,1)
) as point
from dual;
POINT
------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2001,null,MDSYS.SDO_POINT_TYPE(4.555,4.666,10),null,null)
1 rows selected
-- Update 4D null point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(4001,null,null,
mdsys.sdo_elem_info_array(1,1,1),
mdsys.sdo_ordinate_array(null,null,null,null)),
mdsys.vertex_type(null,null,null,null,1),
mdsys.vertex_type(4.555,4.666,5,6,1)
) as point
from dual;
POINT
-------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(4001,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1,1),MDSYS.SDO_ORDINATE_ARRAY(4.555,4.666,5,6))
1 rows selected
-- Update first point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(2002,null,null,sdo_elem_info_array(1,2,1),
sdo_ordinate_array(1.12345,1.3445,2.43534,2.03998398,3.43513,3.451245)),
mdsys.vertex_type(1.12345,1.3445,null,null,1),
mdsys.vertex_type(29.8,29.9,99,null,1)) as point
from dual;
POINT
-------------------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2002,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(29.8,29.9,2.43534,2.03998398,3.43513,3.451245))
1 rows selected
-- Update any point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(3002,null,null,sdo_elem_info_array(1,2,1),
sdo_ordinate_array(1.12345,1.3445,9,2.43534,2.03998398,9,3.43513,3.451245,9)),
mdsys.vertex_type(2.43534,2.03998398,9,null,1),
mdsys.vertex_type(29.8,29.9,99,null,1)) as point
from dual;
POINT
----------------------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(3002,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(1.12345,1.3445,9,29.8,29.9,99,3.43513,3.451245,9))
1 rows selected
-- Update last point
select geom.SDO_VertexUpdate(mdsys.SDO_Geometry(2001,null,null,sdo_elem_info_array(1,2,1),
sdo_ordinate_array(1.12345,1.3445,2.43534,2.03998398,3.43513,3.451245)),
mdsys.vertex_type(3.43513,3.451245,10,null,1),
mdsys.vertex_type(29.8,29.9,99,null,1)) as point
from dual;
POINT
-----------------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2001,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(1.12345,1.3445,2.43534,2.03998398,29.8,29.9))
1 rows selected
--Change first point in a OGC 2D single linestring
SELECT GEOM.ST_VertexUpdate(a.linestring,a.old_point,a.new_point).Get_WKT() as st_geom
FROM (SELECT mdsys.OGC_LineStringFromText('LINESTRING(1.12345 1.3445,2.43534 2.03998398,3.43513 3.451245)') as linestring,
mdsys.OGC_PointFromText('POINT(3.43513 3.451245)') as old_point,
mdsys.OGC_PointFromText('POINT(29.8 29.9)') as new_point
FROM dual) a;
ST_GEOM
----------------------------------------------------------
LINESTRING (1.12345 1.3445, 2.43534 2.03998398, 29.8 29.9)
1 rows selected
-- Update the last point in a simple polygon (note result is correct, as against SDO_SetPoint)
select Geom.SDO_VertexUpdate(b.the_geom,
mdsys.vertex_type(2,2,null,null,1),
mdsys.vertex_type(29,29,null,null,1)) as setGeom
from (select sdo_geometry('POLYGON((2 2, 2 7, 12 7, 12 2, 2 2))',NULL) as the_geom
from dual
) b;
SETGEOM
--------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(29,29,2,7,12,7,12,2,29,29))
1 rows selected
-- Update first point of complex polygon
select Geom.SDO_VertexUpdate(
SDO_GEOMETRY(2003,null,null,
SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,2,5,2,1),
SDO_ORDINATE_ARRAY(-0.175,9.998,-0.349,9.994,-0.523,9.986,0,0,-0.175,9.998)),
mdsys.vertex_type(-0.175,9.998,null,null,1),
mdsys.vertex_type(1,1,null,null,1)) as UpdateGeom
from dual;
UPDATEGEOM
--------------------------------------------------------------------------------------------------------------------------------------------------
MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,2,5,2,1),MDSYS.SDO_ORDINATE_ARRAY(1,1,-0.349,9.994,-0.523,9.986,0,0,1,1))
1 rows selected
-- MultiPolygon: Update first/last point in second geometry
select Geom.SDO_VertexUpdate(b.the_geom,
mdsys.vertex_type(20,20,null,null,1),
mdsys.vertex_type(21,21,null,null,1)).Get_WKT() as UpdateGeom
from (select sdo_geometry('MULTIPOLYGON(((2 2, 2 7, 12 7, 12 2, 2 2)), ((20 20, 20 70, 120 70, 120 20, 20 20)) )',NULL) as the_geom
from dual
) b;
UPDATEGEOM
-----------------------------------------------------------------------------------------------------------------------------
MULTIPOLYGON (((2.0 2.0, 2.0 7.0, 12.0 7.0, 12.0 2.0, 2.0 2.0)), ((21.0 21.0, 20.0 70.0, 120.0 70.0, 120.0 20.0, 21.0 21.0)))
1 rows selected
Conclusion
I have to admin that, having implemented the PostGIS *_SetPoint functions and the IBM *_VertexUpdate functions, I think that the IBM versions are far better than the PostGIS ones because one does not have to worry about the complexity of start/end points for polygon geometry elements (inner and outer rings).
I hope this article is of use to someone.
Documentation
- GeoRaptor 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