/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo) * * 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 org.geotools.data.oracle.sdo; import java.sql.SQLException; import java.util.Iterator; import oracle.jdbc.OracleConnection; import oracle.sql.ARRAY; import oracle.sql.ArrayDescriptor; import oracle.sql.CHAR; import oracle.sql.CharacterSet; import oracle.sql.Datum; import oracle.sql.NUMBER; import oracle.sql.STRUCT; import oracle.sql.StructDescriptor; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateList; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Sample use of SDO class for simple JTS Geometry. * <p> * If needed I can make a LRSGeometryConverter that allows * JTS Geometries with additional ordinates beyond xyz. * </p> * @author jgarnett * * @source $URL$ */ public class GeometryConverter { protected OracleConnection connection; GeometryFactory geometryFactory; public GeometryConverter( OracleConnection connection ){ this( connection, new GeometryFactory() ); } public GeometryConverter( OracleConnection connection, GeometryFactory geometryFactory ){ this.geometryFactory = geometryFactory; this.connection = connection; } public static final String DATATYPE = "MDSYS.SDO_GEOMETRY"; /** * Used to handle MDSYS.SDO_GEOMETRY. * * @return <code>MDSYS.SDO_GEOMETRY</code> * * @see net.refractions.jspatial.Converter#getDataType() */ public String getDataTypeName() { return DATATYPE; } /** * Ensure that obj is a JTS Geometry (2D or 3D) with no LRS measures. * <p> * This Converter does not support SpatialCoordinates</p> * * @param geom the Geometry to be converted * @return <code>true</code> if <code>obj</code> is a JTS Geometry * @see net.refractions.jspatial.Converter#isCapable(java.lang.Object) */ public boolean isCapable(Geometry geom) { if( geom == null ) return true; if( geom instanceof Point || geom instanceof MultiPoint || geom instanceof LineString || geom instanceof MultiLineString || geom instanceof Polygon || geom instanceof MultiPolygon || geom instanceof GeometryCollection ) { int d = SDO.D( geom ); int l = SDO.L( geom ); return l == 0 && ( d == 2 || d == 3); } return false; } /** * Convert provided SDO_GEOMETRY to JTS Geometry. * <p> * Will return <code>null</code> as <code>null</code>. * </p> * * @param sdoGeometry datum STRUCT to be converted to a double[] * * @return JTS <code>Geometry</code> representing the provided <code>datum</code> * @throws SQLException * * @see net.refractions.jspatial.Converter#toObject(oracle.sql.STRUCT) */ public Geometry asGeometry(STRUCT sdoGeometry) throws SQLException { // Note Returning null for null Datum if( sdoGeometry == null ) return null; Datum data[] = sdoGeometry.getOracleAttributes(); final int GTYPE = asInteger( data[0], 0 ); final int SRID = asInteger( data[1], SDO.SRID_NULL ); final double POINT[] = asDoubleArray( (STRUCT) data[2], Double.NaN ); final int ELEMINFO[] = asIntArray( (ARRAY) data[3], 0 ); final double ORDINATES[] = asDoubleArray( (ARRAY) data[4], Double.NaN );; return SDO.create( geometryFactory, GTYPE, SRID, POINT, ELEMINFO, ORDINATES ); } /** * Used to convert double[] to SDO_ODINATE_ARRAY. * <p> * Will return <code>null</code> as an empty <code>SDO_GEOMETRY</code></p> * * @param geom Map to be represented as a STRUCT * @return STRUCT representing provided Map * @see net.refractions.jspatial.Converter#toDataType(java.lang.Object) */ public STRUCT toSDO(Geometry geom) throws SQLException { return toSDO(geom, geom.getSRID()); } /** * Used to convert double[] to SDO_ODINATE_ARRAY. * <p> * Will return <code>null</code> as an empty <code>SDO_GEOMETRY</code></p> * * @param geom Map to be represented as a STRUCT * @return STRUCT representing provided Map * @see net.refractions.jspatial.Converter#toDataType(java.lang.Object) */ public STRUCT toSDO(Geometry geom, int srid) throws SQLException { if( geom == null) return asEmptyDataType(); int gtype = SDO.gType( geom ); NUMBER SDO_GTYPE = new NUMBER( gtype ); NUMBER SDO_SRID = (srid == SDO.SRID_NULL || srid == 0) ? null : new NUMBER( srid ); double[] point = SDO.point( geom ); STRUCT SDO_POINT; ARRAY SDO_ELEM_INFO; ARRAY SDO_ORDINATES; if( point == null ) { final Envelope env = geom.getEnvelopeInternal(); if(env.getWidth() > 0 && env.getHeight() > 0 && geom.getEnvelope().equalsTopo(geom)) { // rectangle optimization. Actually, more than an optimization. A few operators // do not work properly if they don't get rectangular geoms encoded as rectangles // SDO_FILTER is an example of this silly situation SDO_POINT = null; int elemInfo[] = new int[] {1, 1003, 3}; double ordinates[]; if(SDO.D(geom) == 2) ordinates = new double[] {env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY()}; else ordinates = new double[] {env.getMinX(), env.getMinY(), 0, env.getMaxX(), env.getMaxY(), 0}; SDO_POINT = null; SDO_ELEM_INFO = toARRAY( elemInfo, "MDSYS.SDO_ELEM_INFO_ARRAY" ); SDO_ORDINATES = toARRAY( ordinates, "MDSYS.SDO_ORDINATE_ARRAY" ); } else { int elemInfo[] = SDO.elemInfo( geom ); double ordinates[] = SDO.ordinates( geom ); 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 ); } /** * Representation of <code>null</code> as an Empty <code>SDO_GEOMETRY</code>. * * @return <code>null</code> as a SDO_GEOMETRY */ protected STRUCT asEmptyDataType() throws SQLException{ return toSTRUCT( null, DATATYPE ); } /** Convience method for STRUCT construction. */ protected final 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> */ protected final 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. * <p> * Forced to burn memory here - only way to actually place * <code>NULL</code> numbers in the ordinate stream.</p> * <ul> * <li>JTS: records lack of data as <code>Double.NaN</code></li> * <li>SDO: records lack of data as <code>NULL</code></li> * </ul> * <p> * The alternative is to construct the array from a array of * doubles, which does not record <code>NULL</code> NUMBERs.</p> * <p> * The results is an "MDSYS.SDO_ORDINATE_ARRAY"</p> * <code><pre> * list = c1(1,2,0), c2(3,4,Double.NaN) * measures = {{5,6},{7,8} * * toORDINATE( list, measures, 2 ) * = (1,2,5,7, 3,4,6,8) * * toORDINATE( list, measures, 3 ) * = (1,2,0,5,7, 3,4,NULL,6,8) * * toORDINATE( list, null, 2 ) * = (1,2, 3,4) * </pre></code> * * @param list CoordinateList to be represented * @param measures Per Coordiante Measures, <code>null</code> if not required * @param D Dimension of Coordinates (limited to 2d, 3d) */ protected final ARRAY toORDINATE( CoordinateList list, double measures[][], final int D) throws SQLException { ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor( "MDSYS.SDO_ORDINATE_ARRAY", connection ); final int LENGTH = measures != null ? measures.length : 0; final int LEN = D + LENGTH; Datum data[] = new Datum[ list.size()*LEN]; int offset = 0; int index = 0; Coordinate coord; for( Iterator i=list.iterator(); i.hasNext(); index++) { coord = (Coordinate) i.next(); data[ offset++ ] = toNUMBER( coord.x ); data[ offset++ ] = toNUMBER( coord.y ); if( D == 3 ) { data[ offset++ ] = toNUMBER( coord.x ); } for( int j=0; j<LENGTH; j++) { data[ offset++ ] = toNUMBER( measures[ j][ index ] ); } } return new ARRAY( descriptor, connection, data ); } protected final ARRAY toORDINATE( double ords[] ) throws SQLException { ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor( "MDSYS.SDO_ORDINATE_ARRAY", connection ); final int LENGTH = ords.length; Datum data[] = new Datum[ LENGTH ]; for( int i=0; i<LENGTH; i++ ) { data[ i ] = toNUMBER( ords[i] ); } return new ARRAY( descriptor, connection, data ); } protected final ARRAY toATTRIBUTE( double ords[], String desc ) throws SQLException { ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor( desc, connection ); final int LENGTH = ords.length; Datum data[] = new Datum[ LENGTH ]; for( int i=0; i<LENGTH; i++ ) { data[ i ] = toNUMBER( ords[i] ); } return new ARRAY( descriptor, connection, data ); } /** * Convience method for NUMBER construction. * <p> * Double.NaN is represented as <code>NULL</code> to agree * with JTS use.</p> */ protected final NUMBER toNUMBER( double number ) throws SQLException{ if( Double.isNaN( number )){ return null; } return new NUMBER( number ); } /** * Convience method for ARRAY construction. */ protected final 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 */ protected final NUMBER toNUMBER( int number ) { return new NUMBER( number ); } /** Convience method for CHAR construction */ protected final CHAR toCHAR( String s ) { // make sure if the string is larger than one character, only take the first character if (s.length() > 1) s = new String((new Character(s.charAt(0))).toString()); try { //BUG: make sure I am correct return new CHAR(s, CharacterSet.make(CharacterSet.ISO_LATIN_1_CHARSET)); } catch (SQLException e) { e.printStackTrace(); } return null; } // // These functions present Datum as a Java type // /** Presents datum as an int */ protected int asInteger( Datum datum, final int DEFAULT ) throws SQLException { if( datum == null) return DEFAULT; return ((NUMBER) datum).intValue(); } /** Presents datum as a double */ protected double asDouble( Datum datum, final double DEFAULT ) throws SQLException { if( datum == null) return DEFAULT; return ((NUMBER) datum).doubleValue(); } /** Presents struct as a double[] */ protected double[] asDoubleArray( STRUCT struct, final double DEFAULT ) throws SQLException { if( struct == null ) return null; return asDoubleArray( struct.getOracleAttributes(), DEFAULT ); } /** Presents array as a double[] */ protected double[] asDoubleArray( ARRAY array, final double DEFAULT ) throws SQLException { if( array == null ) return null; if( DEFAULT == 0 ) return array.getDoubleArray(); return asDoubleArray( array.getOracleArray(), DEFAULT ); } /** Presents Datum[] as a double[] */ protected double[] asDoubleArray( Datum data[], final double DEFAULT ) throws SQLException { if( data== null ) return null; double array[] = new double[ data.length ]; for( int i=0; i<data.length; i++ ) { array[ i ] = asDouble( data[ i ], DEFAULT ); } return array; } protected int[] asIntArray( ARRAY array, int DEFAULT ) throws SQLException { if( array == null ) return null; if( DEFAULT == 0 ) return array.getIntArray(); return asIntArray( array.getOracleArray(), DEFAULT ); } /** Presents Datum[] as a int[] */ protected int[] asIntArray( Datum data[], final int DEFAULT ) throws SQLException { if( data== null ) return null; int array[] = new int[ data.length ]; for( int i=0; i<data.length; i++ ) { array[ i ] = asInteger( data[ i ], DEFAULT ); } return array; } }