package com.revolsys.oracle.recordstore.field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.revolsys.datatype.DataType;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.ClockDirection;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Lineal;
import com.revolsys.geometry.model.LinearRing;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.Polygonal;
import com.revolsys.geometry.model.Punctual;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.valid.CoordinateInfiniteError;
import com.revolsys.geometry.operation.valid.CoordinateNaNError;
import com.revolsys.jdbc.JdbcUtils;
import com.revolsys.jdbc.field.JdbcFieldDefinition;
import com.revolsys.record.Record;
import com.revolsys.record.property.FieldProperties;
import com.revolsys.util.Property;
import com.revolsys.util.number.Numbers;
public class OracleSdoGeometryJdbcFieldDefinition extends JdbcFieldDefinition {
private static final int[] LINESTRING_ELEM_INFO = new int[] {
1, 2, 1
};
private static final String MDSYS_SDO_GEOMETRY = "MDSYS.SDO_GEOMETRY";
private static final String MDSYS_SDO_POINT_TYPE = "MDSYS.SDO_POINT_TYPE";
private static final int[] RECTANGLE_ELEM_INFO = new int[] {
1, 1003, 3
};
private static final double NAN_VALUE = 0;
private final int axisCount;
private final GeometryFactory geometryFactory;
private final int oracleSrid;
public OracleSdoGeometryJdbcFieldDefinition(final String dbName, final String name,
final DataType type, final int sqlType, final boolean required, final String description,
final Map<String, Object> properties, final GeometryFactory geometryFactory,
final int axisCount, final int oracleSrid) {
super(dbName, name, type, sqlType, 0, 0, required, description, properties);
this.geometryFactory = geometryFactory;
this.axisCount = axisCount;
this.oracleSrid = oracleSrid;
setProperty(FieldProperties.GEOMETRY_FACTORY, geometryFactory);
}
@Override
public void addColumnName(final StringBuilder sql, final String tablePrefix) {
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_GTYPE, ");
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_POINT.X, ");
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_POINT.Y, ");
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_POINT.Z, ");
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_ELEM_INFO, ");
sql.append(tablePrefix);
sql.append(".GEOMETRY.SDO_ORDINATES");
}
@Override
public OracleSdoGeometryJdbcFieldDefinition clone() {
return new OracleSdoGeometryJdbcFieldDefinition(getDbName(), getName(), getDataType(),
getSqlType(), isRequired(), getDescription(), getProperties(), this.geometryFactory,
this.axisCount, this.oracleSrid);
}
@Override
public int setFieldValueFromResultSet(final ResultSet resultSet, final int columnIndex,
final Record record) throws SQLException {
Geometry value;
final int geometryType = resultSet.getInt(columnIndex);
if (!resultSet.wasNull()) {
final int axisCount = geometryType / 1000;
switch (geometryType % 1000) {
case 1:
value = toPoint(resultSet, columnIndex, axisCount);
break;
case 2:
value = toLineString(resultSet, columnIndex, axisCount);
break;
case 3:
value = toPolygon(resultSet, columnIndex, axisCount);
break;
case 5:
value = toPunctual(resultSet, columnIndex, axisCount);
break;
case 6:
value = toLineal(resultSet, columnIndex, axisCount);
break;
case 7:
value = toPolygonal(resultSet, columnIndex, axisCount);
break;
default:
throw new IllegalArgumentException("Unsupported geometry type " + geometryType);
}
record.setValue(getIndex(), value);
}
return columnIndex + 6;
}
@Override
public int setInsertPreparedStatementValue(final PreparedStatement statement,
final int parameterIndex, final Record record) throws SQLException {
final String name = getName();
final Object value = record.getValue(name);
if (Property.isEmpty(value)) {
statement.setNull(parameterIndex, Types.STRUCT, "SDO_GEOMETRY");
} else {
final Connection connection = statement.getConnection();
final Struct oracleValue = toSdoGeometry(connection, value, this.axisCount);
statement.setObject(parameterIndex, oracleValue);
}
return parameterIndex + 1;
}
@Override
public int setPreparedStatementValue(final PreparedStatement statement, final int parameterIndex,
final Object value) throws SQLException {
if (Property.isEmpty(value)) {
statement.setNull(parameterIndex, Types.STRUCT, "SDO_GEOMETRY");
} else {
final Connection connection = statement.getConnection();
final Struct oracleValue = toSdoGeometry(connection, value, 2);
statement.setObject(parameterIndex, oracleValue);
}
return parameterIndex + 1;
}
private Lineal toLineal(final ResultSet resultSet, final int columnIndex, final int axisCount)
throws SQLException {
final List<LineString> lines = new ArrayList<>();
final BigDecimal[] elemInfo = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 4);
final BigDecimal[] coordinatesArray = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 5);
for (int i = 0; i < elemInfo.length; i += 3) {
final int offset = elemInfo[i].intValue();
final int type = elemInfo[i + 1].intValue();
final int interpretation = elemInfo[i + 2].intValue();
int length;
if (i + 3 < elemInfo.length) {
final long nextOffset = elemInfo[i + 3].intValue();
length = (int)(nextOffset - offset);
} else {
length = coordinatesArray.length - offset + 1;
}
if (interpretation == 1) {
final double[] coordinates = Numbers.toDoubleArray(coordinatesArray, offset - 1, length);
final LineString points = this.geometryFactory.lineString(axisCount, coordinates);
lines.add(points);
} else {
throw new IllegalArgumentException(
"Unsupported geometry type " + type + " interpretation " + interpretation);
}
}
if (lines.size() == 1) {
return lines.get(0);
} else {
return this.geometryFactory.lineal(lines);
}
}
private LineString toLineString(final ResultSet resultSet, final int columnIndex,
final int axisCount) throws SQLException {
final int index = columnIndex + 5;
final BigDecimal[] coordinates = JdbcUtils.getBigDecimalArray(resultSet, index);
return this.geometryFactory.lineString(axisCount, coordinates);
}
private Point toPoint(final ResultSet resultSet, final int columnIndex, final int axisCount)
throws SQLException {
final double x = resultSet.getDouble(columnIndex + 1);
final double y = resultSet.getDouble(columnIndex + 2);
if (axisCount == 2) {
return this.geometryFactory.point(x, y);
} else {
final double z = resultSet.getDouble(columnIndex + 3);
return this.geometryFactory.point(x, y, z);
}
}
private Polygon toPolygon(final ResultSet resultSet, final int columnIndex, final int axisCount)
throws SQLException {
final BigDecimal[] elemInfo = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 4);
final BigDecimal[] coordinatesArray = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 5);
final List<LinearRing> rings = new ArrayList<>();
int numInteriorRings = 0;
for (int i = 0; i < elemInfo.length; i += 3) {
final int offset = elemInfo[i].intValue();
final long type = elemInfo[i + 1].longValue();
final long interpretation = elemInfo[i + 2].longValue();
int length;
if (i + 3 < elemInfo.length) {
final long nextOffset = elemInfo[i + 3].longValue();
length = (int)(nextOffset - offset);
} else {
length = coordinatesArray.length - offset + 1;
}
if (interpretation == 1) {
final double[] coordinates = Numbers.toDoubleArray(coordinatesArray, offset - 1, length);
final LinearRing ring = this.geometryFactory.linearRing(axisCount, coordinates);
switch ((int)type) {
case 1003:
if (rings.isEmpty()) {
rings.add(ring);
} else {
throw new IllegalArgumentException("Cannot have two exterior rings on a geometry");
}
break;
case 2003:
if (numInteriorRings == rings.size()) {
throw new IllegalArgumentException("Too many interior rings");
} else {
numInteriorRings++;
rings.add(ring);
}
break;
default:
throw new IllegalArgumentException("Unsupported geometry type " + type);
}
} else {
throw new IllegalArgumentException(
"Unsupported geometry type " + type + " interpretation " + interpretation);
}
}
final Polygon polygon = this.geometryFactory.polygon(rings);
return polygon;
}
private Polygonal toPolygonal(final ResultSet resultSet, final int columnIndex,
final int axisCount) throws SQLException {
final List<Polygon> polygons = new ArrayList<>();
final BigDecimal[] elemInfo = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 4);
final BigDecimal[] coordinatesArray = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 5);
final int coordinateCount = coordinatesArray.length;
List<LinearRing> rings = Collections.emptyList();
for (int i = 0; i < elemInfo.length; i += 3) {
final int offset = elemInfo[i].intValue();
final long type = elemInfo[i + 1].longValue();
final long interpretation = elemInfo[i + 2].longValue();
int length;
if (i + 3 < elemInfo.length) {
final long nextOffset = elemInfo[i + 3].longValue();
length = (int)(nextOffset - offset);
} else {
length = coordinateCount + 1 - offset;
}
if (interpretation == 1) {
final double[] coordinates = Numbers.toDoubleArray(coordinatesArray, offset - 1, length);
final LinearRing ring = this.geometryFactory.linearRing(axisCount, coordinates);
switch ((int)type) {
case 1003:
if (!rings.isEmpty()) {
final Polygon polygon = this.geometryFactory.polygon(rings);
polygons.add(polygon);
}
rings = new ArrayList<>();
rings.add(ring);
break;
case 2003:
rings.add(ring);
break;
default:
throw new IllegalArgumentException("Unsupported geometry type " + type);
}
} else {
throw new IllegalArgumentException(
"Unsupported geometry type " + type + " interpretation " + interpretation);
}
}
if (!rings.isEmpty()) {
final Polygon polygon = this.geometryFactory.polygon(rings);
polygons.add(polygon);
}
if (polygons.size() == 1) {
return polygons.get(0);
} else {
return this.geometryFactory.polygonal(polygons);
}
}
private Punctual toPunctual(final ResultSet resultSet, final int columnIndex, final int axisCount)
throws SQLException {
final BigDecimal[] coordinatesArray = JdbcUtils.getBigDecimalArray(resultSet, columnIndex + 5);
final int vertexCount = coordinatesArray.length / axisCount;
if (vertexCount == 1) {
final double[] coordinates = new double[axisCount];
for (int i = 0; i < axisCount; i++) {
coordinates[i] = coordinatesArray[i].doubleValue();
}
final Point point = this.geometryFactory.point(coordinates);
return point;
} else {
final Point[] points = new Point[vertexCount];
int coordinateIndex = 0;
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
final double[] coordinates = new double[axisCount];
for (int i = 0; i < axisCount; i++) {
coordinates[i] = coordinatesArray[coordinateIndex++].doubleValue();
}
final Point point = this.geometryFactory.point(coordinates);
points[vertexIndex] = point;
}
return this.geometryFactory.punctual(points);
}
}
private int toSdoAddPolygon(int offset, final int[] elemInfo, int elemIndex, final int axisCount,
final double[] coordinates, final Polygon polygon) {
final LinearRing shell = polygon.getShell();
offset = toSodAddPolygonRing(offset, elemInfo, elemIndex, 1003, axisCount, coordinates,
ClockDirection.COUNTER_CLOCKWISE, shell);
for (final LinearRing hole : polygon.holes()) {
elemIndex += 3;
offset = toSodAddPolygonRing(offset, elemInfo, elemIndex, 2003, axisCount, coordinates,
ClockDirection.CLOCKWISE, hole);
}
return offset;
}
private Struct toSdoGeometry(final Connection connection, final int geometryType,
final Struct pointStruct, final int[] elemInfo, final double... coordinates)
throws SQLException {
return JdbcUtils.struct(connection, MDSYS_SDO_GEOMETRY, geometryType, this.oracleSrid,
pointStruct, elemInfo, coordinates);
}
private Struct toSdoGeometry(final Connection connection, final Object object,
final int axisCount) throws SQLException {
if (object instanceof Geometry) {
Geometry geometry = (Geometry)object;
geometry = geometry.newGeometry(this.geometryFactory);
if (object instanceof Polygon) {
final Polygon polygon = (Polygon)geometry;
return toSdoPolygon(connection, polygon, axisCount);
} else if (object instanceof LineString) {
final LineString lineString = (LineString)geometry;
return toSdoLineString(connection, lineString, axisCount);
} else if (object instanceof Point) {
final Point point = (Point)geometry;
return toSdoPoint(connection, point, axisCount);
} else if (object instanceof Punctual) {
final Punctual punctual = (Punctual)geometry;
return toSdoMultiPoint(connection, punctual, axisCount);
} else if (object instanceof Lineal) {
final Lineal lineal = (Lineal)geometry;
return toSdoMultiLineString(connection, lineal, axisCount);
} else if (object instanceof Polygonal) {
final Polygonal polygonal = (Polygonal)geometry;
return toSdoMultiPolygon(connection, polygonal, axisCount);
}
} else if (object instanceof BoundingBox) {
BoundingBox boundingBox = (BoundingBox)object;
boundingBox = boundingBox.convert(this.geometryFactory, 2);
final double minX = boundingBox.getMinX();
final double minY = boundingBox.getMinY();
final double maxX = boundingBox.getMaxX();
final double maxY = boundingBox.getMaxY();
return toSdoGeometry(connection, 3, null, RECTANGLE_ELEM_INFO, minX, minY, maxX, maxY);
}
throw new IllegalArgumentException("Unable to convert to SDO_GEOMETRY " + object.getClass());
}
private Struct toSdoLineString(final Connection connection, final LineString line,
final int axisCount) throws SQLException {
final int geometryType = axisCount * 1000 + 2;
final int vertexCount = line.getVertexCount();
final double[] coordinates = new double[vertexCount * axisCount];
line.copyCoordinates(axisCount, NAN_VALUE, coordinates, 0);
return toSdoGeometry(connection, geometryType, null, LINESTRING_ELEM_INFO, coordinates);
}
private Struct toSdoMultiLineString(final Connection connection, final Lineal lineal,
final int axisCount) throws SQLException {
final int geometryType = axisCount * 1000 + 6;
final int geometryCount = lineal.getGeometryCount();
final int[] elemInfo = new int[geometryCount * 3];
final int vertexCount = lineal.getVertexCount();
final int coordinateCount = vertexCount * axisCount;
final double[] coordinates = new double[coordinateCount];
int offset = 0;
int elemIndex = 0;
for (final LineString line : lineal.lineStrings()) {
elemInfo[elemIndex++] = offset + 1;
elemInfo[elemIndex++] = 2;
elemInfo[elemIndex++] = 1;
offset = line.copyCoordinates(axisCount, NAN_VALUE, coordinates, offset);
}
return toSdoGeometry(connection, geometryType, null, elemInfo, coordinates);
}
private Struct toSdoMultiPoint(final Connection connection, final Punctual punctual,
final int axisCount) throws SQLException {
final int geometryType = axisCount * 1000 + 5;
final int geometryCount = punctual.getGeometryCount();
final int[] elemInfo = new int[] {
1, 1, geometryCount
};
final double[] coordinates = new double[geometryCount * axisCount];
int i = 0;
for (final Point point : punctual.points()) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value = point.getCoordinate(axisIndex);
if (Double.isNaN(value)) {
coordinates[i] = NAN_VALUE;
} else {
coordinates[i] = value;
}
i++;
}
}
return toSdoGeometry(connection, geometryType, null, elemInfo, coordinates);
}
private Struct toSdoMultiPolygon(final Connection connection, final Polygonal polygonal,
final int axisCount) throws SQLException {
final int geometryType = axisCount * 1000 + 7;
final int vertexCount = polygonal.getVertexCount();
final int coordinateCount = vertexCount * axisCount;
final double[] coordinates = new double[coordinateCount];
int ringCount = 0;
for (final Polygon polygon : polygonal.polygons()) {
ringCount += polygon.getRingCount();
}
final int[] elemInfo = new int[ringCount * 3];
int offset = 0;
int elemIndex = 0;
for (final Polygon polygon : polygonal.polygons()) {
offset = toSdoAddPolygon(offset, elemInfo, elemIndex, axisCount, coordinates, polygon);
elemIndex += 3 * polygon.getRingCount();
}
return toSdoGeometry(connection, geometryType, null, elemInfo, coordinates);
}
private Struct toSdoPoint(final Connection connection, final Point point, final int axisCount)
throws SQLException {
final double x = point.getX();
final double y = point.getY();
validateCoordinate(point, 0, x);
validateCoordinate(point, 1, y);
Double z = null;
int geometryType = 1;
if (axisCount > 2) {
geometryType = 3001;
z = point.getZ();
if (Double.isNaN(z)) {
z = null;
}
}
final Struct pointStruct = JdbcUtils.struct(connection, MDSYS_SDO_POINT_TYPE, x, y, z);
return toSdoGeometry(connection, geometryType, pointStruct, null, null);
}
private Struct toSdoPolygon(final Connection connection, final Polygon polygon,
final int axisCount) throws SQLException {
final int geometryType = axisCount * 1000 + 3;
final int ringCount = polygon.getRingCount();
final int[] elemInfo = new int[ringCount * 3];
final int vertexCount = polygon.getVertexCount();
final int coordinateCount = vertexCount * axisCount;
final double[] coordinates = new double[coordinateCount];
toSdoAddPolygon(0, elemInfo, 0, axisCount, coordinates, polygon);
return toSdoGeometry(connection, geometryType, null, elemInfo, coordinates);
}
private int toSodAddPolygonRing(int offset, final int[] elemInfo, final int elemIndex,
final int elemType, final int axisCount, final double[] coordinates,
final ClockDirection expectedRingOrientation, final LinearRing ring) {
elemInfo[elemIndex] = offset + 1;
elemInfo[elemIndex + 1] = elemType; // Exterior counter clockwise
elemInfo[elemIndex + 2] = 1;
final ClockDirection ringOrientation = ring.getClockDirection();
if (ringOrientation == expectedRingOrientation) {
offset = ring.copyCoordinates(axisCount, NAN_VALUE, coordinates, offset);
} else {
offset = ring.copyCoordinatesReverse(axisCount, NAN_VALUE, coordinates, offset);
}
return offset;
}
protected void validateCoordinate(final Point point, final int axisIndex,
final double coordinate) {
if (!Double.isFinite(coordinate)) {
final Vertex vertex = point.getVertex(axisIndex);
if (Double.isNaN(coordinate)) {
throw new CoordinateNaNError(vertex, axisIndex);
} else {
throw new CoordinateInfiniteError(vertex, axisIndex);
}
}
}
}