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)
Elem_Info_Array Processing: An alternative to SDO_UTIL.GetNumRings and querying SDO_ELEM_INFO itself
This article is in two parts: 1) on SDO_UTIL.GetNumRings; 2) and an outline of a function to return the element info array in its normalised form.
1. SDO_UTIL.GETNUMRINGS
Oracle Spatial, well at release 10g, has an undocumented function in the SDO_UTIL package called GetNumRings.
FUNCTION GETNUMRINGS RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- GEOM SDO_GEOMETRY IN
This function takes a polygon geometry object and returns the number of rings that compose it. So, if I provide it a simple xD polygon, it will tell me how many rings (outer and inner) compose it. Here is an example:
select mdsys.sdo_util.getnumrings(SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,3),SDO_ORDINATE_ARRAY(1,1,10,10))) as ringcount
from dual;
RINGCOUNT
----------
1
1 rows selected
I had a quick look at this function and found that it does not handle compound rings.
select a.polytype,sdo_util.getnumrings(a.geom) as ringcount,a.geom.sdo_elem_info from projpoly2d a; POLYTYPE RINGCOUNT GEOM.SDO_ELEM_INFO ---------------------------------------- ---------------------- ---------------------------- VERTEXWITHHOLE 2 NUMBER(1,1003,1,77,2003,1) VERTEXNOHOLE 1 NUMBER(1,1003,1) VERTEXNOHOLE 1 NUMBER(1,1003,1) COMPOUNDOUTERSHELL 0 NUMBER(1,1005,2,1,2,1,5,2,2) VERTEXNOHOLE 1 NUMBER(1,1003,1) VERTEXWITHARCNOHOLE 0 NUMBER(1,1005,2,1,2,1,5,2,2) NORMALISEDRECTANGLENOHOLE 1 NUMBER(1,1003,3) CURVEPOLYWITHHOLE 2 NUMBER(1,1003,4,7,2003,4) 8 rows selected
If you are worried about this, try just using straight SQL.
select polytype,count(b.column_value)
from projpoly2d a,
table(a.geom.sdo_elem_info) b
where b.column_value in (1003,1005,2003,2005)
group by polytype, b.column_value;
POLYTYPE COUNT(B.COLUMN_VALUE)
---------------------------------------- ----------------------
VERTEXNOHOLE 3
COMPOUNDOUTERSHELL 1
NORMALISEDRECTANGLENOHOLE 1
VERTEXWITHHOLE 1
CURVEPOLYWITHHOLE 1
VERTEXWITHHOLE 1
CURVEPOLYWITHHOLE 1
VERTEXWITHARCNOHOLE 1
8 rows selected
Or, as I have not published in this article how to construct and populate the projpoly2d table, here is something more explicit you can try yourself:
select sdo_util.getnumrings(a.geom) as ringcount
from (SELECT SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)) as geom
FROM dual) a;
RINGCOUNT
----------------------
0
1 rows selected
Which is wrong. So, let’s try pure SQL:
select count(*) as ringcount
from TABLE(SELECT SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)).sdo_elem_info
FROM dual) a
where a.column_value in (1003,1005,2003,2005);
RINGCOUNT
----------------------
3
1 rows selected
Which is correct.
The SQL examples above are simplified and will not produce a correct in all cases. The reason for this is that the predicate:
... where b.column_value in (1003,1005,2003,2005) ...
Does not discriminate between the ordinate offset, etype and interpretation triplet that is a standard entry for sdo_elem_info_array. So, if an ordinate offset was either 1003, 1005, 2003 or 2005 it would incorrectly add to the count of rings.
The way to correctly query the sdo_elem_info_array is via a SQL pivot as follows:
SELECT count(*) as ringcount
FROM (SELECT e.id,
e.etype,
e.offset,
e.interpretation
FROM (SELECT trunc((rownum - 1) / 3,0) as id,
sum(case when mod(rownum,3) = 1 then sei.column_value else null end) as offset,
sum(case when mod(rownum,3) = 2 then sei.column_value else null end) as etype,
sum(case when mod(rownum,3) = 0 then sei.column_value else null end) as interpretation
FROM TABLE(SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)).sdo_elem_info
) sei
GROUP BY trunc((rownum - 1) / 3,0)
) e
) i
WHERE i.etype in (1003,1005,2003,2005);
RINGCOUNT
----------
3
1 rows selected
To create your own function – in case you are worried about use of an undocumented Oracle function – using the above approach, is fairly trivial:
create or replace Function GetNumRings( p_geometry in mdsys.sdo_geometry )
Return Number Deterministic
Is
v_ring_count number := 0;
Begin
SELECT count(*) as ringcount
INTO v_ring_count
FROM (SELECT e.id,
e.etype,
e.offset,
e.interpretation
FROM (SELECT trunc((rownum - 1) / 3,0) as id,
sum(case when mod(rownum,3) = 1 then sei.column_value else null end) as offset,
sum(case when mod(rownum,3) = 2 then sei.column_value else null end) as etype,
sum(case when mod(rownum,3) = 0 then sei.column_value else null end) as interpretation
FROM TABLE(p_geometry.sdo_elem_info) sei
GROUP BY trunc((rownum - 1) / 3,0)
) e
) i
WHERE i.etype in (1003,1005,2003,2005);
Return v_ring_count;
End GetNumRings;
/
SHOW ERRORS
And, testing it:
SELECT GetNumRings(NULL) As RingCount FROM DUAL;
RINGCOUNT
----------------------
0
1 rows selected
SELECT GetNumRings(SDO_GEOMETRY(2001,NULL,SDO_POINT_TYPE(10,10,10),NULL,NULL)) As RingCount FROM DUAL;
RINGCOUNT
----------------------
0
1 rows selected
SELECT GetNumRings(SDO_GEOMETRY(2002,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,2,1),SDO_ORDINATE_ARRAY(1,1,10,10))) As RingCount FROM DUAL;
RINGCOUNT
----------------------
0
1 rows selected
SELECT GetNumRings(SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,3),SDO_ORDINATE_ARRAY(1,1,10,10))) As RingCount FROM DUAL;
RINGCOUNT
----------------------
1
1 rows selected
SELECT GetNumRings(SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,3,5,2003,3),SDO_ORDINATE_ARRAY(1,1,100,100,20,20,40,40))) As RingCount FROM DUAL;
RINGCOUNT
----------------------
2
1 rows selected
SELECT GetNumRings(SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)
)
)
as RingCount
FROM DUAL;
RINGCOUNT
----------------------
3
1 rows selected
Note that the function did not need EXCEPTION handling for NULL or non-polygon geometries as the answer returned from the SQL is correct.
2. Function Returning Normalised Element Info Array
The Oracle sdo_geometry ordinates and element info arrays are simple, one dimensional (denormalised) arrays that, for the newcomer is, strangly, a little daunting. But we can approach them more easily. Oracle 10g has the SDO_UTIL.GetNumVertices function that returns a more natrual MDSYS.Vertex_Type (see documentation). But there are not many functions that present the elements of the sdo_elem_info_array in a normalised form.
To do so, we will need to write a function that can be used in a query. I prefer PIPELINE functions in this case as they are more efficient on memory use and are more “native” to the way the Oracle database itself works.
Let’s start with a traditional coding to access the SDO_ELEM_INFO_ARRAY varray.
First we need to create an appropriate data types:
create or replace type t_ElemInfoType As Object (
offset NUMBER,
etype NUMBER,
interpretation NUMBER);
create or replace type T_ElemInfoSetType as Table of T_ElemInfoType;
Now we can code a function, in the traditional way, that returns many (a set) of t_ElemInfoTypes.
Create Or Replace Function GetElemInfo(
p_geometry in mdsys.sdo_geometry)
Return T_ElemInfoSetType pipelined
Is
v_elements number;
Begin
If ( p_geometry is not null ) Then
If ( p_geometry.sdo_elem_info is not null) Then
v_elements := ( ( p_geometry.sdo_elem_info.COUNT / 3 ) - 1 );
<<element_extraction>>
for v_i IN 0 .. v_elements LOOP
PIPE ROW ( T_ElemInfoType(
p_geometry.sdo_elem_info(v_i * 3 + 1),
p_geometry.sdo_elem_info(v_i * 3 + 2),
p_geometry.sdo_elem_info(v_i * 3 + 3) ) );
end loop element_extraction;
End If;
End If;
Return;
End GetElemInfo;
Which we can use as follows:
SELECT e.*
FROM TABLE(GetElemInfo(SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)
)
)
) e;
OFFSET ETYPE INTERPRETATION
---------------------- ---------------------- ----------------------
1 1005 2
1 2 1
5 2 2
11 2005 2
11 2 1
15 2 2
21 1005 2
21 2 1
25 2 2
9 rows selected
Now, let’s re-write the function using the SQL approach we introduced in part 1.
Create Or Replace Function GetElemInfo(
p_geometry in mdsys.sdo_geometry)
Return T_ElemInfoSetType pipelined
Is
v_elements number;
CURSOR c_elements( p_geometry in mdsys.sdo_geometry) Is
SELECT e.id,
e.etype,
e.offset,
e.interpretation
FROM (SELECT trunc((rownum - 1) / 3,0) as id,
sum(case when mod(rownum,3) = 1 then sei.column_value else null end) as offset,
sum(case when mod(rownum,3) = 2 then sei.column_value else null end) as etype,
sum(case when mod(rownum,3) = 0 then sei.column_value else null end) as interpretation
FROM TABLE(p_geometry.sdo_elem_info) sei
GROUP BY trunc((rownum - 1) / 3,0)
) e;
Begin
If ( p_geometry is not null ) Then
If ( p_geometry.sdo_elem_info is not null) Then
<<element_extraction>>
for rec IN c_elements(p_geometry) LOOP
PIPE ROW ( T_ElemInfoType(
rec.offset,
rec.etype,
rec.interpretation ) );
end loop element_extraction;
End If;
End If;
Return;
End GetElemInfo;
Again, testing:
SELECT e.*
FROM TABLE(GetElemInfo(SDO_GEOMETRY(2007, -- two-dimensional multi-part polygon with hole
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1005,2, 1,2,1, 5,2,2,
11,2005,2, 11,2,1, 15,2,2,
21,1005,2, 21,2,1, 25,2,2),
SDO_ORDINATE_ARRAY( 6,10, 10,1, 14,10, 10,14, 6,10,
13,10, 10,2, 7,10, 10,13, 13,10,
106,110, 110,101, 114,110, 110,114,106,110)
)
)
) e;
OFFSET ETYPE INTERPRETATION
---------------------- ---------------------- ----------------------
1 1005 2
1 2 1
5 2 2
11 2005 2
11 2 1
15 2 2
21 1005 2
21 2 1
25 2 2
9 rows selected
Note they are identical which shows that there are many ways to code an algorithm in PL/SQL. I personally prefer an SQL approach but others prefer a programmatic approach. One day I will compare the two (as in the above) using Tom Kyte’s RUNSTATS package and the PL/SQL PROFILER.
Till then, I hope this article is of use to someone out there.
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