All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vividsolutions.jts.io.oracle.OraWriter Maven / Gradle / Ivy

The newest version!
/*
 * 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 com.vividsolutions.jts.util.Assert;

import oracle.jdbc.OracleConnection;
import oracle.sql.*;

/**
 * Translates a JTS Geometry into an Oracle STRUCT representing an MDSYS.SDO_GEOMETRY object. 
 * Supports writing all JTS geometry types into an equivalent Oracle representation.
 * 

* To write an Oracle STRUCT a connection to an Oracle instance with access to the definition of the MDSYS.SDO_GEOMETRY * type is required. Oracle SDO_GEOMETRY SQL strings may be written without a connection, however. *

* By default, a single {@link Point} is written using the optimized SDO_POINT_TYPE attribute. * This can be overridden to use the (less compact) SDO_ELEM_INFO/SDOORDINATES representation * by using {@link #setOptimizePoint(boolean)}. *

* By default, rectangular polygons are written as regular 5-point polygons. * This can be changed to use the optimized RECTANGLE points * by using {@link #setOptimizeRectangle(boolean)}. * Note that RECTANGLEs do not support LRS Measure ordinate values. * Also, this class only writes RECTANGLEs for polygons containing a single ring (i.e. the shell). *

* Oracle cannot represent {@link MultiPolygon}s or {@link MultiLineString}s directly as elements * of a {@link GeometryCollection}. Instead, their components are written individually. * {@link MultiPoint}s are represented directly, however. * * The dimension of the output SDO_GEOMETRY is determined as follows: *

    *
  • by default, the dimension matches that of the input *
  • currently the coordinate dimension of the input is determined by inspecting a sample coordinate. * If the Z value is NaN, the coordinate dimension is assumed to be 2. * (In the future this will be determined from the underlying {@link CoordinateSequence}s. *
  • the dimension can be set explicitly by the {@link #setDimension(int)} method. * This allows forcing Z output even if the Z values are NaN. * Conversely, if Z values are present this allows forcing 2D output. *
* *

LIMITATIONS

*
    *
  • Since JTS does not support Measures, they cannot be written. * (A future release could allow forcing interpreting Z as M, or else providing a fixed M value). *
* * @author Martin Davis * @author David Zwiers, Vivid Solutions. */ public class OraWriter { /** * A connection providing access to the required type definitions */ private OracleConnection connection; /** * The maximum output dimension to write */ private int outputDimension = OraGeom.NULL_DIMENSION; /** * The default SRID to write */ private int srid = OraGeom.SRID_NULL; private boolean isOptimizeRectangle = false; private boolean isOptimizePoint = true; /** * Creates a writer for Oracle geometry formats. * * The output dimension will be whatever the dimension of the input is. */ public OraWriter() { } /** * Creates a writer for Oracle geometry formats, * specifying the maximum output dimension. * * @param outputDimension the coordinate dimension to use for the output */ public OraWriter(int outputDimension) { this.outputDimension = outputDimension; } /** * Creates a writer using a valid Oracle connection. *

* To simplify connection resource handling, the connection should be * provided in the {@link #write(Geometry, OracleConnection)} method. * Accordingly, this constructor has been deprecated. *

* The connection should have sufficient privileges to view the description of the MDSYS.SDO_GEOMETRY type. *

* The output dimension will be whatever the dimension of the input is. * * @param con a valid Oracle connection * @deprecated use {@link #OraWriter()} instead */ public OraWriter(OracleConnection con) { this.connection = con; } /** * Creates a writer using a valid Oracle connection, * and specifying the maximum output dimension. *

* To simplify connection resource handling, the connection should be * provided in the {@link #write(Geometry, OracleConnection)} method. * Accordingly, this constructor has been deprecated. *

* The connection should have sufficient privileges to view the description of the MDSYS.SDO_GEOMETRY type. * * @param con a valid Oracle connection * @param outputDimension the coordinate dimension to use for the output * @deprecated use {@link #OraWriter(int)} instead */ public OraWriter(OracleConnection con, int outputDimension) { this.connection = con; this.outputDimension = outputDimension; } /** * Sets the coordinate dimension for the created Oracle geometries. * * @param outputDimension * the coordinate dimension to use for the output */ public void setDimension(int outputDimension) { if (outputDimension < 2) throw new IllegalArgumentException("Output dimension must be >= 2"); this.outputDimension = outputDimension; } /** * Forces geometries to be written using the specified SRID. * This is useful in two cases: *

    *
  • to avoid using the native geometry's SRID *
  • to ensure an entire table is written using a fixed SRID. *
* * @param srid the srid to use */ public void setSRID(int srid) { this.srid = srid; } /** * Sets whether rectangle polygons should be written using the * optimized 4-coordinate RECTANGLE format * (ETYPE=1003, INTERPRETATION=3). * If this option is false, rectangles are written as 5-coordinate polygons. * The default setting is false. * * @param isOptimizeRectangle whether to optimize rectangle writing */ public void setOptimizeRectangle(boolean isOptimizeRectangle) { this.isOptimizeRectangle = isOptimizeRectangle; } /** * Sets whether points should be written using the * optimized SDO_POINT_TYPE format. * If this option is false, points are written using the SDO_ORDINATES attribute. * The default setting is true. * * @param isOptimizePoint whether to optimize point writing */ public void setOptimizePoint(boolean isOptimizePoint) { this.isOptimizePoint = isOptimizePoint; } /** * Converts a {@link Geometry} into an Oracle MDSYS.SDO_GEOMETRY STRUCT. *

* Although invalid geometries may be encoded, and inserted into an Oracle DB, * this is not recommended. It is the responsibility of the user to ensure the * geometry is valid prior to calling this method. *

* The SRID of the created SDO_GEOMETRY is the SRID defined explicitly for the writer, if any; * otherwise it is the SRID contained in the input geometry. * The caller should ensure the the SRID is valid for the intended use, * since an incorrect SRID may cause indexing exceptions during an * INSERT or UPDATE. *

* When a null Geometry is passed in, a non-null, empty SDO_GEOMETRY STRUCT is returned. * Therefore, inserting the output of the writer into a * table will never result in NULL insertions. * To pass a NULL Geometry into an Oracle SDO_GEOMETRY-valued parameter using JDBC, use *

   * java.sql.CallableStatement.setNull(index, java.sql.Types.STRUCT, "MDSYS.SDO_GEOMETRY"). 
   * 
* * @param geom the geometry to encode * @return a Oracle MDSYS.SDO_GEOMETRY STRUCT representing the geometry * @throws SQLException if an encoding error was encountered * @deprecated */ public STRUCT write(Geometry geom) throws SQLException { return write(geom, connection); } /** * Converts a {@link Geometry} into an Oracle MDSYS.SDO_GEOMETRY STRUCT. *

* Although invalid geometries may be encoded, and inserted into an Oracle DB, * this is not recommended. It is the responsibility of the user to ensure the * geometry is valid prior to calling this method. *

* The SRID of the created SDO_GEOMETRY is the SRID defined explicitly for the writer, if any; * otherwise it is the SRID contained in the input geometry. * The caller should ensure the the SRID is valid for the intended use, * since an incorrect SRID may cause indexing exceptions during an * INSERT or UPDATE. *

* When a null Geometry is passed in, a non-null, empty SDO_GEOMETRY STRUCT is returned. * Therefore, inserting the output of the writer into a * table will never result in NULL insertions. * To pass a NULL Geometry into an Oracle SDO_GEOMETRY-valued parameter using JDBC, use *

   * java.sql.CallableStatement.setNull(index, java.sql.Types.STRUCT, "MDSYS.SDO_GEOMETRY"). 
   * 
* * @param geom the geometry to encode * @return a Oracle MDSYS.SDO_GEOMETRY STRUCT representing the geometry * @throws SQLException if an encoding error was encountered */ public STRUCT write(Geometry geom, OracleConnection connection) 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 ); if (geom == null || geom.isEmpty() || geom.getCoordinate() == null) return createEmptySDOGeometry(connection); OraGeom oraGeom = createOraGeom(geom); STRUCT SDO_POINT = null; ARRAY SDO_ELEM_INFO = null; ARRAY SDO_ORDINATES = null; if (oraGeom.point == null) { SDO_ELEM_INFO = OraUtil.toARRAY(oraGeom.elemInfo, OraGeom.TYPE_ELEM_INFO_ARRAY, connection); SDO_ORDINATES = OraUtil.toARRAY(oraGeom.ordinates, OraGeom.TYPE_ORDINATE_ARRAY, connection); } else { // Point Optimization Datum data[] = new Datum[] { OraUtil.toNUMBER(oraGeom.point[0]), OraUtil.toNUMBER(oraGeom.point[1]), OraUtil.toNUMBER(oraGeom.point[2]), }; SDO_POINT = OraUtil.toSTRUCT(data, OraGeom.TYPE_POINT_TYPE, connection); } NUMBER SDO_GTYPE = new NUMBER(oraGeom.gType); NUMBER SDO_SRID = oraGeom.srid == OraGeom.SRID_NULL ? null : new NUMBER(oraGeom.srid); Datum sdoGeometryComponents[] = new Datum[] { SDO_GTYPE, SDO_SRID, SDO_POINT, SDO_ELEM_INFO, SDO_ORDINATES }; return OraUtil.toSTRUCT(sdoGeometryComponents, OraGeom.TYPE_GEOMETRY, connection); } /** * Writes a Geometry in Oracle SDO_GEOMETRY SQL literal format. *

* Examples of output are: *

   * SDO_GEOMETRY(2001,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1,1),SDO_ORDINATE_ARRAY(50,50))
   * SDO_GEOMETRY(3001,NULL,SDO_POINT_TYPE(50,50,100,),NULL,NULL)
   * SDO_GEOMETRY(3006,8307,NULL,SDO_ELEM_INFO_ARRAY(1,2,1,  7,2,1),SDO_ORDINATE_ARRAY(0,0,2,  50,50,100,  10,10,12,  150,150,110))
   * 
* * @param geom the Geometry to write * @return a string representing the geometry as an SDO_GEOMETRY literal */ public String writeSQL(Geometry geom) { if (geom == null) return OraGeom.SQL_NULL; OraGeom oraGeom = createOraGeom(geom); return oraGeom.toString(); } private STRUCT createEmptySDOGeometry(OracleConnection connection) throws SQLException { return OraUtil.toSTRUCT(new Datum[5], OraGeom.TYPE_GEOMETRY, connection); } /** * Creates an {@link OraGeom} structure corresponding to the Oracle SDO_GEOMETRY * attributes representing the given Geometry. * This allows disconnected testing, since no Oracle types are accessed. * * @param geom the non-null, non-empty Geometry to write * @return an OraGeom structure */ OraGeom createOraGeom(Geometry geom) { int gtype = gType(geom); int srid = this.srid == OraGeom.SRID_NULL ? geom.getSRID() : this.srid; double[] point = null; int elemInfo[] = null; double[] ordinates = null; // if geometry ordinate data should be represented by SDO_ORDINATES array if (isEncodeAsPointType(geom)) { point = pointOrdinates(geom); } else { int dim = dimension(geom); List elemTriplets = new ArrayList(); List ordGeoms = new ArrayList(); int lastOrdOffset = writeElement(geom, dim, 1, elemTriplets, ordGeoms); elemInfo = flattenTriplets(elemTriplets); ordinates = writeGeometryOrdinates(elemTriplets, ordGeoms, lastOrdOffset - 1, dim); } OraGeom oraGeom = new OraGeom(gtype, srid, point, elemInfo, ordinates); return oraGeom; } /** * Extracts ordinate data for SDO_POINT_TYPE for Point geometries. * null is returned * for all non-Point geometries, or for LRS points. * This cannot be used for LRS coordinates. * Subclasses may wish to repress this method and force Points to be * represented using SDO_ORDINATES. * * @param geom the geometry providing the ordinates * @return double[] the point ordinates */ private double[] pointOrdinates(Geometry geom) { Point point = (Point) geom; Coordinate coord = point.getCoordinate(); return new double[] { coord.x, coord.y, coord.z }; } /** * Writes each geometry element which will appear in the output, * by recursing through the input geometry, * and identifying each element and how it will * appear in the output elemInfo array. * For each element the relevant geometry component * is recorded as well, to allow the ordinates * array to be written from them subsequently. * The total length of the ordinate array is summed * during this process as well (which also allows determining startingOffsets). * * @param geom * @param dim * @param offset * @param elemTriplets * @param ordGeoms * @return the final startingOffset */ private int writeElement(Geometry geom, int dim, int offset, List elemTriplets, List ordGeoms) { int interp; int geomType = OraGeom.geomType(geom); switch (geomType) { case OraGeom.GEOM_TYPE.POINT: // full point encoding - optimized one has been done earlier if possible Point point = (Point) geom; elemTriplets.add(triplet(offset, OraGeom.ETYPE.POINT, OraGeom.INTERP.POINT)); ordGeoms.add(point); return offset + dim; case OraGeom.GEOM_TYPE.MULTIPOINT: MultiPoint points = (MultiPoint) geom; int nPts = points.getNumGeometries(); // this works for nPts >= 1 (0 has already been handled) elemTriplets.add(triplet(offset, OraGeom.ETYPE.POINT, nPts)); ordGeoms.add(points); return offset + dim * nPts; case OraGeom.GEOM_TYPE.LINE: LineString line = (LineString) geom; elemTriplets.add(triplet(offset, OraGeom.ETYPE.LINE, OraGeom.INTERP.LINESTRING)); ordGeoms.add(line); return offset + dim * line.getNumPoints(); case OraGeom.GEOM_TYPE.MULTILINE: MultiLineString lines = (MultiLineString) geom; for (int i = 0; i < lines.getNumGeometries(); i++) { LineString lineElem = (LineString) lines.getGeometryN(i); offset = writeElement(lineElem, dim, offset, elemTriplets, ordGeoms); } return offset; case OraGeom.GEOM_TYPE.POLYGON: Polygon polygon = (Polygon) geom; // shell LineString ring = polygon.getExteriorRing(); interp = isWriteAsRectangle(polygon) ? OraGeom.INTERP.RECTANGLE : OraGeom.INTERP.POLYGON; elemTriplets.add(triplet(offset, OraGeom.ETYPE.POLYGON_EXTERIOR, interp)); ordGeoms.add(ring); if (interp == OraGeom.INTERP.RECTANGLE) { offset += 4; } else { offset += dim * ring.getNumPoints(); } // holes int holes = polygon.getNumInteriorRing(); for (int i = 0; i < holes; i++) { ring = polygon.getInteriorRingN(i); elemTriplets.add(triplet(offset, OraGeom.ETYPE.POLYGON_INTERIOR, OraGeom.INTERP.POLYGON)); ordGeoms.add(ring); offset += dim * ring.getNumPoints(); } return offset; case OraGeom.GEOM_TYPE.MULTIPOLYGON: MultiPolygon polys = (MultiPolygon) geom; Polygon poly; for (int i = 0; i < polys.getNumGeometries(); i++) { poly = (Polygon) polys.getGeometryN(i); offset = writeElement(poly, dim, offset, elemTriplets, ordGeoms); } return offset; case OraGeom.GEOM_TYPE.COLLECTION: GeometryCollection geoms = (GeometryCollection) geom; for (int i = 0; i < geoms.getNumGeometries(); i++) { geom = geoms.getGeometryN(i); offset = writeElement(geom, dim, offset, elemTriplets, ordGeoms); } return offset; } throw new IllegalArgumentException("Cannot encode JTS " + geom.getGeometryType() + " as SDO_ELEM_INFO " + "(Limited to Point, Line, Polygon, GeometryCollection, MultiPoint," + " MultiLineString and MultiPolygon)"); } private static int[] triplet(int sOffset, int etype, int interp) { return new int[] { sOffset, etype, interp }; } private int[] flattenTriplets(List elemTriplets) { int[] elemInfo = new int[3 * elemTriplets.size()]; int eiIndex = 0; for (int i = 0; i < elemTriplets.size(); i++) { int[] triplet = (int[]) elemTriplets.get(i); for (int ii = 0; ii < 3; ii++) { elemInfo[eiIndex++] = triplet[ii]; } } return elemInfo; } /** * Writes the ordinate values extracted from each element Geometry * into a double array. * This optimizes memory usage by only allocating the single * double array required to pass the ordinates to the Oracle STRUCT. * * @param elemTriplets * @param ordGeoms * @param ordSize * @param dim * @return the final ordinate array */ private double[] writeGeometryOrdinates(List elemTriplets, List ordGeoms, int ordSize, int dim) { double[] ords = new double[ordSize]; int ordIndex = 0; for (int ielem = 0; ielem < elemTriplets.size(); ielem++) { int[] triplet = (int[]) elemTriplets.get(ielem); int startOffset = triplet[0]; //verify startOffset is same as ordIndex Assert.isTrue(startOffset == ordIndex + 1, "ElemInfo computed startingOffset does not match actual ordinates position"); int elemType = triplet[1]; int interp = triplet[2]; Geometry geom = (Geometry) ordGeoms.get(ielem); switch (elemType) { case OraGeom.ETYPE.POINT: if (interp == 1) { ordIndex = writeOrds(((Point) geom).getCoordinateSequence(), dim, ords, ordIndex); } else { // must be > 1 - write MultiPoint ordIndex = writeOrds((MultiPoint) geom, dim, ords, ordIndex); } break; case OraGeom.ETYPE.LINE: ordIndex = writeOrds(((LineString) geom).getCoordinateSequence(), dim, ords, ordIndex); break; case OraGeom.ETYPE.POLYGON_EXTERIOR: if (interp == OraGeom.INTERP.RECTANGLE) { ordIndex = writeRectangleOrds(geom, dim, ords, ordIndex); } else { ordIndex = writeOrdsOriented(((LineString) geom).getCoordinateSequence(), dim, ords, ordIndex, true); } break; case OraGeom.ETYPE.POLYGON_INTERIOR: ordIndex = writeOrdsOriented(((LineString) geom).getCoordinateSequence(), dim, ords, ordIndex, false); break; } } return ords; } /** * Writes ordinates in the orientation * specified by the isWriteCCW CCW flag. * Coordinates are reversed if necessary. * * @param seq the coordinates to write * @param dim the output dimension required * @param ordData the ordinates array * @param ordIndex the starting index in the ordinates array * @param isWriteCCW true if the ordinates should be written in CCW orientation, false if CW * @return the next index to write in the ordinates array */ private int writeOrdsOriented(CoordinateSequence seq, int dim, double[] ordData, int ordIndex, boolean isWriteCCW) { Coordinate[] coords = seq.toCoordinateArray(); //TODO: add method to CGAlgorithms to compute isCCW for CoordinateSequences boolean isCCW = CGAlgorithms.isCCW(coords); if (isCCW != isWriteCCW) { return writeOrdsReverse(seq, dim, ordData, ordIndex); } return writeOrds(seq, dim, ordData, ordIndex); } private int writeOrdsReverse(CoordinateSequence seq, int dim, double[] ordData, int ordIndex) { int nCoord = seq.size(); for (int i = nCoord-1; i >= 0; i--) { for (int id = 0; id < dim; id++) { ordData[ordIndex++] = seq.getOrdinate(i, id); } } return ordIndex; } private int writeOrds(CoordinateSequence seq, int dim, double[] ordData, int ordIndex) { int nCoord = seq.size(); for (int i = 0; i < nCoord; i++) { for (int id = 0; id < dim; id++) { ordData[ordIndex++] = seq.getOrdinate(i, id); } } return ordIndex; } private int writeOrds(MultiPoint geom, int dim, double[] ordData, int ordIndex) { int nGeom = geom.getNumGeometries(); for (int i = 0; i < nGeom; i++) { CoordinateSequence seq = ((Point) geom.getGeometryN(i)).getCoordinateSequence(); for (int id = 0; id < dim; id++) { ordData[ordIndex++] = seq.getOrdinate(0, id); } } return ordIndex; } private int writeRectangleOrds(Geometry ring, int dim, double[] ordData, int ordIndex) { Envelope e = ring.getEnvelopeInternal(); ordData[ordIndex++] = e.getMinX(); ordData[ordIndex++] = e.getMinY(); ordData[ordIndex++] = e.getMaxX(); ordData[ordIndex++] = e.getMaxY(); return ordIndex; } /** * Tests if a polygon can be written aa a RECTANGLE. * Rectangles are only supported without a SRID! * * @param polygon * @return true if polygon is SRID==NULL and a rectangle */ private boolean isWriteAsRectangle(Polygon polygon) { if (! isOptimizeRectangle) return false; if (lrsDim(polygon) != 0) { // cannot support LRS on a rectangle return false; } return polygon.isRectangle(); } private boolean isEncodeAsPointType(Geometry geom) { if (! isOptimizePoint) return false; if (geom instanceof Point && (lrsDim(geom) == 0) && outputDimension <= 3) return true; // Geometry type is not appropriate for SDO_POINT_TYPE return false; } /** * Produce SDO_GTYPE code for input Geometry. * * @param geom * @return SDO_GTYPE code */ private int gType(Geometry geom) { return OraGeom.gType(dimension(geom), lrsDim(geom), OraGeom.geomType(geom)); } /** * Return dimension of output coordinates (either 2,3 or 4), * respecting the explicit output dimension (if any). * * @param geom * @return coordinate dimension number */ private int dimension(Geometry geom) { if (outputDimension != OraGeom.NULL_DIMENSION) return outputDimension; //TODO: check dimension of a geometry CoordinateSequence to determine dimension int d = Double.isNaN(geom.getCoordinate().z) ? 2 : 3; return d; } /** * Return LRS dimension as defined by SDO_GTYPE (either 3,4 or 0). * * @param geom * @return LRS dimension */ private int lrsDim(Geometry geom) { //TODO: implement measure support when available return 0; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy