Reducing 5 Vertex Polygon to Optimized Rectangle

I had occasion to convert a set of 5 vertex polygons that defined grid squares to their optimized rectangle equivalent.

To do this I created a simple function as follows.

 create or replace
 function Polygon2Rectangle( p_geometry in mdsys.sdo_geometry ) 
 return mdsys.sdo_geometry deterministic as 
    v_vertices mdsys.vertex_set_type;
    v_id       pls_integer;
    v_ords     mdsys.sdo_ordinate_array :=  new mdsys.sdo_ordinate_array(null);
 begin
    v_ords.DELETE;
    v_vertices := mdsys.sdo_util.getVertices(p_geometry);
    if ( v_vertices.COUNT <> 5 ) Then
       return p_geometry;
    End If;
    <<for_all_vectors>>
    FOR v_id in v_vertices.FIRST..(v_vertices.LAST-1) LOOP
        if ( v_id in (1,3) ) then
           v_ords.EXTEND(2);
           v_ords(v_ords.COUNT-1) := v_vertices(v_id).x;
           v_ords(v_ords.COUNT)   := v_vertices(v_id).y;
        end if;
    END LOOP for_all_vectors;
    return mdsys.sdo_geometry(p_geometry.sdo_gtype,
                               p_geometry.sdo_srid,
                               p_geometry.sdo_point,
                               mdsys.sdo_elem_info_array(1,1003,3),
                               v_ords);
 end Polygon2Rectangle;

Which can then used as follows:

 select polygon2rectangle(sdo_geometry(2003,null,null,
                                        sdo_elem_info_array(1,1003,1),
                                        sdo_ordinate_array(1,1,10,1,10,10,1,10,1,1))) 
         as optimised_rectangle
   from dual;
  
 OPTIMISED_RECTANGLE 
 -----------------------------
 MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,3),MDSYS.SDO_ORDINATE_ARRAY(1,1,10,10))

Update
On the basis of the excellent comments made by Farid Cheraghi, I have included his rectangle check (which could also have been done as a check of the diagonals of the rectangle), I have updated the function adding support for more complex sdo_geometry objects.

Note: The Oracle sdo_util.extract function, when it extracts inner rings (2003) renames them to 1003. Also, a complex element’s sdo_elem_info needs to be processed more carefully than is evident in the Oracle pl/sql functions.

 create or replace
 Function Polygon2Rectangle( p_geometry  in mdsys.sdo_geometry,
                              p_tolerance in number default 0.005 ) 
   Return mdsys.sdo_geometry DETERMINISTIC 
 AS 
    v_vertices        mdsys.vertex_set_type;
    v_ords            mdsys.sdo_ordinate_array :=  new mdsys.sdo_ordinate_array(null);
    v_num_elems       pls_integer;
    v_actual_etype    pls_integer;
    v_ring_elem_count pls_integer := 0;
    v_ring            mdsys.sdo_geometry;
    v_num_rings       pls_integer;
    v_out_geom        mdsys.sdo_geometry;
  
   Function GetETypeAt(
     p_geometry  in mdsys.sdo_geometry,
     p_element   in pls_integer)
     Return pls_integer
   Is
     v_num_elems number;
   Begin
     If ( p_geometry is not null ) Then
       v_num_elems := ( ( p_geometry.sdo_elem_info.COUNT / 3 ) - 1 );
       <<element_extraction>>
       for v_i IN 0 .. v_num_elems LOOP
          if ( (v_i+1) = p_element ) then 
             RETURN p_geometry.sdo_elem_info(v_i * 3 + 2);
         End If;
         end loop element_extraction;
     End If;
     Return NULL;
   End GetETypeAt;
  
   Function GetNumRings( p_geometry  in mdsys.sdo_geometry,
                         p_ring_type in integer /* 0 = ALL; 1 = OUTER; 2 = INNER */ )
     Return Number
   Is
     v_elements   pls_integer := 0;
     v_ring_count pls_integer := 0;
     v_etype      pls_integer;
     v_ring_type  pls_integer := case when ( p_ring_type is null OR 
                                             p_ring_type not in (0,1,2) ) 
                                      Then 0
                                      Else p_ring_type 
                                  End;
   Begin
     If ( p_geometry is not null ) Then
       v_elements := ( ( p_geometry.sdo_elem_info.COUNT / 3 ) - 1 );
       <<element_extraction>>
       FOR v_i IN 0 .. v_elements LOOP
         v_etype := p_geometry.sdo_elem_info(v_i * 3 + 2);
         If ( ( v_etype in (1003,1005,2003,2005) and 0 = v_ring_type )
           OR ( v_etype in (1003,1005)           and 1 = v_ring_type )
           OR ( v_etype in (2003,2005)           and 2 = v_ring_type ) ) Then
            v_ring_count := v_ring_count + 1;
         End If;
       END LOOP element_extraction;
     End If;
     Return v_ring_count;
   End GetNumRings;
  
 BEGIN
    IF ( p_geometry is null ) THEN
       RETURN p_geometry;
    END IF;
    -- Is polygon?
    IF ( p_geometry.get_gtype() not in (3,7) ) THEN
       RETURN p_geometry;
    END IF;
  
    v_num_elems := MDSYS.SDO_UTIL.GETNUMELEM(p_geometry);
    <<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 := 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);
            v_actual_etype := GetEtypeAt(p_geometry,(v_ring_elem_count+1));
            v_ring_elem_count := v_ring_elem_count + v_ring.sdo_elem_info.COUNT / 3;
            IF ( v_ring is not null ) THEN
              IF ( v_ring.sdo_elem_info(2) = 1003 AND
                   v_ring.sdo_elem_info(2) <> v_actual_etype ) THEN
                 -- Replace etype as Oracle extracts 2003 as 1003
                 v_ring.sdo_elem_info(2) := v_actual_etype;
              End If;
              v_vertices := mdsys.sdo_util.getVertices(v_ring);
              IF ( v_vertices.COUNT = 5 ) THEN
                -- Do the five vertices form a rectangle? 
                -- inner product of two consequent vector must equals to zero   
                IF ( (v_vertices(1).x-v_vertices(2).x) * (v_vertices(2).x-v_vertices(3).x) + 
                     (v_vertices(1).y-v_vertices(2).y) * (v_vertices(2).y-v_vertices(3).y) = 0 AND
                     (v_vertices(3).x-v_vertices(4).x) * (v_vertices(4).x-v_vertices(5).x) +
                     (v_vertices(3).y-v_vertices(4).y) * (v_vertices(4).y-v_vertices(5).y) = 0 ) THEN
                   v_ring.sdo_elem_info(1) := 1;
                   v_ring.sdo_elem_info(3) := 3;
                   v_ords.DELETE;
                   v_ords.EXTEND(4);
                   v_ords(1) := v_vertices(1).x;
                   v_ords(2) := v_vertices(1).y;
                   v_ords(3) := v_vertices(3).x;
                   v_ords(4) := v_vertices(3).y;
                   v_ring := mdsys.sdo_geometry(v_ring.sdo_gtype,
                                                v_ring.sdo_srid,
                                                v_ring.sdo_point,
                                                v_ring.sdo_elem_info,
                                                v_ords);
                END IF;
              END IF;
              IF ( v_out_geom is null ) THEN
                v_out_geom := v_ring;
              ELSE
                v_out_geom := mdsys.sdo_util.APPEND(v_out_geom,v_ring);
              END IF;
            END IF;
        END LOOP All_Rings;
    END LOOP all_elements;
    RETURN mdsys.sdo_geometry(p_geometry.sdo_gtype,
                               p_geometry.sdo_srid,
                               p_geometry.sdo_point,
                               v_out_geom.sdo_elem_info,
                               v_out_geom.sdo_ordinates);
 END Polygon2Rectangle;

Testing…..

 select sdo_geom.validate_geometry(a.geometry,0.005) as valid,
         polygon2rectangle(a.geometry) as optimised
   from (select sdo_geometry(2003,null,null, sdo_elem_info_array(1,1003,1), sdo_ordinate_array(1,1,10,1,10,10,1,10,1,1)) as geometry from dual
          union all select sdo_geometry(2007,null,null,sdo_elem_info_array(1,1003,1,11,1003,1,21,2003,3), sdo_ordinate_array(1,1,10,1,10,10,1,10,1,1, 15,15,25,15,25,25,15,25,15,15, 18,18,20,20)) as geometry from dual
          union all select sdo_geometry(2007,null,null,sdo_elem_info_array(1,1003,1,11,1003,1,21,2003,1), sdo_ordinate_array(1,1,10,1,10,10,1,10,1,1, 15,15,25,15,25,25,15,25,15,15, 16,16,16,24,24,24,24,16,16,16)) as geometry from dual
          union all select sdo_geometry(2003,null,null,sdo_elem_info_array(1,1005,2, 1,2,1, 5,2,2, 11, 2003, 1), sdo_ordinate_array(6,10, 10,1, 14,10, 10,14, 6,10, 8,8,8,12,12,12,12,8,8,8)) as geometry from dual
          union all select sdo_geometry(2003,null,null,sdo_elem_info_array(1,1003,2,11,2003,1), sdo_ordinate_array(15.0, 115.0, 20.0, 118.0, 15.0, 120.0, 10.0, 118.0, 15.0, 115.0,  13.6,116.7,13.6,118.3,16.3,118.3,16.3,116.7,13.6,116.7)) as geometry from dual
       ) a;
  
 Valid Optimised
 ----- ---------
 TRUE  MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,3),MDSYS.SDO_ORDINATE_ARRAY(1,1,10,10))
 TRUE  MDSYS.SDO_GEOMETRY(2007,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,3,5,1003,3,9,2003,3),MDSYS.SDO_ORDINATE_ARRAY(1,1,10,10,15,15,25,25,18,18,20,20))
 TRUE  MDSYS.SDO_GEOMETRY(2007,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,3,5,1003,3,9,2003,3),MDSYS.SDO_ORDINATE_ARRAY(1,1,10,10,15,15,25,25,16,16,24,24))
 TRUE  MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1005,2,1,2,1,5,2,2,11,2003,3),MDSYS.SDO_ORDINATE_ARRAY(6,10,10,1,14,10,10,14,6,10,8,8,12,12))
 TRUE  MDSYS.SDO_GEOMETRY(2003,null,null,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,2,11,2003,3),MDSYS.SDO_ORDINATE_ARRAY(15,115,20,118,15,120,10,118,15,115,13.6,116.7,16.3,118.3))

I hope this function helps someone.

Leave a Reply

Your email address will not be published. Required fields are marked *