This is an automated email from the ASF dual-hosted git repository. amanin pushed a commit to branch refactor/sql-store in repository https://gitbox.apache.org/repos/asf/sis.git
commit 08b8773706a86070f5fa082dcf3365fbd099469b Author: Alexis Manin <[email protected]> AuthorDate: Wed Nov 6 16:45:44 2019 +0100 WIP(SQLStore): Add PostGIS support. --- .../sis/internal/sql/feature/EWKBReader.java | 151 +++++++++++++++++++++ .../internal/sql/feature/GeometryIdentifier.java | 97 +++++++++++++ 2 files changed, 248 insertions(+) diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java new file mode 100644 index 0000000..1681034 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java @@ -0,0 +1,151 @@ +package org.apache.sis.internal.sql.feature; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import org.apache.sis.internal.feature.Geometries; +import org.apache.sis.math.Vector; +import org.apache.sis.setup.GeometryLibrary; + +/** + * PostGIS Hexa-EWKB Geometry reader/write classes. + * http://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT + * + * This format is the natural form returned by a query selection a geometry field + * whithout using any ST_X method. + * + * @author Johann Sorel (Geomatys) + */ +class EWKBReader { + + private static final int MASK_Z = 0x80000000; + private static final int MASK_M = 0x40000000; + private static final int MASK_SRID = 0x20000000; + private static final int MASK_GEOMTYPE = 0x1FFFFFFF; + + // Copied from PostGIS JDBC source code to avoid dependency for too little information. + private static final int POINT = 1; + private static final int LINESTRING = 2; + private static final int POLYGON = 3; + private static final int MULTIPOINT = 4; + private static final int MULTILINESTRING = 5; + private static final int MULTIPOLYGON = 6; + private static final int GEOMETRYCOLLECTION = 7; + + private final Geometries factory; + + EWKBReader() { + this(null); + } + + EWKBReader(GeometryLibrary library) { + this.factory = Geometries.implementation(library); + } + + Object read(final byte[] eWkb) { + return new Reader(ByteBuffer.wrap(eWkb)).read(); + } + + private class Reader { + final ByteBuffer buffer; + final int geomType; + final int dimension; + final int srid; + + private Reader(ByteBuffer buffer) { + final byte endianess = buffer.get(); + if (isBigEndian(endianess)) { + this.buffer = buffer.order(ByteOrder.BIG_ENDIAN); + } else this.buffer = buffer; + final int flags = buffer.getInt(); + final boolean flagZ = (flags & MASK_Z) != 0; + final boolean flagM = (flags & MASK_M) != 0; + final boolean flagSRID = (flags & MASK_SRID) != 0; + geomType = (flags & MASK_GEOMTYPE); + dimension = 2 + ((flagZ)?1:0) + ((flagM)?1:0); + srid = flagSRID ? buffer.getInt() : 0; + } + + Object read() { + final Object geom = decodeGeometry(); + if (srid > 0) { + // TODO: set CRS + } + return geom; + } + + Object decodeGeometry() { + switch (geomType) { + case POINT: return readPoint(); + case LINESTRING: return readLineString(); + case POLYGON: return readPolygon(); + case MULTIPOINT: return readMultiPoint(); + case MULTILINESTRING: return readMultiLineString(); + case MULTIPOLYGON: return readMultiPolygon(); + case GEOMETRYCOLLECTION: return readCollection(); + } + + throw new UnsupportedOperationException("Unsupported geometry type: "+geomType); + } + + private Object readMultiLineString() { + throw new UnsupportedOperationException(); + } + + private Object readMultiPolygon() { + throw new UnsupportedOperationException(); + } + + private Object readCollection() { + throw new UnsupportedOperationException(); + } + + final Object readPoint() { + final double[] ordinates = readCoordinateSequence(1); + // TODO: we lose information here ! We need to evolve Geometry API. + return factory.createPoint(ordinates[0], ordinates[1]); + } + + final Object readLineString() { + final double[] ordinates = readCoordinateSequence(); + return factory.createPolyline(dimension, Vector.create(ordinates)); + } + + private Object readPolygon() { + final int nbRings=buffer.getInt(); + final Vector outerShell = Vector.create(readCoordinateSequence()); + + final double[] nans = new double[dimension]; + Arrays.fill(nans, Double.NaN); + final Vector separator = Vector.create(nans); + final Vector[] allShells = new Vector[nbRings + nbRings -1]; // include ring separators + allShells[0] = outerShell; + for (int i = 1 ; i < nbRings ;) { + allShells[i++] = separator; + allShells[i++] = Vector.create(readCoordinateSequence()); + } + + return factory.toPolygon(factory.createPolyline(dimension, outerShell)); + } + + private double[] readMultiPoint() { + throw new UnsupportedOperationException(); + } + + private double[] readCoordinateSequence() { + return readCoordinateSequence(buffer.getInt()); + } + + private double[] readCoordinateSequence(int nbPts) { + final double[] brutPoint = new double[dimension*nbPts]; + for (int i = 0 ; i < brutPoint.length ; i++) brutPoint[i] = buffer.getDouble(); + return brutPoint; + } + + } + + private static boolean isBigEndian(byte endianess) { + return endianess == 0; // org.postgis.binary.ValueGetter.XDR.NUMBER + } +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java new file mode 100644 index 0000000..cd0cdc0 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java @@ -0,0 +1,97 @@ +package org.apache.sis.internal.sql.feature; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.apache.sis.internal.metadata.sql.SQLBuilder; + +import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty; + +/** + * Not THREAD-SAFE ! + */ +class GeometryIdentifier implements AutoCloseable { + + private final Connection c; + final PreparedStatement identifySchemaQuery; + final PreparedStatement columnQuery; + + /** + * Prefetch an SQL builder, so subsequent calls will use a copy constructor to avoid re-analyzing database metadata. + */ + private final SQLBuilder template; + + public GeometryIdentifier(Connection c, boolean quoteSchema) throws SQLException { + this.c = c; + template = new SQLBuilder(c.getMetaData(), quoteSchema); + identifySchemaQuery = c.prepareStatement("SELECT DISTINCT(f_table_schema) FROM GEOMETRY_COLUMNS WHERE f_table_name = ?"); + columnQuery = c.prepareStatement("SELECT f_geometry_column, coord_dimension, srid FROM GEOMETRY_COLUMNS WHERE f_schema_name = ? AND table = ?"); + } + + Set<GeometryColumn> fetchGeometricColumns(String schema, final String table) throws SQLException { + final SQLBuilder builder = new SQLBuilder(template); + ensureNonEmpty("Table name", table); + if (schema == null || (schema = schema.trim()).isEmpty()) { + // To avoid ambiguity, we have to restrict search to a single schema + identifySchemaQuery.setString(1, table); + try (ResultSet result = identifySchemaQuery.executeQuery()) { + if (!result.next()) return Collections.EMPTY_SET; + schema = result.getString(1); + if (result.next()) throw new IllegalArgumentException("Multiple tables match given name. Please specify schema to remove all ambiguities"); + } + } + + columnQuery.setString(1, schema); + columnQuery.setString(2, table); + try (ResultSet result = columnQuery.executeQuery()) { + final HashSet<GeometryColumn> cols = new HashSet<>(); + while (result.next()) { + cols.add(new GeometryColumn(result.getString(1), result.getInt(2), result.getInt(3))); + } + return cols; + } + } + + @Override + public void close() throws SQLException { + try (SQLCloseable c1 = columnQuery::close; SQLCloseable c2 = identifySchemaQuery::close;) {} + } + + private interface SQLCloseable extends AutoCloseable { + @Override + void close() throws SQLException; + } + + static final class GeometryColumn { + final String name; + final int dimension; + final int srid; + + GeometryColumn(String name, int dimension, int srid) { + this.name = name; + this.dimension = dimension; + this.srid = srid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GeometryColumn that = (GeometryColumn) o; + return dimension == that.dimension && + srid == that.srid && + name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } +}
