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)
SDO2GeoJSON
Recently, like many before me, I had need to create a function that converted a single SDO_GEOMETRY object to GeoJSON.
The function I created is below.
Note that this function:
- Does not support attributes (it could do via passing in a refCursor or table of record);
- Nor does nor does it address how to create a whole GeoJSON document from a collection of SDO_GEOMETRY records.
The latter could be done via a function that processed a SQL statement or refCursor with the appropriate parameters (filename etc).
One thing about GeoJSON is that it is uses the same keywords over and over again within a FeatureCollection. This could mean that a GeoJSON collection would repeat keywords eg MultiLineString as many times as there were records in the SQL statement. This would mean the collection would use more network bandwidth for communication. One approach could be to replace the keywords with unique short-name tags. I have done this in this function even though it is non-standard.
Additionally, I have wondered if some improvement in size could be won if the each geometry’s mbr is applied to the coordinates in order to make them “relative”, perhaps saving space. This non-standard idea is included as an “idea”.
CREATE OR REPLACE FUNCTION sdo2geojson(p_geometry IN sdo_geometry, p_decimal_places IN pls_integer DEFAULT 2, p_compress_tags IN pls_integer DEFAULT 0, p_relative2mbr IN pls_integer DEFAULT 0, p_num_fmt IN varchar2 DEFAULT '999.9999999') RETURN CLOB deterministic /* Note: Does not support curved geometries. * If required, stroke geometry before calling function. * If Compressed apply bbox to coordinates..... * { "type": "Feature", * "bbox": [-180.0, -90.0, 180.0, 90.0], * "geometry": { * "type": "Polygon", * "coordinates": [[ [-180.0, 10.0], [20.0, 90.0], [180.0, -5.0], [-30.0, -90.0] ]] * } * ... * } */ AS v_relative BOOLEAN := CASE WHEN p_relative2mbr<>0 THEN TRUE ELSE FALSE END; v_result CLOB; v_type varchar2(50); v_compress_tags BOOLEAN := CASE WHEN p_compress_tags<>0 THEN TRUE ELSE FALSE END; v_feature_key varchar2(100) := CASE WHEN v_compress_tags THEN 'F' ELSE '"Feature"' END; v_bbox_tag varchar2(100) := CASE WHEN v_compress_tags THEN 'b:' ELSE '"bbox":' END; v_coord_tag varchar2(100) := CASE WHEN v_compress_tags THEN 'c:' ELSE '"coordinates":' END; v_geometry_tag varchar2(100) := CASE WHEN v_compress_tags THEN 'g:' ELSE '"Geometry":' END; v_type_tag varchar2(100) := CASE WHEN v_compress_tags THEN 't:' ELSE '"type":' END; v_temp_string varchar2(30000); v_precision pls_integer := nvl(p_decimal_places,2); v_i pls_integer; v_num_rings pls_integer; v_num_elements pls_integer; v_element_no pls_integer; v_vertices mdsys.vertex_set_type; v_element mdsys.sdo_geometry; v_ring mdsys.sdo_geometry; v_mbr mdsys.sdo_geometry; v_rGeom mdsys.sdo_geometry; v_dims pls_integer; FUNCTION hasRectangles( p_elem_info IN mdsys.sdo_elem_info_array ) RETURN Pls_Integer IS v_rectangle_count NUMBER := 0; v_etype pls_integer; v_interpretation pls_integer; v_elements pls_integer; BEGIN IF ( p_elem_info IS NULL ) THEN RETURN 0; END IF; v_elements := ( ( p_elem_info.COUNT / 3 ) - 1 ); <<element_extraction>> FOR v_i IN 0 .. v_elements LOOP v_etype := p_elem_info(v_i * 3 + 2); v_interpretation := p_elem_info(v_i * 3 + 3); IF ( v_etype IN (1003,2003) AND v_interpretation = 3 ) THEN v_rectangle_count := v_rectangle_count + 1; END IF; END loop element_extraction; RETURN v_rectangle_Count; END hasRectangles; FUNCTION hasCircularArcs(p_elem_info IN mdsys.sdo_elem_info_array) RETURN BOOLEAN IS v_elements NUMBER; BEGIN v_elements := ( ( p_elem_info.COUNT / 3 ) - 1 ); <<element_extraction>> FOR v_i IN 0 .. v_elements LOOP IF ( ( /* etype */ p_elem_info(v_i * 3 + 2) = 2 AND /* interpretation*/ p_elem_info(v_i * 3 + 3) = 2 ) OR ( /* etype */ p_elem_info(v_i * 3 + 2) IN (1003,2003) AND /* interpretation*/ p_elem_info(v_i * 3 + 3) IN (2,4) ) ) THEN RETURN TRUE; END IF; END loop element_extraction; RETURN FALSE; END hasCircularArcs; FUNCTION GetNumRings( p_geometry IN mdsys.sdo_geometry, p_ring_type IN INTEGER DEFAULT 0 /* 0 = ALL; 1 = OUTER; 2 = INNER */ ) RETURN NUMBER IS v_ring_count NUMBER := 0; v_ring_type NUMBER := p_ring_type; v_elements NUMBER; v_etype pls_integer; BEGIN IF ( p_geometry IS NULL ) THEN RETURN 0; END IF; IF ( p_geometry.sdo_elem_info IS NULL ) THEN RETURN 0; END IF; IF ( v_ring_type NOT IN (0,1,2) ) THEN v_ring_type := 0; END IF; 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; RETURN v_ring_count; END GetNumRings; PROCEDURE ADD_Coordinate( p_ordinates IN OUT nocopy mdsys.sdo_ordinate_array, p_dim IN NUMBER, p_x_coord IN NUMBER, p_y_coord IN NUMBER, p_z_coord IN NUMBER, p_m_coord IN NUMBER, p_measured IN BOOLEAN := FALSE, p_duplicates IN BOOLEAN := FALSE) IS FUNCTION Duplicate RETURN BOOLEAN IS BEGIN RETURN CASE WHEN p_ordinates IS NULL OR p_ordinates.COUNT = 0 THEN FALSE ELSE CASE p_dim WHEN 2 THEN ( p_ordinates(p_ordinates.COUNT) = p_y_coord AND p_ordinates(p_ordinates.COUNT-1) = p_x_coord ) WHEN 3 THEN ( p_ordinates(p_ordinates.COUNT) = CASE WHEN p_measured THEN p_m_coord ELSE p_z_coord END AND p_ordinates(p_ordinates.COUNT-1) = p_y_coord AND p_ordinates(p_ordinates.COUNT-2) = p_x_coord ) WHEN 4 THEN ( p_ordinates(p_ordinates.COUNT) = p_m_coord AND p_ordinates(p_ordinates.COUNT-1) = p_z_coord AND p_ordinates(p_ordinates.COUNT-2) = p_y_coord AND p_ordinates(p_ordinates.COUNT-3) = p_x_coord ) END END; END Duplicate; BEGIN IF ( p_ordinates IS NULL ) THEN p_ordinates := NEW mdsys.sdo_ordinate_array(NULL); p_ordinates.DELETE; END IF; IF ( p_duplicates OR NOT Duplicate() ) THEN IF ( p_dim >= 2 ) THEN p_ordinates.extend(2); p_ordinates(p_ordinates.count-1) := p_x_coord; p_ordinates(p_ordinates.COUNT ) := p_y_coord; END IF; IF ( p_dim >= 3 ) THEN p_ordinates.extend(1); p_ordinates(p_ordinates.COUNT) := CASE WHEN p_dim = 3 AND p_measured THEN p_m_coord ELSE p_z_coord END; END IF; IF ( p_dim = 4 ) THEN p_ordinates.extend(1); p_ordinates(p_ordinates.COUNT) := p_m_coord; END IF; END IF; END ADD_Coordinate; FUNCTION Rectangle2Polygon(p_geometry IN mdsys.sdo_geometry) RETURN mdsys.sdo_geometry AS v_dims pls_integer; v_ordinates mdsys.sdo_ordinate_array := NEW mdsys.sdo_ordinate_array(NULL); v_vertices mdsys.vertex_set_type; v_etype pls_integer; v_start_coord mdsys.vertex_type; v_end_coord mdsys.vertex_type; BEGIN v_ordinates.DELETE; v_dims := p_geometry.get_dims(); v_etype := p_geometry.sdo_elem_info(2); v_vertices := sdo_util.getVertices(p_geometry); v_start_coord := v_vertices(1); v_end_coord := v_vertices(2); -- First coordinate ADD_Coordinate( v_ordinates, v_dims, v_start_coord.x, v_start_coord.y, v_start_coord.z, v_start_coord.w ); -- Second coordinate IF ( v_etype = 1003 ) THEN ADD_Coordinate(v_ordinates,v_dims,v_end_coord.x,v_start_coord.y,(v_start_coord.z + v_end_coord.z) /2, v_start_coord.w); ELSE ADD_Coordinate(v_ordinates,v_dims,v_start_coord.x,v_end_coord.y,(v_start_coord.z + v_end_coord.z) /2, (v_end_coord.w - v_start_coord.w) * ((v_end_coord.x - v_start_coord.x) / ((v_end_coord.x - v_start_coord.x) + (v_end_coord.y - v_start_coord.y)) )); END IF; -- 3rd or middle coordinate ADD_Coordinate(v_ordinates,v_dims,v_end_coord.x,v_end_coord.y,v_end_coord.z,v_end_coord.w); -- 4th coordinate IF ( v_etype = 1003 ) THEN ADD_Coordinate(v_ordinates,v_dims,v_start_coord.x,v_end_coord.y,(v_start_coord.z + v_end_coord.z) /2,v_start_coord.w); ELSE Add_Coordinate(v_ordinates,v_dims,v_end_coord.x,v_start_coord.y,(v_start_coord.z + v_end_coord.z) /2, (v_end_coord.w - v_start_coord.w) * ((v_end_coord.x - v_start_coord.x) / ((v_end_coord.x - v_start_coord.x) + (v_end_coord.y - v_start_coord.y)) )); END IF; -- Last coordinate ADD_Coordinate(v_ordinates,v_dims,v_start_coord.x,v_start_coord.y,v_start_coord.z,v_start_coord.w); RETURN mdsys.sdo_geometry(p_geometry.sdo_gtype,p_geometry.sdo_srid,NULL,mdsys.sdo_elem_info_array(1,v_etype,1),v_ordinates); END Rectangle2Polygon; FUNCTION formatCoord(p_x IN NUMBER, p_y IN NUMBER, p_relative IN BOOLEAN) RETURN varchar2 AS BEGIN RETURN '[' || CASE WHEN p_relative THEN TRIM(to_char(round(p_x - v_mbr.sdo_ordinates(1),v_precision),p_num_fmt)) || ',' || TRIM(to_char(round(p_y - v_mbr.sdo_ordinates(2),v_precision),p_num_fmt)) ELSE TRIM(to_char(round(p_x,v_precision),p_num_fmt)) || ',' || TRIM(to_char(round(p_y,v_precision),p_num_fmt)) END || ']'; END formatCoord; BEGIN IF ( p_geometry IS NULL ) THEN RETURN NULL; END IF; -- Currently, we do not support compound objects -- IF ( p_geometry.get_gtype() NOT IN (1,2,3,5,6,7) ) THEN RETURN NULL; END IF; DBMS_LOB.createtemporary (lob_loc => v_result, cache => TRUE); v_type := CASE WHEN v_compress_tags THEN CASE p_geometry.get_gtype() WHEN 1 THEN 'P' WHEN 2 THEN 'LS' WHEN 3 THEN 'PG' WHEN 5 THEN 'MP' WHEN 6 THEN 'MLS' WHEN 7 THEN 'MPG' END ELSE CASE p_geometry.get_gtype() WHEN 1 THEN '"Point"' WHEN 2 THEN '"LineString"' WHEN 3 THEN '"Polygon"' WHEN 5 THEN '"MultiPoint"' WHEN 6 THEN '"MultiLineString"' WHEN 7 THEN '"MultiPolygon"' END END; v_temp_string := '{'; IF ( p_geometry.get_gtype() = 1 ) THEN v_temp_string := v_temp_string || v_type_tag || v_type || ',' || v_coord_tag; IF (p_geometry.SDO_POINT IS NOT NULL ) THEN v_temp_string := v_temp_string || '[' || TRIM(to_char(round(p_geometry.SDO_POINT.X,v_precision),p_num_fmt)) || ',' || TRIM(to_char(round(p_geometry.SDO_POINT.Y,v_precision),p_num_fmt)) || ']}'; ELSE v_temp_string := v_temp_string || '[' || TRIM(to_char(round(p_geometry.sdo_ordinates(1),v_precision),p_num_fmt)) || ',' || TRIM(to_char(round(p_geometry.sdo_ordinates(2),v_precision),p_num_fmt)) || ']}'; END IF; DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => 1, buffer => v_temp_string ); RETURN v_result; END IF; IF ( v_relative ) THEN v_mbr := SDO_GEOM.SDO_MBR(p_geometry); IF ( v_mbr IS NOT NULL ) THEN v_temp_string := v_temp_string || v_type_tag || v_feature_key || ',' || v_bbox_tag || '[' || v_mbr.sdo_ordinates(1) || ',' || v_mbr.sdo_ordinates(2) || ',' || v_mbr.sdo_ordinates(3) || ',' || v_mbr.sdo_ordinates(4) || ',' || '],' || v_geometry_tag || '{'; END IF; END IF; IF p_geometry.get_gtype() IN (5,6,7) THEN v_temp_string := v_temp_string || v_type_tag || v_type || ',' || v_coord_tag || '['; ELSE v_temp_string := v_temp_string || v_type_tag || v_type || ',' || v_coord_tag; END IF; -- Write header DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => 1, buffer => v_temp_string); IF ( hasCircularArcs(p_geometry.sdo_elem_info) ) THEN RETURN NULL; END IF; v_num_elements := mdsys.sdo_util.GetNumElem(p_geometry); <<for_all_elements>> FOR v_element_no IN 1..v_num_elements LOOP v_element := mdsys.sdo_util.EXTRACT(p_geometry,v_element_no); -- Extract element with all sub-elements IF ( v_element.get_gtype() IN (1,2,5) ) THEN IF (v_element_no = 1) THEN v_temp_string := '['; elsif ( v_element.get_gtype() = 2 ) THEN v_temp_string := '],['; END IF; DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); v_vertices := mdsys.sdo_util.getVertices(v_element); v_temp_string := formatCoord(v_vertices(1).x,v_vertices(1).y,v_relative); DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); <<for_all_vertices>> FOR j IN 2..v_vertices.COUNT loop v_temp_string := ',' || formatCoord(v_vertices(j).x,v_vertices(j).y,v_relative); DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); END loop for_all_vertices; ELSE IF (v_element_no = 1) THEN v_temp_string := '['; ELSE v_temp_string := '],['; END IF; DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); v_num_rings := GetNumRings(v_element); <<for_all_rings>> FOR v_ring_no IN 1..v_num_rings Loop v_ring := MDSYS.SDO_UTIL.EXTRACT(p_geometry,v_element_no,v_ring_no); -- Extract ring from element .. must do it this way, can't correctly extract from v_element. IF ( v_ring_no > 1 ) THEN -- inner ring needs reversing v_rGeom := Sdo_Geometry(v_dims*1000+2,v_ring.Sdo_Srid,NULL,Sdo_Elem_Info_Array(1,2,1),v_ring.Sdo_Ordinates); v_ring.Sdo_Ordinates := Mdsys.Sdo_Util.Reverse_Linestring(v_rGeom).Sdo_Ordinates; END IF; IF (hasRectangles(v_ring.sdo_elem_info)>0) THEN v_ring := Rectangle2Polygon(v_ring); END IF; IF ( v_ring_no > 1 ) THEN v_temp_string := ','; DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); END IF; v_vertices := mdsys.sdo_util.getVertices(v_ring); v_temp_string := '[' || formatCoord(v_vertices(1).x,v_vertices(1).y,v_relative); DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); <<for_all_vertices>> FOR j IN 2..v_vertices.COUNT loop v_temp_string := ',' || formatCoord(v_vertices(j).x,v_vertices(j).y,v_relative); DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); END loop for_all_vertices; v_temp_string := ']'; -- Close Ring DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); END Loop for_all_rings; END IF; END LOOP for_all_elements; -- Closing tag IF p_geometry.get_gtype() IN (5,6,7) THEN v_temp_string := ']]}'; ELSE v_temp_string := ']}'; END IF; IF ( v_relative AND p_geometry.get_gtype() <> 1 ) THEN v_temp_string := v_temp_string || '}'; END IF; DBMS_LOB.WRITE(lob_loc => v_result, amount => LENGTH (v_temp_string), offset => DBMS_LOB.GETLENGTH(v_result)+1, buffer => v_temp_string ); RETURN v_result; END Sdo2GeoJson;
I tested this with the following.
SELECT CFLAG, CASE WHEN iscompress=0 THEN 'Ordinary' ELSE 'Compressed' END AS CFLAGTTYPE, geojson FROM (SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2001,NULL,sdo_point_type(3312345,5212345,NULL),NULL,NULL),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2001,NULL,NULL,sdo_elem_info_array(1,1,1), sdo_ordinate_array(312345,5212345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2005,NULL,NULL,sdo_elem_info_array(1,1,4), sdo_ordinate_array(312345,5212345,322345,5222345,332345,5232345,342345,5242345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2002,NULL,NULL,sdo_elem_info_array(1,2,1), sdo_ordinate_array(312345,5212345,322345,5222345,332345,5232345,342345,5242345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2006,NULL,NULL,sdo_elem_info_array(1,2,1,5,2,1), sdo_ordinate_array(312345,5212345,322345,5222345,332345,5232345,342345,5242345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2003,NULL,NULL,sdo_elem_info_array(1,1003,3,5,2003,3),sdo_ordinate_array(312345,5212345,322345,5222345,332345,5232345,342345,5242345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 UNION ALL SELECT (level-1) AS iscompress, sdo2geojson(sdo_geometry(2007,NULL,NULL,sdo_elem_info_array(1,1003,3,5,1003,3),sdo_ordinate_array(312345,5212345,322345,5222345,332345,5232345,342345,5242345)),1,(level-1),(level-1)) AS geojson FROM dual CONNECT BY level < 3 ); -- Results CFLAG CFLAGTTYPE GEOJSON ----- ---------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 0 Ordinary {"type":"Point","coordinates":[3312345,5212345]} 1 Compressed {t:P,c:[3312345,5212345]} 0 Ordinary {"type":"Point","coordinates":[312345,5212345]} 1 Compressed {t:P,c:[312345,5212345]} 0 Ordinary {"type":"MultiPoint","coordinates":[[312345,5212345],[322345,5222345],[332345,5232345],[342345,5242345]]} 1 Compressed {t:F,b:[312345,5212345,342345,5242345,],g:{t:MP,c:[[0,0],[10000,10000],[20000,20000],[30000,30000]]}} 0 Ordinary {"type":"LineString","coordinates":[[312345,5212345],[322345,5222345],[332345,5232345],[342345,5242345]]} 1 Compressed {t:F,b:[312345,5212345,342345,5242345,],g:{t:LS,c:[[0,0],[10000,10000],[20000,20000],[30000,30000]]}} 0 Ordinary {"type":"MultiLineString","coordinates":[[312345,5212345],[322345,5222345]],[[332345,5232345],[342345,5242345]]} 1 Compressed {t:F,b:[312345,5212345,342345,5242345,],g:{t:MLS,c:[[0,0],[10000,10000]],[[20000,20000],[30000,30000]]}} 0 Ordinary {"type":"Polygon","coordinates":[[[312345,5212345],[322345,5212345],[322345,5222345],[312345,5222345],[312345,5212345]],[[332345,5232345],[342345,5232345],[342345,5242345],[332345,5242345],[332345,5232345]]]} 1 Compressed {t:F,b:[312345,5212345,342345,5242345,],g:{t:PG,c:[[[0,0],[10000,0],[10000,10000],[0,10000],[0,0]],[[20000,20000],[30000,20000],[30000,30000],[20000,30000],[20000,20000]]]}} 0 Ordinary {"type":"MultiPolygon","coordinates":[[[312345,5212345],[322345,5212345],[322345,5222345],[312345,5222345],[312345,5212345]]],[[[332345,5232345],[342345,5232345],[342345,5242345],[332345,5242345],[332345,5232345]]]} 1 Compressed {t:F,b:[312345,5212345,342345,5242345,],g:{t:MPG,c:[[[0,0],[10000,0],[10000,10000],[0,10000],[0,0]]],[[[20000,20000],[30000,20000],[30000,30000],[20000,30000],[20000,20000]]]}} 14 ROWS selected
I hope this is of use to someone.
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