Filtering Rings (Oracle Spatial)

 Note: This article was edited on 1st March 2012 to cope with situation where all inner rings are filtered away by the change to using SDO_AGGR_SET_UNION which is not subject to the same licensing as SDO_AGGR_UNION. The changes are reflected only in the Filter_Rings PL/SQL function at the end of this article. In addition, a pure PL/SQL version of the Filter_Rings function is presented that is SDO_DIFFERENCE free as well.

I recently wrote an article Filtering Rings in Polygon ( PostGIS ).

How can we do this for Oracle?

If you recall I have written an article on How to extract elements from the result of an sdo_intersection of two polygons.

Well we can combine these two generate a solution to the filtering problem.

First, note how we can construct an Oracle SDO_GEOMETRY from a WKT input:

 SELECT mdsys.sdo_geometry('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))',NULL) AS geom
   FROM DUAL;
 --
 GEOM
 ------------------------------------------------------------------------------------------------
 MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1,21,2003,1),MDSYS.SDO_ORDINATE_ARRAY(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))
 --
 1 ROWS selected

Now there are a few approaches we could take all on the basis of the SDO_UTIL.EXTRACT function and the GetNumRings function I wrote for the article Elem_Info_Array Processing: An alternative to SDO_UTIL.GetNumRings and querying SDO_ELEM_INFO itself

1. Generate_Series

Firstly, in the article generate_series: an Oracle implementation in light of SQL Design Patterns in which I showed a number of methods for generating an integer series of numbers. We can use this function with the GetNumRings and Extract functions as follows.

 SELECT GetNumRings(mdsys.sdo_geometry('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))',NULL)) AS numRings 
   FROM DUAL;
 --
 NUMRINGS
 --------
 3
 --
 1 rows selected
 SELECT b.column_value AS ring_no, 
        Sdo_Util.EXTRACT(mdsys.sdo_geometry('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))',NULL),1,b.column_value) AS ring
  FROM TABLE(geom.generate_series(1,
                                  sdo_util.GetNumRings(mdsys.sdo_geometry('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))',NULL)),
                                  1)) b;
 --
 RING_NO RING
 ------- ------------------------------------------------------------------------------------------------------------------------------
 1       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
 2       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(10,10,11,10,11,11,10,11,10,10))
 3       MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(5,5,7,5,7,7,5,7,5,5))
 --
 3 ROWS selected

To filter the rings and rebuild we can do the following:

 SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)).Get_WKT() AS filtered_geom
   FROM (SELECT b.column_value AS ring_no, 
                Sdo_Util.EXTRACT(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL),1,b.column_value) AS ring
           FROM TABLE(geom.generate_series(
                           1,
                           GetNumRings(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL)),
                           1)) b
         ) c
   WHERE c.ring_no = 1 
      OR ( c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2);
 --
 FILTERED_GEOM
 -------------------------------------------------------------
 POLYGON ((20.0 20.0, 0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0))
 --
 1 ROWS selected

While we end up with a POLYGON you will note that the inner ring has been dissolved into the polygon. This is because we used the SDO_AGGR_UNION aggregate operator. There is no other useful aggregate operator that we can use that will maintain any inner rings. So, we will have to resort to “uglier” SQL to achieve our aim.

In the Oracle “toolkit” there is the SDO_DIFFERENCE geoprocessing operator. This will do what we want but it is not an aggregate operator. The operator acts on two single SDO_GEOMETRY objects so will will have to “serve up” to this operator the outer ring separately from the inner rings (which can be aggregated).

 SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
  FROM (SELECT sdo_util.EXTRACT(mdsys.sdo_geometry('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))',NULL),1,1) AS outer_ring
   3           FROM DUAL) a,
   4        (SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
   5           FROM (SELECT b.column_value as ring_no, 
   6                        Sdo_Util.EXTRACT(mdsys.sdo_geometry('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))',NULL),1,b.column_value) AS ring
   7                   FROM TABLE(geom.generate_series(
   8                           1,
   9                           GetNumRings(mdsys.sdo_geometry('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))',NULL)),
  10                           1)) b
  11                ) c
  12          WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
  13*       ) d;
 --
 FILTERED_GEOM
 -----------------------------------------------------------------------------------------------------------
 POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
 --
 1 ROWS selected

Which is exactly what we want.

Put in a function….

2. Use of Geom.ExtractElementsPiped

But what if you don’t want to use generate_series? I have encapsulated element extraction into the the ExplodeGeometry or ExtractElementsPiped functions in my GEOM package. We can use these to do the function extraction as follows:

 SELECT b.geometry
  FROM TABLE(geom.ExtractElementsPiped(mdsys.sdo_geometry('POLYGON((10 10,10 20,20 20,20 10,10 10),(0 0,0 1,1 1,1 0,0 0),(5 5,5 7,7 7,7 5,5 5))',NULL),0) ) b;
 --
 GEOMETRY
 ------------------------------------------------------------------------------------------------------------------------------
 MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
 MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(10,10,11,10,11,11,10,11,10,10))
 MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(5,5,7,5,7,7,5,7,5,5))
 --
 3 ROWS selected

But, as we learned above, we cannot just filter these polygons and reconstruct the original polygon with SDO_AGGR_UNION. The final solution for this approach based on the last SQL of section 1 above would be:

 SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
  FROM (SELECT sdo_util.EXTRACT(mdsys.sdo_geometry('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))',NULL),1,1) AS outer_ring
          FROM DUAL) a,
       (SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
          FROM (SELECT rownum AS ring_no, 
                       b.geometry AS ring
                  FROM TABLE(geom.ExtractElementsPiped(mdsys.sdo_geometry('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))',NULL),0)) b
               ) c
         WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
       ) d;
 --
 FILTERED_GEOM
 -----------------------------------------------------------------------------------------------------------
 POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
 --
 1 ROWS selected

3. Use of Hierarchical Query

Generate_Series provides for a range of integer number generation and is, in many ways, overkill for what we are doing here. We can use a simple hierarchical query to generate our ring numbers for SDO_UTIL.EXTRACT. However, Generate_Series (or ExtractElementsPiped etc) is needed if you are running on 9i as the following hierarchical query doesn’t work in 9i.

The final SQL if you use a hierarchical query is as follows:

 SELECT SDO_GEOM.SDO_DIFFERENCE(a.outer_ring,d.inner_rings,0.05).Get_WKT() AS filtered_geom
  FROM (SELECT sdo_util.Extract(mdsys.sdo_geometry('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))',NULL),1,1) AS outer_ring
          FROM DUAL) a,
       (SELECT SDO_AGGR_UNION(SDOAGGRTYPE(c.ring,0.05)) AS inner_rings
          FROM (SELECT level AS ring_no, 
                       Sdo_Util.EXTRACT(mdsys.sdo_geometry('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))',NULL),1,LEVEL) AS ring
                  FROM DUAL
                CONNECT BY LEVEL <= GetNumRings(mdsys.sdo_geometry('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))',NULL) )
               ) c
         WHERE c.ring_no > 1 AND SDO_GEOM.SDO_AREA(c.ring,0.05) > 2
       ) d;
 --
 FILTERED_GEOM
 -----------------------------------------------------------------------------------------------------------
 POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0, 5.0 7.0))
 --
 1 ROWS selected

4. Encapsulate in a Function

Regardless as to the approach taken to extract the elements, it will be cleaner to encapsulate our algorithm inside a function as follows.

NOTE: This function requires my GEOM package to be able to compile and run.

 /** ----------------------------------------------------------------------------------------
 * @function  : Filter_Rings
 * @precis     : Function that allows a user to remove inner rings from a polygon/multipolygon
 * based         on an area value.
 * @version    : 1.0
 * @usage      : FUNCTION Filter_Rings(p_geometry   in mdsys.sdo_geometry,
 *                                     p_tolerance in number,
 *                                     p_area      in number,
 *                                     p_ring      in number := 0)
 *                 RETURN mdsys.sdo_geometry DETERMINISTIC;
 *               eg select Filter_Rings(mdsys.sdo_geometry('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))',null),
 *                                      10,
 *                                      0.05)
 *                    from dual;
 * @param      : p_geometry  : mdsys.sdo_geometry : Original polygon/multipolygon
 * @param      : p_area      : number : Area in square srid units below which an inner ring is removed.
 * @param      : p_tolerance : number : Standard Oracle diminfo tolerance.
 * @Param      : p_ring      : number : The number of the internal ring to be removed
 * @return    : mdsys.sdo_geometry : input geometry with any qualifying inner rings removed
 * @history    : Simon Greener - December 2008 - 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 Filter_Rings(p_geometry IN mdsys.sdo_geometry,
                       p_tolerance IN NUMBER,
                       p_area      IN NUMBER,
                       p_ring      IN NUMBER := 0)
   RETURN MDSYS.SDO_GEOMETRY
 IS
   v_geom mdsys.sdo_geometry;
   v_ok   NUMBER;
 BEGIN
   If ( p_geometry IS NULL
        or
        Mod(p_geometry.sdo_gtype,10) NOT IN (3,7) ) THEN
      raise_application_error(-20001,'p_geometry is null or is not a polygon',TRUE);
   End If;
   Begin
     -- Can only use this if Enterprise Edition and SDO
     SELECT 1
       INTO v_Ok
       FROM v$version
      WHERE banner LIKE '%Enterprise Edition%';
     SELECT 1 
       INTO v_Ok
       FROM dba_registry 
      WHERE comp_id = 'SDO';
     EXCEPTION
       WHEN OTHERS THEN
         raise_application_error(-20001,'Not licensed for use of sdo_difference/sdo_area',TRUE);
         RETURN p_geometry;
   END;
  SELECT CASE WHEN e.inner_rings IS NULL THEN a.outer_rings ELSE mdsys.SDO_GEOM.SDO_DIFFERENCE(a.outer_rings,e.inner_rings,p_tolerance) END AS filtered_geom 
    INTO v_geom
    FROM (SELECT 1 AS id, SDO_AGGR_SET_UNION(CAST(COLLECT(MDSYS.SDO_UTIL.Extract(p_geometry,level,1)) AS SDO_Geometry_Array),p_tolerance) AS outer_rings
            FROM DUAL
          CONNECT BY level <= &&defaultSchema..GEOM.getnumouterrings(p_geometry)
          ) a
          LEFT OUTER JOIN
          (SELECT 1 as id, SDO_AGGR_SET_UNION(d.geom_array, p_tolerance) AS inner_rings
             FROM (SELECT CAST(COLLECT(c.ring) AS SDO_Geometry_Array) AS geom_array, count(*) AS geomCount, p_tolerance
                     FROM (SELECT s.geom_no,
                                  level AS ring_no,
                                  MDSYS.SDO_UTIL.EXTRACT(s.geom,s.geom_no,LEVEL) AS ring
                             FROM (SELECT r.geom_no, r.geom
                                     FROM (SELECT level AS geom_no,
                                                  MDSYS.SDO_UTIL.EXTRACT(p_geometry,level) AS geom
                                             FROM DUAL
                                            CONNECT BY LEVEL <= &&defaultSchema..GEOM.getNumOuterRings(p_geometry)
                                           ) r
                                    WHERE &&defaultSchema..GEOM.GetNumInnerRings(r.geom) > 0
                                  ) s
                           CONNECT BY level <= &&defaultSchema..GEOM.GetNumRings(s.geom)
                          ) c
                    WHERE c.ring_no > 1
                      AND MDSYS.SDO_GEOM.SDO_AREA(c.ring,p_tolerance) > p_area
                      AND c.ring_no <> p_ring 
                  ) d
             WHERE d.geomcount > 0
          ) e
          ON (e.id = a.id);
   RETURN v_geom;
 END Filter_Rings;

I have not extended this function to allow it to handle multipolygons in order to to make this blog too complicated.

Testing it we get:

 WITH testGeom AS (
   SELECT mdsys.sdo_geometry('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))',NULL) AS geom
     FROM DUAL
 )
 SELECT geom.filter_rings(a.geom,0.005,2.0).Get_WKT() AS filtered_geom
   FROM testGeom a;
 --
 FILTERED_GEOM
 -----------------------------------------------------------------------------------------------------------
 POLYGON ((0.0 20.0, 0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0), (5.0 5.0, 5.0 7.0, 7.0 7.0, 7.0 5.0, 5.0 5.0))
 --
 1 ROWS selected

Or to filter out all inner rings…

 WITH testGeom AS (
   SELECT mdsys.sdo_geometry('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))',NULL) AS geom
     FROM DUAL
 )
 SELECT geom.filter_rings(a.geom,0.005,10.0).Get_WKT() AS filtered_geom
   FROM testGeom a;
 --
 FILTERED_GEOM
 -----------------------------------------------------------
 POLYGON ((0.0 0.0, 20.0 0.0, 20.0 20.0, 0.0 20.0, 0.0 0.0))
 --
 1 ROWS selected

Which are the answers we are after.

Non-SQL, Locator Friendly Version

Two problems with the above function arise:

  • SQL inside functions is slower than native PL/SQL.
  • The above uses SDO_GEOM.SDO_DIFFERENCE which is an advanced Spatial function requiring expensive licensing. (Though one could use my SC4O.ST_Difference function.)

So, can we create a function that is free of SQL and could be run on Locator?

Yes, here is how we can do this:

 /** ----------------------------------------------------------------------------------------
 * @function  : Filter_Rings
 * @precis     : Function that allows a user to remove inner rings from a polygon/multipolygon
 *               based on an area value.
 * @version    : 1.0
 * @usage      : FUNCTION Filter_Rings(p_geometry   in mdsys.sdo_geometry,
 *                                     p_tolerance in number,
 *                                     p_area      in number,
 *                                     p_ring      in number := 0)
 *                 RETURN mdsys.sdo_geometry DETERMINISTIC;
 *               eg select Filter_Rings(mdsys.sdo_geometry('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))',null),
 *                                      10,
 *                                      0.05)
 *                    from dual;
 * @param      : p_geometry  : mdsys.sdo_geometry : Original polygon/multipolygon
 * @param      : p_area      : number : Area in square srid units below which an inner ring is removed.
 * @param      : p_tolerance : number : Standard Oracle diminfo tolerance.
 * @Param      : p_ring      : number : The number of the internal ring to be removed
 * @return    : mdsys.sdo_geometry : input geometry with any qualifying inner rings removed
 * @history    : Simon Greener - December 2008 - 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 Filter_Rings(p_geometry  IN mdsys.sdo_geometry,
                        p_tolerance IN NUMBER,
                        p_area      IN NUMBER,
                        p_ring      IN NUMBER := 0)
   RETURN MDSYS.SDO_GEOMETRY
 IS
   v_vertices         mdsys.vertex_set_type;
   v_ords             mdsys.sdo_ordinate_array := NEW mdsys.sdo_ordinate_array(NULL);
   v_num_dims         pls_integer;
   v_num_elems        pls_integer;
   v_actual_etype     pls_integer;
   v_ring_elem_count  pls_integer := 1;
   v_ring             mdsys.sdo_geometry;
   v_num_rings        pls_integer;
   v_geom mdsys.sdo_geometry;
   v_ok   NUMBER; 
 BEGIN
   If ( p_geometry IS NULL OR MOD(p_geometry.sdo_gtype,10) NOT IN (3,7) ) THEN
      raise_application_error(-20001,'p_geometry is null or is not a polygon',TRUE);
   End If;
   If ( DBMS_DB_VERSION.VERSION <= 10 ) THEN
       Begin
         -- Can only use this if Enterprise Edition and SDO
         SELECT 1
           INTO v_Ok
           FROM v$version
          WHERE banner LIKE '%Enterprise Edition%';
         SELECT 1 
           INTO v_Ok
           FROM dba_registry 
          WHERE comp_id = 'SDO';
         EXCEPTION
           WHEN OTHERS THEN
             raise_application_error(-20001,'Not licensed for use of sdo_area',TRUE);
             RETURN p_geometry;
       End;
   End If;
   /* The processing below assumes the structure of a polygon/multipolygon 
      is correct and passes sdo_geom.validate_geometry */
   v_num_dims  := p_geometry.get_dims();
   v_num_elems := MDSYS.SDO_UTIL.GETNUMELEM(p_geometry); -- Gets number of 1003 geometries
   <<all_elements>>
   FOR v_elem_no IN 1..v_num_elems LOOP
       -- Need to process and check all inner rings
       --
       -- Process all rings in the extracted single - 2003 - polygon
       v_num_rings := &&defaultSchema..GEOM.GetNumRings(MDSYS.SDO_UTIL.EXTRACT(p_geometry,v_elem_no),0);
       <<All_Rings>>
       FOR v_ring_no IN 1..v_num_rings LOOP
           v_ring := MDSYS.SDO_UTIL.EXTRACT(p_geometry,v_elem_no,v_ring_no);
           IF ( v_ring IS NOT NULL ) THEN
              If ( v_ring_no = 1 ) THEN -- outer ring
                v_geom := CASE WHEN ( v_geom IS NULL ) THEN v_ring ELSE mdsys.sdo_util.APPEND(v_geom,v_ring) END;
              Else -- Inner Ring
                 IF ( mdsys.sdo_geom.sdo_area(v_ring,p_tolerance) > p_area ) THEN
                    IF ( v_ring.sdo_ordinates.COUNT <> v_num_dims * 2 ) THEN -- If optimized rectangle don't swap
                       v_ring := &&defaultSchema..GEOM.Reverse_Geometry(v_ring);
                    End If;
                    v_ring.sdo_elem_info(2) := v_ring.sdo_elem_info(2) + 1000;
                    v_geom := mdsys.sdo_util.APPEND(v_geom,v_ring);
                 End If;
              End If;
           END IF;
       END LOOP All_Rings;
   END LOOP all_elements;
   RETURN v_geom;
 END Filter_Rings;

NOTE: This function requires my GEOM package to be able to compile and run: mdsys.SDO_GEOM.SDO_AREA is only an issue for versions before 10g.

Testing we get:

 SELECT id, filtered_geom
   FROM (
 SELECT 1 AS id,filter_rings(a.mgeom,0.005,2.0)   AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 2 AS id,filter_rings(a.mgeom,0.005,200.0) AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 3 AS id,filter_rings(a.geom,0.005,2.0)    AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 4 AS id,filter_rings(a.geom,0.005,10.0)   AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 5 AS id,filter_rings(a.cgeom,0.005,2.0)   AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 6 AS id,filter_rings(a.cmgeom,0.005,2.0)  AS filtered_geom FROM testGeoms a UNION ALL
 SELECT 7 AS id,filter_rings(a.twosq,0.005,3.0)   AS filtered_geom FROM testGeoms a
 )
 ORDER BY id;
 -- Results
 ID FILTERED_GEOM
 -- ---------------------------------------------------------------------------------------------------------------------
 1  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1,21,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,5,5,5,7,7,7,7,5,5,5,40,40,50,40,50,50,40,50,40,40))
 2  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,40,40,50,40,50,50,40,50,40,40))
 3  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1,11,2003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0,5,5,5,7,7,7,7,5,5,5))
 4  SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1),SDO_ORDINATE_ARRAY(0,0,20,0,20,20,0,20,0,0))
 5  SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,1,5,2,2),SDO_ORDINATE_ARRAY(6,10,10,1,14,10,10,14,6,10))
 6  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,1,5,2,2,11,2005,2,11,2,2,15,2,1,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))
 7  SDO_GEOMETRY(2007,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,3,5,1003,1,15,2003,3),SDO_ORDINATE_ARRAY(-10,-10,60,60,0,0,50,0,50,50,0,50,0,0,10,10,12,12))
 --
  7 ROWS selected

There is an issue with the third resulting polygon as the original polygon was 2003. I will see if I can track down why this is occurring.

I hope this helpful to someone.