Split Sdo_Geometry Linestring at a known point

I recently received an email from someone asking me:

Just wondering if you could shed some light on a problem I have. In oracle using PLSQL I need to cut a polyline at two know (sic) points (not necessarly vertices and the line may be not be straight) and copy the cut line into another table. I would prefer not to go to linear referencing to do this. I cannot seem to find a function that will cut polylines. I can find the concatinate (sic) functions but this does not help much.

I’m new to oracal (sic) spatial but have a very extensive GIS background mostly ESRI and was suprised that there was not a built in function to split a line.

Any help you could offer would be greatly appreciated and really help me out.

It is true that there there is no function in Oracle Locator/Spatial that can split a linestring at a known point and return the “halfs” as separate linestrings. The closest LRS function in LRS is:

SDO_LRS.SPLIT_GEOM_SEGMENT(
    geom_segment IN SDO_GEOMETRY,
    split_measure IN NUMBER,
    segment_1 OUT SDO_GEOMETRY,
    segment_2 OUT SDO_GEOMETRY);

But note that it does not take a point sdo_geometry but, suprise, surprise, a measure! (Also it is a procedure and not a function.)

My correspondant happened to be lucky as I had just been playing around with some of the vector functions in my GEOM PL/SQL package to find the nearest vector (segment of a linestring) closest to other objects eg:

codesys@XE> select rownum as id,
  2         b.startcoord.x as x1,
  3         b.startcoord.y as y1,
  4         b.endcoord.x as x2,
  5         b.endcoord.y as y2,
  6         sdo_geom.sdo_distance(mdsys.sdo_geometry(2002,NULL,NULL,
  7                                        mdsys.sdo_elem_info_array(1,2,1),
  8                                        mdsys.sdo_ordinate_array(b.startcoord.x,b.startcoord.y,b.endcoord.x,b.endcoord.y)),
  9                               mdsys.sdo_geometry(2001,null,
 10                                        mdsys.sdo_point_type(380326.792,5167489.29,NULL),NULL,NULL),0.005) as linedist 
 11    from projline2d a,
 12         table(codesys.geom.getpipedvector2d(a.geom)) b
 13* where linetype = 'VERTEX'
codesys@XE> / 
        ID         X1         Y1         X2         Y2   LINEDIST
---------- ---------- ---------- ---------- ---------- ----------
         1 380326.792 5167089.29 380326.792 5167889.29          0
         2 380326.792 5167889.29 380826.792 5167889.29    399.996
         3 380826.792 5167889.29 380126.792 5167489.29 99.2243147

I realised that this was the basis of what my correspondant wanted so I created a procedure called Split and have integrated it into my geom PL/SQL package. The procedure’s type signature is as follows:

procedure Split( p_line in mdsys.sdo_geometry,
                 p_point in mdsys.sdo_geometry,
                 p_tolerance in number,
                 p_out_line1 out mdsys.sdo_geometry,
                 p_out_line2 out mdsys.sdo_geometry )

This procedure can be called as follows:

codesys@XE> declare
  2    v_line mdsys.sdo_geometry := mdsys.sdo_geometry(2006,NULL,NULL, mdsys.sdo_elem_info_array(1,2,1,5,2,1), mdsys.sdo_ordinate_array(0,0,5,5,5,5,10
,10));
  3    v_point mdsys.sdo_geometry := mdsys.sdo_geometry(2001,null,mdsys.sdo_point_type(2.5,2.5,NULL),NULL,NULL);
  4    v_oline1 mdsys.sdo_geometry;
  5    v_oline2 mdsys.sdo_geometry;
  6    cursor c_vectors(p_geometry in mdsys.sdo_geometry) Is
  7    select rownum as id,
  8           b.x, b.y
  9      from table(sdo_util.getvertices(p_geometry)) b;
 10    procedure printgeom(p_text in varchar2,p_geom in mdsys.sdo_geometry)
 11    Is
 12      v_geom mdsys.sdo_geometry;
 13    Begin
 14      dbms_output.put_line(p_text || ': Number of Elements ' || sdo_util.GetNumElem(p_geom));
 15      for v_element in 1..sdo_util.GetNumElem(p_geom) loop
 16        v_geom := sdo_util.Extract(p_geom,v_element); -- Extract element with all sub-elements
 17        dbms_output.put_line('Element ' || v_element || ' Vertices');
 18        for rec in c_vectors(v_geom) loop
 19          dbms_output.put_line(rec.id||','||rec.x||','||rec.y);
 20        end loop;
 21      end loop;
 22    End PrintGeom;
 23  begin
 24    codesys.geom.split(v_line, v_point,0.005,v_oline1,v_oline2);
 25    PrintGeom('Outline1',v_oline1);
 26    PrintGeom('Outline2',v_oline2);
 27  end;
 28 /
Outline1: Number of Elements 1
Element 1 Vertices
1,0,0
2,2.5,2.5
Outline2: Number of Elements 2
Element 1 Vertices
1,2.5,2.5
2,5,5
Element 2 Vertices
1,5,5
2,10,10
PL/SQL procedure successfully completed.

Now the Split procedure will handle single (2002) and multi-part (2006) linestring geometries. It will also handle situations where the point:

  • Falls on a vertex
  • Falls on the line string between two vertices
  • Does not fall on the linestring at all (A position on the line at the nearest projection of the point to the line is computed and the linestring is split at this point. The position is computed by simple distance ratios and so will not be accurate for geodetic data.)

Function not Procedure

For those who want a function instead of a procedure so that the function can be used in SQL, I have added a Split function that returns a set of sdo_geometries as follows.

Function Split( p_line in mdsys.sdo_geometry,
                p_point in mdsys.sdo_geometry,
                p_tolerance in number )
  Return codesys.GeometrySetType pipelined;

This function can be used with a table function in a SQL SELECT statement as follows:

codesys@XE> select b.*
  2    from table( codesys.geom.split( mdsys.sdo_geometry(2006,NULL,NULL, mdsys.sdo_elem_info_array(1,2,1,5,2,1), mdsys.sdo_ordinate_array(0,0,5,5,5,5,10,10)),
  3                                    mdsys.sdo_geometry(2001,null,mdsys.sdo_point_type(2.5,2.5,NULL),NULL,NULL),
  4                               0.005) ) b
  5  /
 GEOMETRY(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES)
 -----------------------------------------------------------------------------------------------------------------------------------
 SDO_GEOMETRY(2002, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 2, 1), SDO_ORDINATE_ARRAY(0, 0, 2.5, 2.5))
 SDO_GEOMETRY(2006, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 2, 1, 5, 2, 1), SDO_ORDINATE_ARRAY(2.5, 2.5, 5, 5, 5, 5, 10,10))

Two caveats:

  • Split does not handle compound linestrings with 3 point arcs. Any results from calling Split with such an arc will not be correct as no check is made as to arc existance.
  • Results for geodetic data will be correct only where the point actually falls on the line: all other situations will result in a rough approximation.

I will describe the algorithm I have created in another posting: I need to prepare some images that show how it works as these will be simpler than laborious text!

Finally, if there are any errors in the function please let me know and I will endeavour to correct them.

I hope the function is useful.