This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 4f930df Refactor the parsing of hexadecimal strings for making possible to control the decoding algorithm according the database driver. The mechanism is applied on geometries and shared by rasters. Tests are consolidated in a "SpatialFeatures" database schema. 4f930df is described below commit 4f930dfc2057a5f9b8cc66536291b8889f5613ec Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Dec 23 23:20:54 2021 +0100 Refactor the parsing of hexadecimal strings for making possible to control the decoding algorithm according the database driver. The mechanism is applied on geometries and shared by rasters. Tests are consolidated in a "SpatialFeatures" database schema. --- .../org/apache/sis/internal/jdk9/HexFormat.java | 76 +++++++++++ .../org/apache/sis/internal/jdk9/package-info.java | 2 +- storage/sis-sqlstore/pom.xml | 5 + .../sis/internal/sql/feature/BinaryEncoding.java | 138 ++++++++++++++++++++ .../apache/sis/internal/sql/feature/Column.java | 4 +- .../apache/sis/internal/sql/feature/Database.java | 34 +++-- .../{EWKBReader.java => GeometryGetter.java} | 72 ++--------- .../sis/internal/sql/feature/InfoStatements.java | 1 - .../sis/internal/sql/feature/ValueGetter.java | 17 ++- .../apache/sis/internal/sql/postgis/Postgres.java | 17 ++- .../apache/sis/internal/sql/feature/EWKBTest.java | 125 ------------------ .../internal/sql/feature/GeometryGetterTest.java | 139 +++++++++++++++++++++ .../sis/internal/sql/postgis/PostgresTest.java | 22 +++- .../org/apache/sis/test/suite/SQLTestSuite.java | 2 +- .../sis/internal/sql/feature/hexa_ewkb_4326.csv | 21 ---- .../sis/internal/sql/feature/hexa_ewkb_4326.sql | 43 ------- .../sis/internal/sql/postgis/SpatialFeatures.sql | 59 ++++++++- 17 files changed, 499 insertions(+), 278 deletions(-) diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/HexFormat.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/HexFormat.java new file mode 100644 index 0000000..0019ed2 --- /dev/null +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/HexFormat.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.jdk9; + +import org.apache.sis.util.resources.Errors; + + +/** + * Place holder for a functionality defined only in JDK17. + * + * @author Martin Desruisseaux (Geomatys) + * @since 1.2 + * @version 1.2 + * @module + */ +public final class HexFormat { + private static final HexFormat INSTANCE = new HexFormat(); + + private HexFormat() { + } + + public static HexFormat of() { + return INSTANCE; + } + + /** + * Returns the byte array parsed from the given hexadecimal string. + * + * @param string the hexadecimal string. + * @return the parsed bytes. + * @throws NumberFormatException if a character is not a hexadecimal digit. + */ + public byte[] parseHex(final CharSequence string) { + final int length = string.length(); + if ((length & 1) != 0) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.OddArrayLength_1, "wkb")); + } + final byte[] data = new byte[length >>> 1]; + for (int i=0; i<length;) { + data[i >>> 1] = (byte) ((fromHexDigit(string.charAt(i++)) << 4) | fromHexDigit(string.charAt(i++))); + } + return data; + } + + /** + * Returns the numerical value of the given hexadecimal digit. + * The hexadecimal digit can be the decimal digits 0 to 9, or the letters A to F ignoring case. + * + * <div class="note"><b>Implementation note:</b> + * we do not use {@link Character#digit(char, int)} because that method handled a large + * range of Unicode characters, which is a wider scope than what is intended here.</div> + * + * @param c the hexadecimal digit. + * @throws NumberFormatException if the given character is not a hexadecimal digit. + */ + public static int fromHexDigit(final int c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - ('A' - 10); + if (c >= 'a' && c <= 'f') return c - ('a' - 10); + throw new NumberFormatException(Errors.format(Errors.Keys.CanNotParse_1, String.valueOf(c))); + } +} diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/package-info.java index a396f43..2409866 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/package-info.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/package-info.java @@ -24,7 +24,7 @@ * may change in incompatible ways in any future version without notice. * * @author Martin Desruisseaux (Geomatys) - * @since 1.1 + * @since 1.2 * @version 0.8 * @module */ diff --git a/storage/sis-sqlstore/pom.xml b/storage/sis-sqlstore/pom.xml index 6a04a05..ae0751f 100644 --- a/storage/sis-sqlstore/pom.xml +++ b/storage/sis-sqlstore/pom.xml @@ -144,6 +144,11 @@ <artifactId>jts-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.esri.geometry</groupId> + <artifactId>esri-geometry-api</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/BinaryEncoding.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/BinaryEncoding.java new file mode 100644 index 0000000..215007f --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/BinaryEncoding.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.feature; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.apache.sis.internal.jdk9.HexFormat; + + +/** + * The way binary data are encoded in a table column. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.2 + * @module + */ +public enum BinaryEncoding { + /** + * The bytes returned by the JDBC driver in a query are directly the binary data. + */ + RAW, + + /** + * The bytes returned by the JDBC driver are encoded as hexadecimal numbers. + */ + HEXADECIMAL() { + /** Returns the value in the specified column as an array of decoded bytes. */ + @Override public byte[] getBytes(final ResultSet results, final int columnIndex) throws SQLException { + final String value = results.getString(columnIndex); + return (value != null) ? HexFormat.of().parseHex(value) : null; + } + + /** Returns an input stream decoding bytes on-the-fly. */ + @Override public InputStream decode(final InputStream source) { + return new FromHex(source); + } + }; + + /** + * Returns the value in the specified column as an array of decoded bytes. + * If the bytes returned by the JDBC driver are encoded, this method decode them. + * + * @param results the result set from which to get the values. + * @param columnIndex column from which to get the values. + * @return the column values, or {@code null} if none. + * @throws SQLException if an error occurred while fetching column values. + * + * @see ResultSet#getBytes(int) + */ + public byte[] getBytes(final ResultSet results, final int columnIndex) throws SQLException { + return results.getBytes(columnIndex); + } + + /** + * Returns an input stream decoding bytes on-the-fly. + * + * @param source the stream of data in their encoded format. + * @return a stream of decoded bytes. + * + * @see ResultSet#getBinaryStream(int) + */ + public InputStream decode(final InputStream source) { + return source; + } + + /** + * An input stream which converts hexadecimal string on-the-fly. + */ + private static final class FromHex extends InputStream { + /** The input stream providing hexadecimal digits. */ + private final InputStream source; + + /** Creates a new input stream which will decode the given source. */ + FromHex(final InputStream source) { + this.source = source; + } + + /** Returns the next decoded byte. */ + @Override public int read() throws IOException { + final int hi = source.read(); if (hi < 0) return -1; + final int lo = source.read(); if (lo < 0) throw new EOFException(); + return (HexFormat.fromHexDigit(hi) << 4) | HexFormat.fromHexDigit(lo); + } + + /** Skips over and discards <var>n</var> bytes of data. */ + @Override public long skip(long n) throws IOException { + if ((n & 0xC000000000000000L) == 0) n <<= 1; + n = source.skip(n); + if ((n & 1) != 0 && source.read() >= 0) n++; + return n >> 1; + } + + /** Returns an estimate of the number of bytes that can be read. */ + @Override public int available() throws IOException { + return source.available() >> 1; + } + + /** Tests if this input stream supports the mark and reset methods. */ + @Override public boolean markSupported() { + return source.markSupported(); + } + + /** Marks the current position in this input stream. */ + @Override public void mark(int n) { + if ((n & 0xC0000000) == 0) n <<= 1; + else n = Integer.MAX_VALUE; + source.mark(n); + } + + /** Repositions this stream to the position of the mark. */ + @Override public void reset() throws IOException { + source.reset(); + } + + /** Closes this input stream. */ + @Override public void close() throws IOException { + source.close(); + } + } +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java index 36418a9..f6a558a 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java @@ -70,7 +70,7 @@ public final class Column { String propertyName; /** - * Type of values as one of the constant enumerated in {@link Types} class. + * Type of values as one of the constants enumerated in {@link Types} class. * * @see Reflection#DATA_TYPE */ @@ -124,7 +124,7 @@ public final class Column { * This method does not change cursor position. * * @param analyzer the analyzer which is creating this column. - * @param metadata the + * @param metadata the result of {@code DatabaseMetaData.getColumns(…)}. * @throws SQLException if an error occurred while fetching metadata. * * @see DatabaseMetaData#getColumns(String, String, String, String) diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java index cf81859..8bd9f8b 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java @@ -501,17 +501,35 @@ public class Database<G> extends Syntax { case Types.TIMESTAMP: return ValueGetter.AsInstant.INSTANCE; case Types.TIME_WITH_TIMEZONE: return ValueGetter.AsOffsetTime.INSTANCE; case Types.TIMESTAMP_WITH_TIMEZONE: return ValueGetter.AsOffsetDateTime.INSTANCE; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: return ValueGetter.AsBytes.INSTANCE; + case Types.BLOB: return ValueGetter.AsBytes.INSTANCE; case Types.ARRAY: // TODO case Types.OTHER: case Types.JAVA_OBJECT: return ValueGetter.AsObject.INSTANCE; - default: return null; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: { + final BinaryEncoding encoding = getBinaryEncoding(columnDefinition); + switch (encoding) { + case RAW: return ValueGetter.AsBytes.INSTANCE; + case HEXADECIMAL: return ValueGetter.AsBytes.HEXADECIMAL; + default: throw new AssertionError(encoding); + } + } + default: return null; } } /** + * Returns an identifier of the way binary data are encoded by the JDBC driver. + * + * @param columnDefinition information about the column to extract binary values from. + * @return how the binary data are returned by the JDBC driver. + */ + protected BinaryEncoding getBinaryEncoding(final Column columnDefinition) { + return BinaryEncoding.RAW; + } + + /** * Returns a function for getting values from a geometry column. * This is a helper method for {@link #getMapping(Column)} implementations. * @@ -521,12 +539,8 @@ public class Database<G> extends Syntax { protected final ValueGetter<?> forGeometry(final Column columnDefinition) { final GeometryType type = columnDefinition.getGeometryType(); final Class<? extends G> geometryClass = geomLibrary.getGeometryClass(type).asSubclass(geomLibrary.rootClass); - /* - * TODO: verify if the condition below works. We should have `hexadecimal = true` on PostGIS. - */ - final boolean hexadecimal = (columnDefinition.type != Types.BLOB); - return new EWKBReader<>(geomLibrary, geometryClass, columnDefinition.getGeometryCRS(), hexadecimal); - // TODO: need to invoke EWKBReader.setSridResolver(statements(…)) somewhere. + return new GeometryGetter<>(geomLibrary, geometryClass, columnDefinition.getGeometryCRS(), getBinaryEncoding(columnDefinition)); + // TODO: need to invoke GeometryGetter.setSridResolver(statements(…)) somewhere. } /** 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/GeometryGetter.java similarity index 67% rename from storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java rename to storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryGetter.java index 2590b8f..1151690 100644 --- 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/GeometryGetter.java @@ -23,8 +23,6 @@ import java.sql.ResultSet; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.internal.feature.GeometryWrapper; import org.apache.sis.internal.feature.Geometries; -import org.apache.sis.storage.DataStoreContentException; -import org.apache.sis.util.resources.Errors; /** @@ -50,11 +48,11 @@ import org.apache.sis.util.resources.Errors; * @param <G> the type of geometry objects created by the factory. * @param <V> the type of geometry objects returned by this getter. * - * @version 1.1 + * @version 1.2 * @since 1.1 * @module */ -final class EWKBReader<G, V extends G> extends ValueGetter<V> { +final class GeometryGetter<G, V extends G> extends ValueGetter<V> { /** * The factory to use for creating geometries from WKB definitions. */ @@ -63,7 +61,7 @@ final class EWKBReader<G, V extends G> extends ValueGetter<V> { /** * The mapper to use for resolving a Spatial Reference Identifier (SRID) integer * as Coordinate Reference System (CRS) object. - * This is {@code null} is there is no mapping to apply. + * This is {@code null} if there is no mapping to apply. */ private InfoStatements fromSridToCRS; @@ -74,9 +72,9 @@ final class EWKBReader<G, V extends G> extends ValueGetter<V> { private final CoordinateReferenceSystem defaultCRS; /** - * Whether binary data are encoded in an hexadecimal string. + * The way binary data are encoded in the geometry column. */ - private final boolean hexadecimal; + private final BinaryEncoding encoding; /** * Creates a new reader. The same instance can be reused for parsing an arbitrary @@ -85,16 +83,16 @@ final class EWKBReader<G, V extends G> extends ValueGetter<V> { * @param geometryFactory the factory to use for creating geometries from WKB definitions. * @param geometryClass the type of geometry to be returned by this {@code ValueGetter}. * @param defaultCRS the CRS to use if none can be mapped from the SRID, or {@code null} if none. - * @param hexadecimal whether binary data are encoded in an hexadecimal string. + * @param encoding the way binary data are encoded in the geometry column. * @return a WKB reader resolving SRID with the specified mapper and default CRS. */ - EWKBReader(final Geometries<G> geometryFactory, final Class<V> geometryClass, - final CoordinateReferenceSystem defaultCRS, final boolean hexadecimal) + GeometryGetter(final Geometries<G> geometryFactory, final Class<V> geometryClass, + final CoordinateReferenceSystem defaultCRS, final BinaryEncoding encoding) { super(geometryClass); this.geometryFactory = geometryFactory; this.defaultCRS = defaultCRS; - this.hexadecimal = hexadecimal; + this.encoding = encoding; } /** @@ -131,59 +129,13 @@ final class EWKBReader<G, V extends G> extends ValueGetter<V> { */ @Override public V getValue(final ResultSet source, final int columnIndex) throws Exception { - final GeometryWrapper<G> geom; - if (hexadecimal) { - final String wkb = source.getString(columnIndex); - if (wkb == null) return null; - geom = readHexa(wkb); - } else { - final byte[] wkb = source.getBytes(columnIndex); - if (wkb == null) return null; - geom = read(wkb); - } + final byte[] wkb = encoding.getBytes(source, columnIndex); + if (wkb == null) return null; + final GeometryWrapper<G> geom = read(wkb); return valueType.cast(geom.implementation()); } /** - * Parses a WKB encoded as hexadecimal numbers in a character string. - * Each byte uses 2 characters. No separator is allowed between bytes. - * - * @param wkb the hexadecimal values to decode. Should neither be null nor empty. - * @return geometry parsed from the given hexadecimal text. Never null, never empty. - * @throws Exception if the WKB can not be parsed. The exception type depends on the geometry implementation. - */ - final GeometryWrapper<G> readHexa(final String wkb) throws Exception { - final int length = wkb.length(); - if ((length & 1) != 0) { - throw new DataStoreContentException(Errors.format(Errors.Keys.OddArrayLength_1, "wkb")); - } - final byte[] data = new byte[length >>> 1]; - for (int i=0; i<length;) { - data[i >>> 1] = (byte) ((digit(wkb.charAt(i++)) << 4) | digit(wkb.charAt(i++))); - } - return read(data); - } - - /** - * Returns the numerical value of the given hexadecimal digit. - * The hexadecimal digit can be the decimal digits 0 to 9, or the letters A to F ignoring case. - * - * <div class="note"><b>Implementation note:</b> - * we do not use {@link Character#digit(char, int)} because that method handled a large - * range of Unicode characters, which is a wider scope than what is intended here.</div> - * - * @param c the hexadecimal digit. - * @return numerical value of given digit. - * @throws DataStoreContentException if the given character is not a hexadecimal digit. - */ - private static int digit(final char c) throws DataStoreContentException { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - ('A' - 10); - if (c >= 'a' && c <= 'f') return c - ('a' - 10); - throw new DataStoreContentException(Errors.format(Errors.Keys.CanNotParse_1, String.valueOf(c))); - } - - /** * Parses a WKB stored in the given byte array. * * @param wkb the array containing the WKB to decode. Should neither be null nor empty. diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java index 3b62c34..2ec5cf5 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java @@ -375,7 +375,6 @@ public class InfoStatements implements Localized, AutoCloseable { * Finished to parse entries from the "SPATIAL_REF_SYS" table. * Reports warning if any, then return the non-null CRS. */ - wktFromSrid.clearParameters(); if (crs == null) { if (authorityError != null) { throw authorityError; diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java index ed7c967..4920b95 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java @@ -46,7 +46,7 @@ import org.apache.sis.util.ArgumentChecks; * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * @since 1.1 * @module */ @@ -129,13 +129,20 @@ public abstract class ValueGetter<T> { * This getter delegates to {@link ResultSet#getBytes(int)} and returns that value with no change. */ static final class AsBytes extends ValueGetter<byte[]> { - /** The unique instance of this accessor. */ - public static final AsBytes INSTANCE = new AsBytes(); - private AsBytes() {super(byte[].class);} + /** The encoding of bytes returned by JDBC driver. */ + private final BinaryEncoding encoding; + + /** The instance of this accessor for array of bytes without encoding. */ + public static final AsBytes INSTANCE = new AsBytes(BinaryEncoding.RAW); + public static final AsBytes HEXADECIMAL = new AsBytes(BinaryEncoding.HEXADECIMAL); + private AsBytes(final BinaryEncoding encoding) { + super(byte[].class); + this.encoding = encoding; + } /** Fetches the value from the specified column in the given result set. */ @Override public byte[] getValue(ResultSet source, int columnIndex) throws SQLException { - return source.getBytes(columnIndex); + return encoding.getBytes(source, columnIndex); } } diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java index 507f681..cb36120 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java @@ -17,6 +17,7 @@ package org.apache.sis.internal.sql.postgis; import java.util.Set; +import java.sql.Types; import java.sql.Connection; import java.sql.Statement; import java.sql.ResultSet; @@ -25,6 +26,7 @@ import java.sql.SQLException; import javax.sql.DataSource; import java.util.logging.Level; import org.apache.sis.internal.feature.Geometries; +import org.apache.sis.internal.sql.feature.BinaryEncoding; import org.apache.sis.internal.sql.feature.InfoStatements; import org.apache.sis.internal.sql.feature.Column; import org.apache.sis.internal.sql.feature.Database; @@ -42,7 +44,7 @@ import org.apache.sis.util.Version; * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * @since 1.1 * @module */ @@ -118,6 +120,19 @@ public final class Postgres<G> extends Database<G> { } /** + * Returns an identifier of the way binary data are encoded by the JDBC driver. + * Data stored as PostgreSQL {@code BYTEA} type are encoded in hexadecimal. + */ + @Override + protected BinaryEncoding getBinaryEncoding(final Column columnDefinition) { + if (columnDefinition.type == Types.BLOB) { + return super.getBinaryEncoding(columnDefinition); + } else { + return BinaryEncoding.HEXADECIMAL; + } + } + + /** * Prepares a cache of statements about spatial information using the given connection. * Statements will be created only when first needed. * diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/EWKBTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/EWKBTest.java deleted file mode 100644 index b39bee2..0000000 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/EWKBTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.internal.sql.feature; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import org.opengis.referencing.crs.GeographicCRS; -import org.apache.sis.internal.feature.Geometries; -import org.apache.sis.internal.feature.GeometryWrapper; -import org.apache.sis.referencing.CommonCRS; -import org.apache.sis.setup.GeometryLibrary; -import org.apache.sis.test.TestCase; -import org.junit.Test; - -import static org.junit.Assert.*; - - -/** - * Tests the parsing of geometries encoded in Extended Well Known Binary (EWKB) format. - * - * @author Alexis Manin (Geomatys) - * @version 1.1 - * @since 1.1 - * @module - * - * @todo Run the same test for all supported geometry implementations (ESRI and JTS). - */ -public strictfp class EWKBTest extends TestCase { - /** - * The factory to use for creating geometric objects. - * It requires a geometry implementation to be available on the classpath. - */ - private final Geometries<?> GF; - - /** - * Creates a new test using JTS geometry implementation. - */ - public EWKBTest() { - GF = Geometries.implementation(GeometryLibrary.JTS); - } - - /** - * Creates a reader to use for testing. - */ - private static <G> EWKBReader<G, ? extends G> createReader(final Geometries<G> GF, final GeographicCRS crs) { - return new EWKBReader<>(GF, GF.rootClass, crs, false); - } - - /** - * Decodes a geometry encoded in EWKB format and compares with the geometry specified in WKT format. - * - * @param wkt WKT representation of the geometry. This is used as the reference value. - * @param wkb WKB representation of the same geometry. This is the value to test. - * @throws Exception if an error occurred while decoding one of the given strings. - */ - public void decodeHexadecimal(final String wkt, final String wkb) throws Exception { - final GeographicCRS expectedCRS = CommonCRS.defaultGeographic(); - final EWKBReader<?,?> reader = createReader(GF, expectedCRS); - assertEquals("WKT and hexadecimal EWKB representation don't match", - GF.parseWKT(wkt).implementation(), - reader.readHexa(wkb).implementation()); - } - - /** - * Tests the decoding of a geometry from a byte array. The purpose of this test is not to check complex geometries, - * which are validated by {@link #decodeHexadecimal(String, String)}. This test only ensures that decoding directly - * a byte array behaves in the same way than decoding a string of hexadecimal digits. - * - * @throws Exception if an error occurred while decoding the WKB. - */ - @Test - public void testBinary() throws Exception { - final ByteBuffer point = ByteBuffer.allocate(21); - point.put((byte) 0); // XDR mode. - - // Create a 2D point. - point.putInt(1); - point.putDouble(42.2); - point.putDouble(43.3); - - // Read the point. - point.rewind(); - final GeometryWrapper<?> read = createReader(GF, null).read(point.array()); - assertEquals(GF.createPoint(42.2, 43.3), read.implementation()); - } - - /** - * Temporary test for simulating JUnit 5 execution of {@link #decodeHexadecimal(String, String)} - * as a parameterized test. To be removed after migration to JUnit 5. - * - * @throws Exception if test file can not be decoded. - */ - @Test - public void testDecodeHexadecimal() throws Exception { - try (final BufferedReader in = new BufferedReader(new InputStreamReader( - EWKBTest.class.getResourceAsStream("hexa_ewkb_4326.csv"), StandardCharsets.UTF_8))) - { - String line; - int numLinesToSkip = 1; - while ((line = in.readLine()) != null) { - if (!(line = line.trim()).isEmpty() && line.charAt(0) != '#' && --numLinesToSkip < 0) { - final String[] columns = line.split("\t"); - assertEquals(2, columns.length); - decodeHexadecimal(columns[0], columns[1]); - } - } - } - } -} diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/GeometryGetterTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/GeometryGetterTest.java new file mode 100644 index 0000000..e57e0c5 --- /dev/null +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/GeometryGetterTest.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.feature; + +import java.nio.ByteBuffer; +import java.sql.Connection; +import java.sql.Statement; +import java.sql.ResultSet; +import org.apache.sis.internal.feature.Geometries; +import org.apache.sis.internal.feature.GeometryWrapper; +import org.apache.sis.referencing.crs.HardCodedCRS; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.setup.GeometryLibrary; +import org.apache.sis.test.TestCase; +import org.junit.Test; + +// Optional dependencies +import org.locationtech.jts.geom.Geometry; + +import static org.junit.Assert.*; + + +/** + * Tests the parsing of geometries encoded in Well-Known Binary (WKB) format. + * + * @author Alexis Manin (Geomatys) + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.1 + * @module + */ +public strictfp final class GeometryGetterTest extends TestCase { + /** + * The factory to use for creating geometric objects. + */ + private Geometries<?> GF; + + /** + * Creates a new test case. + */ + public GeometryGetterTest() { + } + + /** + * Creates a reader to use for testing. + * + * @param library the geometry implementation to use (JTS or ESRI). + * @param encoding the way binary data are encoded (raw or hexadecimal). + */ + @SuppressWarnings("unchecked") + private GeometryGetter<?,?> createReader(final GeometryLibrary library, final BinaryEncoding encoding) { + GF = Geometries.implementation(library); + return new GeometryGetter<>(GF, (Class) GF.rootClass, HardCodedCRS.WGS84, encoding); + } + + /** + * Tests the decoding of a geometry from a byte array using JTS library. + * The array does not use any encoding. + * + * @throws Exception if an error occurred while decoding the WKB. + */ + @Test + public void testBinaryWithJTS() throws Exception { + testBinary(GeometryLibrary.JTS); + } + + /** + * Tests the decoding of a geometry from a byte array using ESRI library. + * The array does not use any encoding. + * + * @throws Exception if an error occurred while decoding the WKB. + */ + @Test + public void testBinaryWithESRI() throws Exception { + testBinary(GeometryLibrary.ESRI); + } + + /** + * Implementation of {@link #testBinaryWithJTS()} and {@link #testBinaryWithESRI()} methods. + */ + private void testBinary(final GeometryLibrary library) throws Exception { + final ByteBuffer point = ByteBuffer.allocate(21); + point.put((byte) 0); // XDR mode. + + // Create a 2D point. + point.putInt(1); + point.putDouble(42.2); + point.putDouble(43.3); + + // Read the point. + point.rewind(); + final GeometryWrapper<?> read = createReader(library, BinaryEncoding.RAW).read(point.array()); + assertSame(HardCodedCRS.WGS84, read.getCoordinateReferenceSystem()); + assertEquals(GF.createPoint(42.2, 43.3), read.implementation()); + } + + /** + * Compares WKB with WKT parsing using the {@code features."Geometries"} view of test database. + * This test is <em>not</em> executed by this {@code GeometryGetterTest} class. This is a method + * to be invoked from {@linkplain org.apache.sis.internal.sql.postgis.PostgresTest#testGeometryGetter + * another test class} having a connection to a database. + * + * @param connection connection to the database. + * @param spatialRefSys helper method for fetching CRS from SRID codes. + * @param encoding the way binary data are encoded (raw or hexadecimal). + * @throws Exception if an error occurred while querying the database or parsing the WKT or WKB. + */ + public void testFromDatabase(final Connection connection, final InfoStatements spatialRefSys, + final BinaryEncoding encoding) throws Exception + { + final GeometryGetter<?,?> reader = createReader(GeometryLibrary.JTS, encoding); + reader.setSridResolver(spatialRefSys); + try (Statement stmt = connection.createStatement(); + ResultSet results = stmt.executeQuery("SELECT \"WKT\",\"WKB\" FROM features.\"Geometries\"")) + { + while (results.next()) { + final String wkt = results.getString(1); + final Geometry geometry = (Geometry) reader.getValue(results, 2); + final GeometryWrapper<?> expected = GF.parseWKT(wkt); + assertEquals("WKT and WKB parsings gave different results.", expected.implementation(), geometry); + assertSame("SRID", CommonCRS.WGS84.normalizedGeographic(), GF.castOrWrap(geometry).getCoordinateReferenceSystem()); + } + } + } +} diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java index a473b64..8eed2b7 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java @@ -21,6 +21,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.nio.ByteBuffer; import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; import java.lang.reflect.Method; import org.opengis.referencing.crs.ProjectedCRS; import org.apache.sis.setup.OptionKey; @@ -31,6 +32,8 @@ import org.apache.sis.storage.sql.ResourceDefinition; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.sql.SQLStoreTest; import org.apache.sis.internal.storage.io.ChannelDataInput; +import org.apache.sis.internal.sql.feature.BinaryEncoding; +import org.apache.sis.internal.sql.feature.GeometryGetterTest; import org.apache.sis.referencing.crs.HardCodedCRS; import org.apache.sis.test.sql.TestDatabase; import org.apache.sis.test.DependsOn; @@ -87,7 +90,8 @@ public final strictfp class PostgresTest extends TestCase { ExtendedInfo info = new ExtendedInfo(pg, connection)) { testInfoStatements(info); -// testRasterReader(TestRaster.USHORT, info, connection); + testGeometryGetter(info, connection); + testRasterReader(TestRaster.USHORT, info, connection); } } } @@ -104,16 +108,28 @@ public final strictfp class PostgresTest extends TestCase { } /** + * Tests {@link org.apache.sis.internal.sql.feature.GeometryGetter} + * in the context of querying a database. + * + * @throws Exception if an error occurred while testing the database. + */ + private void testGeometryGetter(final ExtendedInfo info, final Connection connection) throws Exception { + final GeometryGetterTest test = new GeometryGetterTest(); + test.testFromDatabase(connection, info, BinaryEncoding.HEXADECIMAL); + } + + /** * Tests {@link RasterReader}. */ private void testRasterReader(final TestRaster test, final ExtendedInfo info, final Connection connection) throws Exception { + final BinaryEncoding encoding = BinaryEncoding.HEXADECIMAL; final RasterReader reader = new RasterReader(info); try (PreparedStatement stmt = connection.prepareStatement("SELECT image FROM features.\"SpatialData\" WHERE filename=?")) { stmt.setString(1, test.filename); final ResultSet r = stmt.executeQuery(); assertTrue(r.next()); - final ChannelDataInput input = new ChannelDataInput(test.filename, - Channels.newChannel(r.getBinaryStream(1)), ByteBuffer.allocate(50), false); + final ReadableByteChannel channel = Channels.newChannel(encoding.decode(r.getBinaryStream(1))); + final ChannelDataInput input = new ChannelDataInput(test.filename, channel, ByteBuffer.allocate(50), false); RasterReaderTest.compareReadResult(test, reader, input); assertFalse(r.next()); } diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java index 47f1db3..566261d 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java @@ -25,7 +25,7 @@ import org.junit.BeforeClass; * All tests from the {@code sis-sqlstore} module, in rough dependency order. */ @Suite.SuiteClasses({ - org.apache.sis.internal.sql.feature.EWKBTest.class, + org.apache.sis.internal.sql.feature.GeometryGetterTest.class, org.apache.sis.internal.sql.feature.SelectionClauseWriterTest.class, org.apache.sis.internal.sql.postgis.BandTest.class, org.apache.sis.internal.sql.postgis.RasterReaderTest.class, diff --git a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.csv b/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.csv deleted file mode 100644 index f8fc6a5..0000000 --- a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.csv +++ /dev/null @@ -1,21 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. - -# -# This dataset has been generated using SQL script given in 'hexa_ewkb_4326.sql' -# in this folder, against a PostGIS 2.5.2. It contains two columns separated by -# a tabulation: -# -# - The geometry in WKT format. -# - The same geometry in EWKB format, with bytes encoded as hexadecimal numbers. -# - -WKT hexadecimal EWKB -POINT(0 0) 0101000020E610000000000000000000000000000000000000 -LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932) 0102000020E610000003000000E44A3D0B42CA51C06EC328081E21454027BF45274BCA51C0F67B629D2A214540957CEC2E50CA51C07099D36531214540 -POLYGON((0 0,0 1,1 1,1 0,0 0)) 0103000020E61000000100000005000000000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000 -POLYGON((-71.1776585052917 42.3902909739571,-71.1776820268866 42.3903701743239,-71.1776063012595 42.3903825660754,-71.1775826583081 42.3903033653531,-71.1776585052917 42.3902909739571)) 0103000020E610000001000000050000006285C7C15ECB51C0ED88FC0DF531454028A46F245FCB51C009075EA6F731454047DED1E65DCB51C0781C510EF83145404871A7835DCB51C0EBDAEE75F53145406285C7C15ECB51C0ED88FC0DF5314540 -MULTILINESTRING((-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932),(-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307)) 0105000020E610000002000000010200000003000000E44A3D0B42CA51C06EC328081E21454027BF45274BCA51C0F67B629D2A214540957CEC2E50CA51C07099D36531214540010200000002000000FEFCFB379AC651C0C0503E9F5B284540FFDDDD4D96C651C033AC3B284F284540 -MULTIPOLYGON(((-71.1031880899493 42.3152774590236,-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307,-71.1023097974109 42.3151969047397,-71.1019285062273 42.3147384934248,-71.102505233663 42.3144722937587,-71.10277487471 42.3141658254797,-71.103113945163 42.3142739188902,-71.10324876416 42.31402489987,-71.1033002961013 42.3140393340215,-71.1033488797549 42.3139495090772,-71.103396240451 42.3138632439557,-71.1041521907712 42.3141153348029,-71.1041411411543 42.31415450145 [...] diff --git a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.sql b/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.sql deleted file mode 100644 index ce508c7..0000000 --- a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/feature/hexa_ewkb_4326.sql +++ /dev/null @@ -1,43 +0,0 @@ - --- Licensed to the Apache Software Foundation (ASF) under one or more --- contributor license agreements. See the NOTICE file distributed with --- this work for additional information regarding copyright ownership. - - --- This SQL script generates the content of `hexa_ewkb_4326.csv` file. --- This file is not used directly by the tests. - -SELECT wkt, ST_GeomFromText(wkt, 4326) AS hexadecimal_ewkb -FROM ( - VALUES ('POINT(0 0)'), - ('LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)'), - ('POLYGON((0 0,0 1,1 1,1 0,0 0))'), - ('POLYGON((-71.1776585052917 42.3902909739571,-71.1776820268866 42.3903701743239,' || - '-71.1776063012595 42.3903825660754,-71.1775826583081 42.3903033653531,' || - '-71.1776585052917 42.3902909739571))'), - ('MULTILINESTRING((-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932),' || - '(-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307))'), - ('MULTIPOLYGON(((-71.1031880899493 42.3152774590236,' || - '-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307,' || - '-71.1023097974109 42.3151969047397,-71.1019285062273 42.3147384934248,' || - '-71.102505233663 42.3144722937587,-71.10277487471 42.3141658254797,' || - '-71.103113945163 42.3142739188902,-71.10324876416 42.31402489987,' || - '-71.1033002961013 42.3140393340215,-71.1033488797549 42.3139495090772,' || - '-71.103396240451 42.3138632439557,-71.1041521907712 42.3141153348029,' || - '-71.1041411411543 42.3141545014533,-71.1041287795912 42.3142114839058,' || - '-71.1041188134329 42.3142693656241,-71.1041112482575 42.3143272556118,' || - '-71.1041072845732 42.3143851580048,-71.1041057218871 42.3144430686681,' || - '-71.1041065602059 42.3145009876017,-71.1041097995362 42.3145589148055,' || - '-71.1041166403905 42.3146168544148,-71.1041258822717 42.3146748022936,' || - '-71.1041375307579 42.3147318674446,-71.1041492906949 42.3147711126569,' || - '-71.1041598612795 42.314808571739,-71.1042515013869 42.3151287620809,' || - '-71.1041173835118 42.3150739481917,-71.1040809891419 42.3151344119048,' || - '-71.1040438678912 42.3151191367447,-71.1040194562988 42.3151832057859,' || - '-71.1038734225584 42.3151140942995,-71.1038446938243 42.3151006300338,' || - '-71.1038315271889 42.315094347535,-71.1037393329282 42.315054824985,' || - '-71.1035447555574 42.3152608696313,-71.1033436658644 42.3151648370544,' || - '-71.1032580383161 42.3152269126061,-71.103223066939 42.3152517403219,' || - '-71.1031880899493 42.3152774590236)),' || - '((-71.1043632495873 42.315113108546,-71.1043583974082 42.3151211109857,' || - '-71.1043443253471 42.3150676015829,-71.1043850704575 42.3150793250568,-71.1043632495873 42.315113108546)))') - ) AS WKTS (wkt) \ No newline at end of file diff --git a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/postgis/SpatialFeatures.sql b/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/postgis/SpatialFeatures.sql index 20a1bda..7302488 100644 --- a/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/postgis/SpatialFeatures.sql +++ b/storage/sis-sqlstore/src/test/resources/org/apache/sis/internal/sql/postgis/SpatialFeatures.sql @@ -10,12 +10,61 @@ SET search_path TO public; CREATE TABLE features."SpatialData" ( "filename" VARCHAR(20) NOT NULL, - "image" RASTER NOT NULL, + "geometry" GEOMETRY, + "image" RASTER, CONSTRAINT "PK_SpatialData" PRIMARY KEY ("filename") ); -INSERT INTO features."SpatialData" ("filename", "image") - VALUES('raster-ushort.wkb', ('0100000200000000000000F43F000000000000044000000000000054C00000000000004EC0000000' - || '00000000000000000000000000E6100000030004000600006F0079008300D300DD00E700370141014B019B01A501AF01060000' - || '70007A008400D400DE00E800380142014C019C01A601B001')::raster); + +-- +-- Rasters with dummy sample values. Those data are duplicated in two ways: +-- The same rasters are created in Java code by the `TestRaster` class, and +-- the same bytes are stored in test files with ".wkb" extension. +-- +INSERT INTO features."SpatialData" ("filename", "image") VALUES + ('raster-ushort.wkb', ('0100000200000000000000F43F000000000000044000000000000054C00000000000004EC0000000' + || '00000000000000000000000000E6100000030004000600006F0079008300D300DD00E700370141014B019B01A501AF01060' + || '00070007A008400D400DE00E800380142014C019C01A601B001')::raster); + + +-- +-- Geometries with arbitrary coordinate values. +-- +INSERT INTO features."SpatialData" ("filename", "geometry") VALUES + ('hexa-wkb.csv:1', ST_GeomFromText('POINT(0 0)', 4326)), + ('hexa-wkb.csv:2', ST_GeomFromText('LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)', 4326)), + ('hexa-wkb.csv:3', ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))', 4326)), + ('hexa-wkb.csv:4', ST_GeomFromText('POLYGON' + || '((-71.1776585052917 42.3902909739571,-71.1776820268866 42.3903701743239,-71.1776063012595 42.3903825660754,' + || '-71.1775826583081 42.3903033653531,-71.1776585052917 42.3902909739571))', 4326)), + ('hexa-wkb.csv:5', ST_GeomFromText('MULTILINESTRING' + || '((-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932),' + || '(-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307))', 4326)), + ('hexa-wkb.csv:6', ST_GeomFromText('MULTIPOLYGON(' + || '((-71.1031880899493 42.3152774590236,-71.1031627617667 42.3152960829043,-71.102923838298 42.3149156848307,' + || '-71.1023097974109 42.3151969047397,-71.1019285062273 42.3147384934248,-71.102505233663 42.3144722937587,' + || '-71.1027748747100 42.3141658254797,-71.1031139451630 42.3142739188902,-71.103248764160 42.3140248998700,' + || '-71.1033002961013 42.3140393340215,-71.1033488797549 42.3139495090772,-71.103396240451 42.3138632439557,' + || '-71.1041521907712 42.3141153348029,-71.1041411411543 42.3141545014533,-71.104128779591 42.3142114839058,' + || '-71.1041188134329 42.3142693656241,-71.1041112482575 42.3143272556118,-71.104107284573 42.3143851580048,' + || '-71.1041057218871 42.3144430686681,-71.1041065602059 42.3145009876017,-71.104109799536 42.3145589148055,' + || '-71.1041166403905 42.3146168544148,-71.1041258822717 42.3146748022936,-71.104137530758 42.3147318674446,' + || '-71.1041492906949 42.3147711126569,-71.1041598612795 42.3148085717390,-71.104251501387 42.3151287620809,' + || '-71.1041173835118 42.3150739481917,-71.1040809891419 42.3151344119048,-71.104043867891 42.3151191367447,' + || '-71.1040194562988 42.3151832057859,-71.1038734225584 42.3151140942995,-71.103844693824 42.3151006300338,' + || '-71.1038315271889 42.3150943475350,-71.1037393329282 42.3150548249850,-71.103544755557 42.3152608696313,' + || '-71.1033436658644 42.3151648370544,-71.1032580383161 42.3152269126061,-71.103223066939 42.3152517403219,' + || '-71.1031880899493 42.3152774590236)),' + || '((-71.1043632495873 42.3151131085460,-71.1043583974082 42.3151211109857,-71.1043443253471 42.315067601583,' + || '-71.1043850704575 42.3150793250568,-71.1043632495873 42.315113108546)))', 4326)); + + +-- +-- Geometries with WKT representation in one column and WKB in another column. +-- Used for parsing the same geometry in two ways and comparing the results. +-- +CREATE VIEW features."Geometries" AS + SELECT ST_AsText(geometry) AS "WKT", geometry AS "WKB" + FROM features."SpatialData" + WHERE geometry IS NOT NULL;