/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ /* * Geotools2 - OpenSource mapping toolkit * http://geotools.org * (C) 2003, Geotools Project Managment Committee (PMC) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ package com.vividsolutions.jts.io.oracle; import java.sql.SQLException; import java.util.*; import com.vividsolutions.jts.algorithm.CGAlgorithms; import com.vividsolutions.jts.geom.*; import oracle.jdbc.OracleConnection; import oracle.sql.*; /** * * Translates a JTS Geometry into an Oracle STRUCT representing an MDSYS.GEOMETRY object. * * A connection to an oracle instance with access to the definition of the MDSYS.GEOMETRY * object is required by the oracle driver. * * @version 9i * @author David Zwiers, Vivid Solutions. */ public class OraWriter { private OracleConnection connection; private int dimension = 2; private int srid = Constants.SRID_NULL; private String DATATYPE = "MDSYS.SDO_GEOMETRY"; /** * Initialize the Oracle MDSYS.GEOMETRY Encoder with a valid oracle connection. * * The connection should have sufficient priveledges to view the description of the MDSYS.GEOMETRY type. * * The dimension is set to 2 * * @param con */ public OraWriter(OracleConnection con){ this.connection = con; } /** * Initialize the Oracle MDSYS.GEOMETRY Encoder with a valid oracle connection. * * The connection should have sufficient priveledges to view the description of the MDSYS.GEOMETRY type. * * @param con * @param dimension */ public OraWriter(OracleConnection con, int dimension){ this.connection = con; this.dimension = dimension; } /** * Provides the oppotunity to force all geometries written using this writter to be written using the * specified srid. This is useful in two cases: 1) when you do not want the geometry's srid to be * over-written or 2) when you want to ensure an entire layer is always written using a constant srid. * * @param srid */ public void setSRID(int srid){ this.srid = srid; } /** * This routine will translate the JTS Geometry into an Oracle MDSYS.GEOMETRY STRUCT. * * Although invalid geometries may be encoded, and inserted into an Oracle DB, this is * not recomended. It is the responsibility of the user to ensure the geometry is valid * prior to calling this method. The user should also ensure the the geometry's SRID * field contains the correct value, if an SRID is desired. An incorrect SRID value may * cause index exceptions during an insert or update. * * When a null Geometry is passed in, a non-null, empty STRUCT is returned. Therefore, * inserting the the result of calling this method directly into a table will never result * in null insertions. * (March 2006) * * To pass a NULL Geometry into an oracle geometry parameter using jdbc, use * java.sql.CallableStatement.setNull(index,java.sql.Types.STRUCT,"MDSYS.SDO_GEOMETRY") * (April 2006) * * @param geom JTS Geometry to encode * @return Oracle MDSYS.GEOMETRY STRUCT * @throws SQLException */ public STRUCT write(Geometry geom) throws SQLException{ // this line may be problematic ... for v9i and later // need to revisit. // was this ... does not work for 9i // if( geom == null) return toSTRUCT( null, DATATYPE ); //works fro 9i if( geom == null) return toSTRUCT( new Datum[5], DATATYPE ); // does not work for 9i // if( geom == null) return null; //empty geom if( geom.isEmpty() || geom.getCoordinate() == null) return toSTRUCT( new Datum[5], DATATYPE ); int gtype = gType( geom); NUMBER SDO_GTYPE = new NUMBER( gtype ); //int srid = geom.getFactory().getSRID(); int srid = this.srid == Constants.SRID_NULL? geom.getSRID() : this.srid; NUMBER SDO_SRID = srid == Constants.SRID_NULL ? null : new NUMBER( srid ); double[] point = point( geom ); STRUCT SDO_POINT; ARRAY SDO_ELEM_INFO; ARRAY SDO_ORDINATES; if( point == null ){ int elemInfo[] = elemInfo( geom , gtype); List list = new ArrayList(); coordinates(list, geom); int dim = gtype / 1000; int lrs = (gtype - dim*1000)/100; int len = dim+lrs; // size per coordinate double[] ordinates = new double[list.size()*len]; int k=0; for(int i=0;i<list.size() && k<ordinates.length;i++){ int j=0; double[] ords = (double[]) list.get(i); for(;j<len && j<ords.length;j++){ ordinates[k++] = ords[j]; } for(;j<len;j++){ // mostly safety ordinates[k++] = Double.NaN; } } list = null; SDO_POINT = null; SDO_ELEM_INFO = toARRAY( elemInfo, "MDSYS.SDO_ELEM_INFO_ARRAY" ); SDO_ORDINATES = toARRAY( ordinates, "MDSYS.SDO_ORDINATE_ARRAY" ); } else { // Point Optimization Datum data[] = new Datum[]{ toNUMBER( point[0] ), toNUMBER( point[1] ), toNUMBER( point[2] ), }; SDO_POINT = toSTRUCT( data, "MDSYS.SDO_POINT_TYPE" ); SDO_ELEM_INFO = null; SDO_ORDINATES = null; } Datum attributes[] = new Datum[]{ SDO_GTYPE, SDO_SRID, SDO_POINT, SDO_ELEM_INFO, SDO_ORDINATES }; return toSTRUCT( attributes, DATATYPE ); } /** * Encode Geometry as described by GTYPE and ELEM_INFO * * @param list Flat list of Double * @param geom Geometry * * @throws IllegalArgumentException If geometry cannot be encoded */ private void coordinates(List list, Geometry geom) { switch (template(geom)) { case Constants.SDO_GTEMPLATE.POINT: addCoordinates(list, ((Point)geom).getCoordinateSequence()); return; case Constants.SDO_GTEMPLATE.LINE: addCoordinates(list, ((LineString)geom).getCoordinateSequence()); return; case Constants.SDO_GTEMPLATE.POLYGON: switch (elemInfoInterpretation(geom,Constants.SDO_ETYPE.POLYGON_EXTERIOR)) { case 3: Envelope e = geom.getEnvelopeInternal(); list.add(new double[] { e.getMinX(), e.getMinY() }); list.add(new double[] { e.getMaxX(), e.getMaxY() }); return; case 1: Polygon polygon = (Polygon) geom; int holes = polygon.getNumInteriorRing(); // check outer ring's direction CoordinateSequence ring = polygon.getExteriorRing().getCoordinateSequence(); if (!CGAlgorithms.isCCW(ring.toCoordinateArray())) { ring = reverse(polygon.getFactory().getCoordinateSequenceFactory(), ring); } addCoordinates(list,ring); for (int i = 0; i < holes; i++) { // check inner ring's direction ring = polygon.getInteriorRingN(i).getCoordinateSequence(); if (CGAlgorithms.isCCW(ring.toCoordinateArray())) { ring = reverse(polygon.getFactory().getCoordinateSequenceFactory(), ring); } addCoordinates(list,ring); } return; } break; // interpretations 2,4 not supported case Constants.SDO_GTEMPLATE.MULTIPOINT: case Constants.SDO_GTEMPLATE.MULTILINE: case Constants.SDO_GTEMPLATE.MULTIPOLYGON: case Constants.SDO_GTEMPLATE.COLLECTION: for (int i = 0; i < geom.getNumGeometries(); i++) { coordinates(list,geom.getGeometryN(i)); } return; } throw new IllegalArgumentException("Cannot encode JTS " + geom.getGeometryType() + " as " + "SDO_ORDINATRES (Limitied to Point, Line, Polygon, " + "GeometryCollection, MultiPoint, MultiLineString and MultiPolygon)"); } /** * Adds a double array to list. * * <p> * The double array will contain all the ordinates in the Coordiante * sequence. * </p> * * @param list * @param sequence */ private static void addCoordinates(List list, CoordinateSequence sequence) { Coordinate coord = null; for (int i = 0; i < sequence.size(); i++) { coord = sequence.getCoordinate(i); if(coord.z == Double.NaN) list.add( new double[] { coord.x, coord.y}); else list.add( new double[] { coord.x, coord.y, coord.z }); } } /** * Return SDO_ELEM_INFO array for geometry * * <pre><code><b> * # Name Meaning</b> * 0 SDO_STARTING_OFFSET Offsets start at one * 1 SDO_ETYPE Describes how ordinates are ordered * 2 SDO_INTERPRETATION SDO_ETYPE: 4, 1005, or 2005 * Number of triplets involved in compound geometry * * SDO_ETYPE: 1, 2, 1003, or 2003 * Describes ordering of ordinates in geometry * </code></pre> * * <p> * For compound elements (SDO_ETYPE values 4 and 5) the last element of one * is the first element of the next. * </p> * * @param geom Geometry being represented * * @return Descriptionof Ordinates representation */ private int[] elemInfo(Geometry geom, int gtype) { List list = new LinkedList(); elemInfo(list, geom, 1, gtype); int[] array = new int[list.size()]; int offset = 0; for (Iterator i = list.iterator(); i.hasNext(); offset++) { array[offset] = ((Number) i.next()).intValue(); } return array; } /** * Add to SDO_ELEM_INFO list for geometry and GTYPE. * * @param elemInfoList List used to gather SDO_ELEM_INFO * @param geom Geometry to encode * @param sOffSet Starting offset in SDO_ORDINATES * * @throws IllegalArgumentException If geom cannot be encoded by ElemInfo */ private void elemInfo(List elemInfoList, Geometry geom, int sOffSet, int gtype) { switch (gtype - (gtype/100) * 100) { // removes right two digits case Constants.SDO_GTEMPLATE.POINT: addInt(elemInfoList, sOffSet); addInt(elemInfoList, Constants.SDO_ETYPE.POINT); addInt(elemInfoList, 1); // INTERPRETATION single point return; case Constants.SDO_GTEMPLATE.MULTIPOINT: MultiPoint points = (MultiPoint) geom; addInt(elemInfoList, sOffSet); addInt(elemInfoList, Constants.SDO_ETYPE.POINT); addInt(elemInfoList, elemInfoInterpretation(points, Constants.SDO_ETYPE.POINT)); return; case Constants.SDO_GTEMPLATE.LINE: addInt(elemInfoList, sOffSet); addInt(elemInfoList, Constants.SDO_ETYPE.LINE); addInt(elemInfoList, 1); // INTERPRETATION straight edges return; case Constants.SDO_GTEMPLATE.MULTILINE: MultiLineString lines = (MultiLineString) geom; LineString line; int offset = sOffSet; int dim = gtype/1000; int len = dim + (gtype-dim*1000)/100; for (int i = 0; i < lines.getNumGeometries(); i++) { line = (LineString) lines.getGeometryN(i); addInt(elemInfoList, offset); addInt(elemInfoList, Constants.SDO_ETYPE.LINE); addInt(elemInfoList, 1); // INTERPRETATION straight edges offset += (line.getNumPoints() * len); } return; case Constants.SDO_GTEMPLATE.POLYGON: Polygon polygon = (Polygon)geom; int holes = polygon.getNumInteriorRing(); if (holes == 0) { addInt(elemInfoList, sOffSet); addInt(elemInfoList, elemInfoEType(polygon)); addInt(elemInfoList, elemInfoInterpretation(polygon, Constants.SDO_ETYPE.POLYGON_EXTERIOR)); return; } dim = gtype/1000; len = dim + (gtype-dim*1000)/100; offset = sOffSet; LineString ring; ring = polygon.getExteriorRing(); addInt(elemInfoList, offset); addInt(elemInfoList, elemInfoEType(polygon)); addInt(elemInfoList, elemInfoInterpretation(polygon, Constants.SDO_ETYPE.POLYGON_EXTERIOR)); offset += (ring.getNumPoints() * len); for (int i = 1; i <= holes; i++) { ring = polygon.getInteriorRingN(i - 1); addInt(elemInfoList, offset); addInt(elemInfoList, Constants.SDO_ETYPE.POLYGON_INTERIOR); addInt(elemInfoList, elemInfoInterpretation(ring, Constants.SDO_ETYPE.POLYGON_INTERIOR)); offset += (ring.getNumPoints() * len); } return; case Constants.SDO_GTEMPLATE.MULTIPOLYGON: MultiPolygon polys = (MultiPolygon) geom; Polygon poly; offset = sOffSet; dim = gtype/1000; len = dim + (gtype-dim*1000)/100; for (int i = 0; i < polys.getNumGeometries(); i++) { poly = (Polygon) polys.getGeometryN(i); elemInfo(elemInfoList, poly, offset, gType(poly)); if( isRectangle( poly )){ offset += (2 * len); } else { offset += (poly.getNumPoints() * len); } } return; case Constants.SDO_GTEMPLATE.COLLECTION: GeometryCollection geoms = (GeometryCollection) geom; offset = sOffSet; dim = gtype/1000; len = dim + (gtype-dim*1000)/100; for (int i = 0; i < geoms.getNumGeometries(); i++) { geom = geoms.getGeometryN(i); // MD 20/3/07 modified to provide gType of component geometry elemInfo(elemInfoList, geom, offset, gType(geom)); if( geom instanceof Polygon && isRectangle( (Polygon) geom )){ offset += (2 * len); } else { offset += (geom.getNumPoints() * len); } } return; } throw new IllegalArgumentException("Cannot encode JTS " + geom.getGeometryType() + " as SDO_ELEM_INFO " + "(Limitied to Point, Line, Polygon, GeometryCollection, MultiPoint," + " MultiLineString and MultiPolygon)"); } private void addInt(List list, int i) { list.add(new Integer(i)); } /** * We need to check if a <code>polygon</code> a rectangle so we can produce * the correct encoding. * * Rectangles are only supported without a SRID! * * @param polygon * * @return <code>true</code> if polygon is SRID==0 and a rectangle */ private boolean isRectangle(Polygon polygon) { if (polygon.getFactory().getSRID() != Constants.SRID_NULL) { // Rectangles only valid in CAD applications // that do not have an SRID system // return false; } if (lrs(polygon) != 0) { // cannot support LRS on a rectangle return false; } Coordinate[] coords = polygon.getCoordinates(); if (coords.length != 5) { return false; } if ((coords[0] == null) || (coords[1] == null) || (coords[2] == null) || (coords[3] == null)) { return false; } if (!coords[0].equals2D(coords[4])) { return false; } double x1 = coords[0].x; double y1 = coords[0].y; double x2 = coords[1].x; double y2 = coords[1].y; double x3 = coords[2].x; double y3 = coords[2].y; double x4 = coords[3].x; double y4 = coords[3].y; if ((x1 == x4) && (y1 == y2) && (x3 == x2) && (y3 == y4)) { // 1+-----+2 // | | // 4+-----+3 return true; } if ((x1 == x2) && (y1 == y4) && (x3 == x4) && (y3 == y2)) { // 2+-----+3 // | | // 1+-----+4 return true; } return false; } /** * Produce <code>SDO_ETYPE</code> for geometry description as stored in the * <code>SDO_ELEM_INFO</code>. * * <p> * Describes how Ordinates are ordered: * </p> * <pre><code><b> * Value Elements Meaning</b> * 0 Custom Geometry (like spline) * 1 simple Point (or Points) * 2 simple Line (or Lines) * 3 polygon ring of unknown order (discouraged update to 1003 or 2003) * 1003 simple polygon ring (1 exterior counterclockwise order) * 2003 simple polygon ring (2 interior clockwise order) * 4 compound series defines a linestring * 5 compound series defines a polygon ring of unknown order (discouraged) * 1005 compound series defines exterior polygon ring (counterclockwise order) * 2005 compound series defines interior polygon ring (clockwise order) * </code></pre> * * @param geom Geometry being represented * * @return Descriptionof Ordinates representation * * @throws IllegalArgumentException */ private int elemInfoEType(Geometry geom) { switch (template(geom)) { case Constants.SDO_GTEMPLATE.POINT: return Constants.SDO_ETYPE.POINT; case Constants.SDO_GTEMPLATE.LINE: return Constants.SDO_ETYPE.LINE; case Constants.SDO_GTEMPLATE.POLYGON: // jts convention return Constants.SDO_ETYPE.POLYGON_EXTERIOR; // cc order default: // should never happen! throw new IllegalArgumentException("Unknown encoding of SDO_GTEMPLATE"); } } /** * Allows specification of <code>INTERPRETATION</code> used to interpret * <code>geom</code>. * * @param geom Geometry to encode * @param etype ETYPE value requiring an INTERPREATION * * @return INTERPRETATION ELEM_INFO entry for geom given etype * * @throws IllegalArgumentException If asked to encode a curve */ private int elemInfoInterpretation(Geometry geom, int etype) { switch (etype) { case Constants.SDO_ETYPE.POINT: if (geom instanceof Point) { return 1; } if (geom instanceof MultiPoint) { return ((MultiPoint) geom).getNumGeometries(); } break; case Constants.SDO_ETYPE.LINE: // always straight for jts return 1; case Constants.SDO_ETYPE.POLYGON: case Constants.SDO_ETYPE.POLYGON_EXTERIOR: case Constants.SDO_ETYPE.POLYGON_INTERIOR: if (geom instanceof Polygon) { Polygon polygon = (Polygon) geom; // always straight for jts if (isRectangle(polygon)) { return 3; } } return 1; } throw new IllegalArgumentException("Cannot encode JTS " + geom.getGeometryType() + " as " + "SDO_INTERPRETATION (Limitied to Point, Line, Polygon, " + "GeometryCollection, MultiPoint, MultiLineString and MultiPolygon)"); } /** * Return SDO_POINT_TYPE for geometry * * Will return non null for Point objects. <code>null</code> is returned * for all non point objects. * You cannot use this with LRS Coordiantes * Subclasses may wish to repress this method and force Points to be * represented using SDO_ORDINATES. * * @param geom * * @return double[] */ private double[] point(Geometry geom) { if (geom instanceof Point && (lrs(geom) == 0)) { Point point = (Point) geom; Coordinate coord = point.getCoordinate(); return new double[] { coord.x, coord.y, coord.z }; } // SDO_POINT_TYPE only used for non LRS Points return null; } /** * Produce SDO_GTEMPLATE representing provided Geometry. * * <p> * Encoding of Geometry type and dimension. * </p> * * <p> * SDO_GTEMPLATE defined as for digits <code>[d][l][tt]</code>: * </p> * * @param geom * * @return SDO_GTEMPLATE */ private int gType(Geometry geom) { int d = dimension(geom) * 1000; int l = lrs(geom) * 100; int tt = template(geom); return d + l + tt; } /** * Return dimensions as defined by SDO_GTEMPLATE (either 2,3 or 4). * * * @param geom * * @return num dimensions */ private int dimension(Geometry geom) { int d = Double.isNaN(geom.getCoordinate().z)?2:3; return d<dimension?d:dimension; } /** * Return LRS as defined by SDO_GTEMPLATE (either 3,4 or 0). * * @param geom * * @return <code>0</code> */ private int lrs(Geometry geom) { // when measures are supported this may change // until then ... return 0; } /** * Return TT as defined by SDO_GTEMPLATE (represents geometry type). * * @see Constants.SDO_GTEMPLATE * * @param geom * * @return template code */ private int template(Geometry geom) { if (geom == null) { return -1; // UNKNOWN } else if (geom instanceof Point) { return Constants.SDO_GTEMPLATE.POINT; } else if (geom instanceof LineString) { return Constants.SDO_GTEMPLATE.LINE; } else if (geom instanceof Polygon) { return Constants.SDO_GTEMPLATE.POLYGON; } else if (geom instanceof MultiPoint) { return Constants.SDO_GTEMPLATE.MULTIPOINT; } else if (geom instanceof MultiLineString) { return Constants.SDO_GTEMPLATE.MULTILINE; } else if (geom instanceof MultiPolygon) { return Constants.SDO_GTEMPLATE.MULTIPOLYGON; } else if (geom instanceof GeometryCollection) { return Constants.SDO_GTEMPLATE.COLLECTION; } throw new IllegalArgumentException("Cannot encode JTS " + geom.getGeometryType() + " as SDO_GTEMPLATE " + "(Limitied to Point, Line, Polygon, GeometryCollection, MultiPoint," + " MultiLineString and MultiPolygon)"); } /** Convience method for STRUCT construction. */ private STRUCT toSTRUCT( Datum attributes[], String dataType ) throws SQLException { if( dataType.startsWith("*.")){ dataType = "DRA."+dataType.substring(2);//TODO here } StructDescriptor descriptor = StructDescriptor.createDescriptor( dataType, connection ); return new STRUCT( descriptor, connection, attributes ); } /** * Convience method for ARRAY construction. * <p> * Compare and contrast with toORDINATE - which treats <code>Double.NaN</code> * as<code>NULL</code></p> */ private ARRAY toARRAY( double doubles[], String dataType ) throws SQLException { ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor( dataType, connection ); return new ARRAY( descriptor, connection, doubles ); } /** * Convience method for ARRAY construction. */ private ARRAY toARRAY( int ints[], String dataType ) throws SQLException { ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor( dataType, connection ); return new ARRAY( descriptor, connection, ints ); } /** * Convience method for NUMBER construction. * <p> * Double.NaN is represented as <code>NULL</code> to agree * with JTS use.</p> */ private NUMBER toNUMBER( double number ) throws SQLException{ if( Double.isNaN( number )){ return null; } return new NUMBER( number ); } /** * reverses the coordinate order * * @param factory * @param sequence * * @return CoordinateSequence reversed sequence */ private CoordinateSequence reverse(CoordinateSequenceFactory factory, CoordinateSequence sequence) { CoordinateList list = new CoordinateList(sequence.toCoordinateArray()); Collections.reverse(list); return factory.create(list.toCoordinateArray()); } /** * @param dimension The dimension to set. */ public void setDimension(int dimension) { this.dimension = dimension; } }