package com.revolsys.geopackage; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.Map; import com.revolsys.datatype.DataType; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.jdbc.field.JdbcFieldDefinition; import com.revolsys.record.Record; import com.revolsys.record.property.FieldProperties; public class GeoPackageGeometryJdbcFieldDefinition extends JdbcFieldDefinition { private final int axisCount; private final GeometryFactory geometryFactory; private final int srid; public GeoPackageGeometryJdbcFieldDefinition(final String dbName, final String name, final DataType dataType, final boolean required, final String description, final Map<String, Object> properties, final int srid, final int axisCount, final GeometryFactory geometryFactory) { super(dbName, name, dataType, -1, 0, 0, required, description, properties); this.srid = srid; this.geometryFactory = geometryFactory; setProperty(FieldProperties.GEOMETRY_FACTORY, geometryFactory); this.axisCount = axisCount; } @Override public JdbcFieldDefinition clone() { return new GeoPackageGeometryJdbcFieldDefinition(getDbName(), getName(), getDataType(), isRequired(), getDescription(), getProperties(), this.srid, this.axisCount, this.geometryFactory); } public Object getInsertUpdateValue(final Object object) throws SQLException { if (object == null) { return null; } else if (object instanceof Geometry) { final Geometry geometry = (Geometry)object; // TODO return null; } else if (object instanceof BoundingBox) { // TODO return null; } else { return object; } } private Geometry parseCollection(final GeometryFactory geometryFactory, final ByteBuffer data) { final int count = data.getInt(); final Geometry[] geoms = new Geometry[count]; parseGeometryArray(geometryFactory, data, geoms); return geometryFactory.geometry(geoms); } private double[] parseCoordinates(final int axisCount, final ByteBuffer data, final boolean hasZ, final boolean hasM) { final int vertexCount = data.getInt(); final double[] coordinates = new double[axisCount * vertexCount]; int coordinateIndex = 0; for (int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { final double x = data.getDouble(); coordinates[coordinateIndex++] = x; final double y = data.getDouble(); coordinates[coordinateIndex++] = y; if (hasM) { if (hasZ) { final double z = data.getDouble(); coordinates[coordinateIndex++] = z; final double m = data.getDouble(); coordinates[coordinateIndex++] = m; } else { coordinateIndex++; // no z so increment index final double m = data.getDouble(); coordinates[coordinateIndex++] = m; } } else if (hasZ) { final double z = data.getDouble(); coordinates[coordinateIndex++] = z; } } return coordinates; } private Geometry parseGeometry(GeometryFactory geometryFactory, final ByteBuffer buffer) { ByteOrder byteOrder; if (buffer.get() == 0) { byteOrder = ByteOrder.BIG_ENDIAN; } else { byteOrder = ByteOrder.LITTLE_ENDIAN; } buffer.order(byteOrder); final int flags = buffer.getInt(); final int geometryType = flags % 1000; final int geometryTypeMode = flags / 1000; boolean hasZ = false; boolean hasM = false; switch (geometryTypeMode) { case 0: break; case 1: hasZ = true; break; case 2: hasM = true; break; case 3: hasZ = true; hasM = true; break; } int axisCount; if (hasM) { axisCount = 4; } else if (hasZ) { axisCount = 3; } else { axisCount = 2; } if (axisCount != geometryFactory.getAxisCount()) { geometryFactory = geometryFactory.convertAxisCount(axisCount); } Geometry geometry; switch (geometryType) { case 1: geometry = parsePoint(geometryFactory, buffer, hasZ, hasM); break; case 2: geometry = parseLineString(geometryFactory, buffer, hasZ, hasM); break; case 3: geometry = parsePolygon(geometryFactory, buffer, hasZ, hasM); break; case 4: geometry = parseMultiPoint(geometryFactory, buffer); break; case 5: geometry = parseMultiLineString(geometryFactory, buffer); break; case 6: geometry = parseMultiPolygon(geometryFactory, buffer); break; case 7: geometry = parseCollection(geometryFactory, buffer); break; default: throw new IllegalArgumentException("Unknown Geometry Type: " + geometryType); } return geometry; } private void parseGeometryArray(final GeometryFactory geometryFactory, final ByteBuffer data, final Geometry[] container) { for (int i = 0; i < container.length; ++i) { data.get(); // read endian container[i] = parseGeometry(geometryFactory, data); } } private LinearRing parseLinearRing(final GeometryFactory geometryFactory, final ByteBuffer data, final boolean hasZ, final boolean hasM) { final int axisCount = geometryFactory.getAxisCount(); final double[] coordinates = parseCoordinates(axisCount, data, hasZ, hasM); return geometryFactory.linearRing(axisCount, coordinates); } private LineString parseLineString(final GeometryFactory geometryFactory, final ByteBuffer data, final boolean hasZ, final boolean hasM) { final int axisCount = geometryFactory.getAxisCount(); final double[] coordinates = parseCoordinates(axisCount, data, hasZ, hasM); return geometryFactory.lineString(axisCount, coordinates); } private Geometry parseMultiLineString(final GeometryFactory geometryFactory, final ByteBuffer data) { final int count = data.getInt(); final LineString[] lines = new LineString[count]; parseGeometryArray(geometryFactory, data, lines); if (lines.length == 1) { return lines[0]; } else { return geometryFactory.lineal(lines); } } private Geometry parseMultiPoint(final GeometryFactory geometryFactory, final ByteBuffer data) { final Point[] points = new Point[data.getInt()]; parseGeometryArray(geometryFactory, data, points); if (points.length == 1) { return points[0]; } else { return geometryFactory.punctual(points); } } private Geometry parseMultiPolygon(final GeometryFactory geometryFactory, final ByteBuffer data) { final int count = data.getInt(); final Polygon[] polys = new Polygon[count]; parseGeometryArray(geometryFactory, data, polys); if (polys.length == 1) { return polys[0]; } else { return geometryFactory.polygonal(polys); } } private Point parsePoint(final GeometryFactory geometryFactory, final ByteBuffer data, final boolean hasZ, final boolean hasM) { final double x = data.getDouble(); final double y = data.getDouble(); if (hasM) { if (hasZ) { final double z = data.getDouble(); final double m = data.getDouble(); return geometryFactory.point(x, y, z, m); } else { final double m = data.getDouble(); return geometryFactory.point(x, y, Double.NaN, m); } } else if (hasZ) { final double z = data.getDouble(); return geometryFactory.point(x, y, z); } else { return geometryFactory.point(x, y); } } private Polygon parsePolygon(final GeometryFactory geometryFactory, final ByteBuffer data, final boolean hasZ, final boolean hasM) { final int count = data.getInt(); final LinearRing[] rings = new LinearRing[count]; for (int i = 0; i < count; ++i) { rings[i] = parseLinearRing(geometryFactory, data, hasZ, hasM); } return geometryFactory.polygon(rings); } private Geometry parseWkb(GeometryFactory geometryFactory, final byte[] data) { final ByteBuffer buffer = ByteBuffer.wrap(data); if (buffer.get() == 'G') { if (buffer.get() == 'P') { final byte version = buffer.get(); final byte flags = buffer.get(); final boolean extended = (flags >> 5 & 1) == 1; final boolean empty = (flags >> 4 & 1) == 1; final int envelopeType = flags >> 1 & 7; ByteOrder byteOrder; if ((flags & 1) == 0) { byteOrder = ByteOrder.BIG_ENDIAN; } else { byteOrder = ByteOrder.LITTLE_ENDIAN; } buffer.order(byteOrder); final int coordinateSystemId = buffer.getInt(); geometryFactory = geometryFactory.convertSrid(coordinateSystemId); int envelopeCoordinateCount = 0; switch (envelopeType) { case 1: envelopeCoordinateCount = 4; break; case 2: envelopeCoordinateCount = 6; break; case 3: envelopeCoordinateCount = 6; break; case 4: envelopeCoordinateCount = 8; break; default: break; } for (int i = 0; i < envelopeCoordinateCount; i++) { buffer.getDouble(); } return parseGeometry(geometryFactory, buffer); } } throw new IllegalArgumentException( "Invalid Geometry header, expecting GP\n" + Arrays.toString(data)); } @Override public int setFieldValueFromResultSet(final ResultSet resultSet, final int columnIndex, final Record object) throws SQLException { final Object databaseValue = resultSet.getObject(columnIndex); final Object value = toJava(databaseValue); object.setValue(getIndex(), value); return columnIndex + 1; } @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); final Object jdbcValue = getInsertUpdateValue(value); if (jdbcValue == null) { final int sqlType = getSqlType(); statement.setNull(parameterIndex, sqlType); } else { statement.setObject(parameterIndex, jdbcValue); } return parameterIndex + 1; } @Override public int setPreparedStatementValue(final PreparedStatement statement, final int parameterIndex, final Object value) throws SQLException { final Object jdbcValue = toJdbc(value); if (jdbcValue == null) { final int sqlType = getSqlType(); statement.setNull(parameterIndex, sqlType); } else { statement.setObject(parameterIndex, jdbcValue); } return parameterIndex + 1; } public Object toJava(final Object object) throws SQLException { if (object instanceof byte[]) { final byte[] bytes = (byte[])object; return parseWkb(this.geometryFactory, bytes); } return object; } public Object toJdbc(final Object object) throws SQLException { if (object instanceof Geometry) { final Geometry geometry = (Geometry)object; if (geometry.isEmpty()) { return null; } else { // TODO return null; } } else if (object instanceof BoundingBox) { BoundingBox boundingBox = (BoundingBox)object; boundingBox = boundingBox.convert(this.geometryFactory); // TODO return null; } else { return object; } } }