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)
Split Oracle Sdo_Geometry Linestring at a Known Point using PL/SQL (1)
Splitting a LineString at a known point
Superceded
The implementation in this article has been superseded since the SDO_LRS package being made available to non-Spatial licensed users (see Oracle statements about this to ensure you are able to use SDO_LRS).
Another article describes how to build a function called ST_Snap based on SDO_LRS procedures and functions.
Background
A correspondent emailed me asking:
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.
Response
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 halves 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, surprise, surprise, a measure! (Also it is a procedure and not a function.)
NOTE: Later versions of Oracle Spatial provide free access to SDO_LRS which removes the need for this implementation.
My correspondent 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:
select rownum as id, b.startcoord.x as x1, b.startcoord.y as y1, b.endcoord.x as x2, b.endcoord.y as y2, sdo_geom.sdo_distance(mdsys.sdo_geometry(2002,NULL,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(b.startcoord.x,b.startcoord.y,b.endcoord.x,b.endcoord.y)), mdsys.sdo_geometry(2001,null,mdsys.sdo_point_type(380326.792,5167489.29,NULL),NULL,NULL), 0.005) as linedist from projline2d a, table(codesys.geom.getpipedvector2d(a.geom)) b where linetype = 'VERTEX' / 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 correspondent 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 )
(The source code for this function is at the end of this article.)
This procedure can be called as follows:
set serveroutput on size unlimited declare 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)); v_point mdsys.sdo_geometry := mdsys.sdo_geometry(2001,null,mdsys.sdo_point_type(2.5,2.5,NULL),NULL,NULL); v_oline1 mdsys.sdo_geometry; v_oline2 mdsys.sdo_geometry; begin geom.Split(v_line, v_point,0.005,v_oline1,v_oline2); dbms_output.put_line('Outline1: '||v_oline1.get_WKT()); dbms_output.put_line('Outline2: '||v_oline2.get_wkt()); end; / Outline1: LINESTRING (0.0 0.0, 2.5 2.5) Outline2: MULTILINESTRING ((2.5 2.5, 5.0 5.0), (5.0 5.0, 10.0 10.0)) 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:
select b.* 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)), mdsys.sdo_geometry(2001,null,mdsys.sdo_point_type(2.5,2.5,NULL),NULL,NULL), 0.005) ) b / 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 existence.
- 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 simple algorithm underlying this implementation in another posting.
Additionally, in my T_GEOMETRY Object PL/SQL code I have implemented a fuller version of split that handles circular strings and is better with geodetic calculations.
Finally, if there are any errors in the functions presented in this post please let me know and I will endeavour to correct them.
I hope the function is useful.
Here is the implementation
Procedure Split( p_line in mdsys.sdo_geometry, p_point in mdsys.sdo_geometry, p_tolerance in number, p_out_line1 out nocopy mdsys.sdo_geometry, p_out_line2 out nocopy mdsys.sdo_geometry ) As cursor c_vectors(p_geometry in mdsys.sdo_geometry) Is select rownum as id, b.startcoord.x as x1, b.startcoord.y as y1, b.endcoord.x as x2, b.endcoord.y as y2 from table(geom.ST_Vectorize(p_geometry)) b; v_gtype number; v_part number; v_element number; v_num_elements number; v_vector_id number; v_start_distance number; v_x1 number; v_y1 number; v_end_distance number; v_x2 number; v_y2 number; v_line_distance number; v_ratio number; v_geometry mdsys.sdo_geometry; v_extract_geom mdsys.sdo_geometry; v_geom_part mdsys.sdo_geometry; v_min_dist number; v_dist number; v_vector_1 dgs_retea.T_Vector := dgs_retea.T_Vector(null,null,null); v_vector_2 dgs_retea.T_Vector := dgs_retea.T_Vector(null,null,null); begin -- Check inputs If ( p_point is NULL or p_line is NULL ) Then RETURN; End If; v_gtype := MOD(p_line.Sdo_GType,10); If ( v_gtype not in (2,6) ) Then RETURN; End If; v_gtype := MOD(p_point.Sdo_GType,10); If ( MOD(p_point.Sdo_Gtype,10) <> 1 ) Then RETURN; End If; if ( p_tolerance is null ) Then RETURN; End If; -- Get nearest element in multilinestring to process -- Check number of elements in input line -- v_num_elements := mdsys.sdo_util.GetNumElem(p_line); If ( v_num_elements = 1 ) Then v_geometry := p_line; v_part := 1; Else v_min_dist := 999999999999.99999999; -- All distances should be less than this <<for_all_vertices>> FOR v_element IN 1..v_num_elements LOOP v_extract_geom := mdsys.sdo_util.Extract(p_line,v_element); -- Extract element with all sub-elements v_dist := mdsys.sdo_geom.sdo_distance(v_extract_geom,p_point,p_tolerance); If ( v_dist < v_min_dist ) Then -- dbms_output.put_line('Assigning element || ' || v_element || ' to v_geometry'); v_geometry := v_extract_geom; v_min_dist := v_dist; v_part := v_element; End If; END LOOP for_all_elements; End If; -- We have the line geometry for splitting in v_geometry -- Find the vector in v_geometry that will be split -- select id,startdist,x1,y1,enddist,x2,y2,linedist,startdist/(startdist+enddist) as ratio into v_vector_id, v_start_distance, v_x1, v_y1, v_end_distance, v_x2, v_y2, v_line_distance, v_ratio from (select rownum as id, b.startcoord.x as x1, b.startcoord.y as y1, b.endcoord.x as x2, b.endcoord.y as y2, mdsys.sdo_geom.sdo_distance( mdsys.sdo_geometry(2001,p_point.sdo_srid, mdsys.sdo_point_type(b.startcoord.x,b.startcoord.y,NULL),NULL,NULL), p_point,p_tolerance) as startDist, mdsys.sdo_geom.sdo_distance( mdsys.sdo_geometry(2001,p_point.sdo_srid, mdsys.sdo_point_type(b.endcoord.x,b.endcoord.y,NULL),NULL,NULL), p_point,p_tolerance) as endDist, mdsys.sdo_geom.sdo_distance( mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(b.startcoord.x,b.startcoord.y,b.endcoord.x,b.endcoord.y)), p_point,p_tolerance) as linedist from table(geom.ST_Vectorize(v_geometry)) b order by 8 ) where rownum < 2; -- Now do the splitting If ( v_line_distance = 0 ) Then -- dbms_output.put('provided point is on the line.'); if ( v_start_distance = 0 ) then -- dbms_output.put('point can only split line at the start of the vector'); v_vector_1 := NULL; v_vector_2 := dgs_retea.T_Vector(2,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(v_x2,v_y2,null)); elsif ( v_end_distance = 0 ) then -- dbms_output.put('point can only split line at the start of the vector'); v_vector_1 := dgs_retea.T_Vector(1,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(v_x2,v_y2,null)); v_vector_2 := NULL; else -- dbms_output.put('point is between start and end of vector'); v_vector_1 := dgs_retea.T_Vector(1,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(p_point.sdo_point.x,p_point.sdo_point.y,null)); v_vector_2 := dgs_retea.T_Vector(2,mdsys.sdo_point_type(p_point.sdo_point.x,p_point.sdo_point.y,null),mdsys.sdo_point_type(v_x2,v_y2,null)); end if; else If ( v_line_distance = v_start_distance ) then -- dbms_output.put('point can only split line at the start of the vector'); v_vector_1 := NULL; v_vector_2 := dgs_retea.T_Vector(2,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(v_x2,v_y2,null)); elsIf ( v_line_distance = v_end_distance ) then -- dbms_output.put('point can only split line at the end of the vector'); v_vector_1 := dgs_retea.T_Vector(1,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(v_x2,v_y2,null)); v_vector_2 := NULL; else -- dbms_output.put('point is between first and last vertex so split point is ratio of start/end distances'); v_vector_1 := dgs_retea.T_Vector(1,mdsys.sdo_point_type(v_x1,v_y1,null),mdsys.sdo_point_type(v_x1+(v_x2-v_x1)*v_ratio,v_y1+(v_y2-v_y1)*v_ratio,null)); v_vector_2 := dgs_retea.T_Vector(2,mdsys.sdo_point_type(v_x1+(v_x2-v_x1)*v_ratio,v_y1+(v_y2-v_y1)*v_ratio,null),mdsys.sdo_point_type(v_x2,v_y2,null)); end if; End If; -- dbms_output.put_line('Vector1: ('||v_vector_1.startcoord.x||','||v_vector_1.startcoord.y||')('||v_vector_1.endcoord.x||','||v_vector_1.endcoord.y||')'); -- dbms_output.put_line('Vector2: ('||v_vector_2.startcoord.x||','||v_vector_2.startcoord.y||')('||v_vector_2.endcoord.x||','||v_vector_2.endcoord.y||')'); -- Construct the output geometries -- Add elements in multi-part geometry before split element to first output line -- FOR v_element IN 1..(v_part-1) LOOP -- dbms_output.put_line('Adding element || ' || v_element || ' to out line 1'); v_extract_geom := mdsys.sdo_util.Extract(p_line,v_element); -- Extract element with all sub-elements p_out_line1 := mdsys.sdo_util.Append(p_out_line1,v_extract_geom); END LOOP; -- Now add the vertexes of the split geometry (in v_geometry) to the output lines -- FOR rec IN c_vectors(v_geometry) LOOP -- dbms_output.put(rec.id || ': '); If ( rec.id < v_vector_id ) Then -- dbms_output.put_line('Add to first part'); if ( rec.id = 1 ) Then -- dbms_output.put_line('Creating output line1 geometry'); v_geom_part := mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(rec.x1,rec.y1,rec.x2,rec.y2)); Else -- dbms_output.put_line('Append vector to output line1 geometry'); v_geom_part := mdsys.sdo_util.Concat_Lines(v_geom_part, mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(rec.x1,rec.y1,rec.x2,rec.y2))); End If; ElsIf ( rec.id = v_vector_id ) Then If ( v_vector_1 is not NULL ) Then if ( v_geom_part is null ) Then -- dbms_output.put_line('Creating output line1 geometry'); v_geom_part := mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(v_vector_1.startcoord.x,v_vector_1.startcoord.y, v_vector_1.endcoord.x, v_vector_1.endcoord.y)); Else -- dbms_output.put_line('Appending v_vector_1 to first part'); v_geom_part := mdsys.sdo_util.Concat_Lines(v_geom_part, mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(v_vector_1.startcoord.x,v_vector_1.startcoord.y, v_vector_1.endcoord.x, v_vector_1.endcoord.y))); End If; End If; p_out_line1 := MDSYS.SDO_UTIL.Append(p_out_line1,v_geom_part); p_out_line2 := NULL; If ( v_vector_2 is not NULL ) Then -- dbms_output.put_line(' Add v_vector_2 to p_out_line2 ready to collect up remaining vectors and elements into output line 2'); p_out_line2 := mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(v_vector_2.startcoord.x,v_vector_2.startcoord.y, v_vector_2.endcoord.x,v_vector_2.endcoord.y)); End If; Else -- dbms_output.put_line(' Add any remaining vectors to v_geom_part'); if ( p_out_line2 is null ) Then p_out_line2 := mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(rec.x1,rec.y1,rec.x2,rec.y2)); Else p_out_line2 := mdsys.sdo_util.Concat_Lines(p_out_line2, mdsys.sdo_geometry(2002,p_line.sdo_srid,NULL, mdsys.sdo_elem_info_array(1,2,1), mdsys.sdo_ordinate_array(rec.x1,rec.y1,rec.x2,rec.y2))); End If; End If; END LOOP; -- Now append any remaining elements in p_line to p_out_line2 FOR v_element IN (v_part+1)..v_num_elements LOOP -- dbms_output.put_line('Adding element || ' || v_element || ' to out line 2'); v_extract_geom := mdsys.sdo_util.Extract(p_line,v_element); -- Extract element with all sub-elements p_out_line2 := mdsys.sdo_util.Append(p_out_line2,v_extract_geom); END LOOP; End Split;
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