This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sis.git
commit e621d10a14072fc1f41aedab2c4a2cfab50ca5ec Merge: da97f66 a63fa19 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Dec 27 21:15:21 2021 +0100 Merge branch 'geoapi-3.1' .../main/java/org/apache/sis/console/Command.java | 4 +- .../main/java/org/apache/sis/console/Option.java | 4 +- .../apache/sis/gui/coverage/CoverageCanvas.java | 4 +- .../org/apache/sis/gui/coverage/GridViewSkin.java | 2 +- .../org/apache/sis/coverage/grid/GridGeometry.java | 5 +- .../java/org/apache/sis/filter/PropertyValue.java | 65 ++- .../apache/sis/image/BandedSampleConverter.java | 20 +- .../java/org/apache/sis/image/ComputedImage.java | 22 + .../java/org/apache/sis/image/ImageAdapter.java | 21 +- .../java/org/apache/sis/image/Interpolation.java | 50 ++- .../org/apache/sis/image/LanczosInterpolation.java | 4 +- .../java/org/apache/sis/image/PlanarImage.java | 13 + .../java/org/apache/sis/image/PrefetchedImage.java | 22 +- .../java/org/apache/sis/image/ResampledImage.java | 40 +- .../org/apache/sis/image/SourceAlignedImage.java | 21 + .../java/org/apache/sis/image/Visualization.java | 22 +- .../sis/internal/coverage/SampleDimensions.java | 41 ++ .../internal/coverage/j2d/BatchComputedImage.java | 218 +++++++++ .../sis/internal/coverage/j2d/ImageUtilities.java | 73 +++- .../apache/sis/internal/feature/Geometries.java | 5 +- .../feature/j2d/DecimatedPathIterator.java | 166 +++++++ .../sis/internal/feature/j2d/DecimatedShape.java | 85 ++++ .../sis/internal/feature/j2d/EmptyShape.java | 25 +- .../apache/sis/internal/feature/j2d/Factory.java | 5 +- .../sis/internal/feature/j2d/ShapeProperties.java | 4 +- .../sis/internal/feature/j2d/ShapeWrapper.java | 189 ++++++++ .../apache/sis/internal/feature/j2d/Wrapper.java | 5 +- .../sis/internal/feature/j2d/package-info.java | 2 +- .../apache/sis/internal/feature/jts/Factory.java | 221 ++++++++-- .../feature/jts/GeometryCoordinateTransform.java | 4 +- .../org/apache/sis/internal/feature/jts/JTS.java | 40 +- .../feature/jts/PackedCoordinateSequence.java | 485 +++++++++++++++++++++ .../jts/PackedCoordinateSequenceFactory.java | 146 +++++++ .../internal/feature/jts/PathIteratorAdapter.java | 269 ++++++++++++ .../sis/internal/feature/jts/ShapeAdapter.java | 209 +++++++++ .../sis/internal/feature/jts/ShapeConverter.java | 327 ++++++++++++++ .../apache/sis/internal/feature/jts/Wrapper.java | 61 ++- .../sis/internal/feature/jts/package-info.java | 2 +- .../sis/internal/feature/jts/FactoryTest.java | 32 +- .../apache/sis/internal/feature/jts/JTSTest.java | 129 +++++- .../sis/internal/feature/jts/ShapeAdapterTest.java | 221 ++++++++++ .../internal/feature/jts/ShapeConverterTest.java | 200 +++++++++ .../internal/filter/sqlmm/RegistryTestCase.java | 4 +- .../apache/sis/test/suite/FeatureTestSuite.java | 2 + core/sis-metadata/pom.xml | 5 + .../org/apache/sis/metadata/PropertyAccessor.java | 4 +- .../apache/sis/metadata/iso/DefaultMetadata.java | 2 +- .../metadata/iso/citation/DefaultTelephone.java | 2 +- .../main/java/org/apache/sis/xml/Transformer.java | 2 +- .../java/org/apache/sis/test/sql/TestDatabase.java | 38 +- .../java/org/apache/sis/test/sql/package-info.java | 2 +- .../apache/sis/test/xml/DocumentComparator.java | 4 +- .../test/java/org/apache/sis/xml/XLinkTest.java | 4 +- .../coverage/MultiResolutionCoverageLoader.java | 10 +- .../sis/internal/map/coverage/RenderingData.java | 6 +- .../MultiResolutionCoverageLoaderTest.java | 6 +- core/sis-referencing/pom.xml | 5 + .../org/apache/sis/geometry/GeneralEnvelope.java | 2 +- .../internal/referencing/j2d/AbstractShape.java | 82 ++++ .../internal/referencing/j2d/ShapeUtilities.java | 16 +- .../sis/internal/referencing/j2d/package-info.java | 2 +- .../referencing/provider/MillerCylindrical.java | 2 +- .../sis/parameter/DefaultParameterDescriptor.java | 2 +- .../sis/parameter/DefaultParameterValue.java | 4 +- .../org/apache/sis/parameter/TensorParameters.java | 2 +- .../operation/CoordinateOperationRegistry.java | 2 +- .../operation/DefaultConcatenatedOperation.java | 4 +- .../DefaultCoordinateOperationFactory.java | 2 +- .../sis/referencing/operation/matrix/Matrices.java | 4 +- .../referencing/operation/matrix/MatrixSIS.java | 2 +- .../operation/matrix/NonSquareMatrix.java | 2 +- .../transform/CoordinateSystemTransform.java | 4 +- .../operation/transform/PassThroughTransform.java | 4 +- .../referencing/j2d/AbstractShapeTest.java | 29 +- .../referencing/j2d/ShapeUtilitiesTest.java | 13 +- .../parameter/DefaultParameterValueGroupTest.java | 2 +- .../referencing/factory/sql/EPSGInstallerTest.java | 19 +- .../sis/test/suite/ReferencingTestSuite.java | 3 +- .../org/apache/sis/internal/jdk9/HexFormat.java | 76 ++++ .../org/apache/sis/internal/jdk9/package-info.java | 2 +- .../apache/sis/internal/system/DataDirectory.java | 2 +- .../sis/internal/system/DefaultFactories.java | 2 +- .../java/org/apache/sis/math/DecimalFunctions.java | 2 +- .../src/main/java/org/apache/sis/util/Classes.java | 2 +- .../org/apache/sis/util/logging/DualLogger.java | 4 + .../apache/sis/util/logging/DualLoggerFactory.java | 4 + .../org/apache/sis/util/logging/LoggerAdapter.java | 4 + .../org/apache/sis/util/logging/LoggerFactory.java | 4 + .../java/org/apache/sis/util/logging/Logging.java | 4 + .../org/apache/sis/util/collection/CacheTest.java | 2 +- .../apache/sis/util/collection/RangeSetTest.java | 2 +- ide-project/NetBeans/build.xml | 1 + ide-project/NetBeans/nbproject/project.properties | 8 +- pom.xml | 12 +- .../sis/internal/geotiff/SchemaModifier.java | 20 +- .../org/apache/sis/storage/geotiff/DataCube.java | 5 +- .../org/apache/sis/storage/geotiff/DataSubset.java | 2 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 13 +- .../sis/storage/geotiff/GridGeometryBuilder.java | 42 +- .../sis/storage/geotiff/ImageFileDirectory.java | 173 ++++++-- .../sis/storage/geotiff/MultiResolutionImage.java | 189 ++++++++ .../org/apache/sis/storage/geotiff/Reader.java | 195 ++++++--- .../org/apache/sis/internal/netcdf/Convention.java | 2 +- .../org/apache/sis/internal/netcdf/Dimension.java | 3 +- .../apache/sis/internal/netcdf/NamedElement.java | 3 +- .../sis/internal/netcdf/ucar/DecoderWrapper.java | 4 +- .../sis/internal/netcdf/ucar/DimensionWrapper.java | 53 ++- .../sis/internal/netcdf/ucar/package-info.java | 2 +- storage/sis-sqlstore/pom.xml | 10 + .../apache/sis/internal/sql/feature/Analyzer.java | 8 +- .../sis/internal/sql/feature/BinaryEncoding.java | 138 ++++++ .../apache/sis/internal/sql/feature/Column.java | 75 +++- .../apache/sis/internal/sql/feature/Database.java | 187 ++++++-- .../sis/internal/sql/feature/EWKBReader.java | 207 --------- .../sis/internal/sql/feature/FeatureAdapter.java | 5 +- .../sis/internal/sql/feature/FeatureAnalyzer.java | 19 +- .../sis/internal/sql/feature/FeatureIterator.java | 26 +- .../sis/internal/sql/feature/FeatureStream.java | 21 +- .../sis/internal/sql/feature/GeometryGetter.java | 126 ++++++ .../sis/internal/sql/feature/InfoStatements.java | 266 ++++++++--- .../sis/internal/sql/feature/QueryAnalyzer.java | 5 +- .../apache/sis/internal/sql/feature/Resources.java | 5 + .../sis/internal/sql/feature/Resources.properties | 1 + .../internal/sql/feature/Resources_fr.properties | 1 + .../org/apache/sis/internal/sql/feature/Table.java | 51 +++ .../sis/internal/sql/feature/TableAnalyzer.java | 7 +- .../sis/internal/sql/feature/TableReference.java | 4 +- .../sis/internal/sql/feature/ValueGetter.java | 82 ++-- .../org/apache/sis/internal/sql/postgis/Band.java | 341 +++++++++++++++ .../sis/internal/sql/postgis/ExtendedInfo.java | 36 +- .../sis/internal/sql/postgis/ExtentEstimator.java | 154 +++++++ .../apache/sis/internal/sql/postgis/Postgres.java | 56 ++- .../sis/internal/sql/postgis/RasterFormat.java | 69 +++ .../sis/internal/sql/postgis/RasterGetter.java | 90 ++++ .../sis/internal/sql/postgis/RasterReader.java | 410 +++++++++++++++++ .../sis/internal/sql/postgis/RasterWriter.java | 313 +++++++++++++ .../sis/internal/sql/postgis/package-info.java | 6 +- .../java/org/apache/sis/storage/sql/SQLStore.java | 3 + .../apache/sis/internal/sql/feature/EWKBTest.java | 125 ------ .../internal/sql/feature/GeometryGetterTest.java | 170 ++++++++ .../sis/internal/sql/feature/ResultSetMock.java | 68 +++ .../apache/sis/internal/sql/postgis/BandTest.java | 65 +++ .../sis/internal/sql/postgis/PostgresTest.java | 160 ++++++- .../sis/internal/sql/postgis/RasterReaderTest.java | 80 ++++ .../sis/internal/sql/postgis/RasterWriterTest.java | 66 +++ .../sis/internal/sql/postgis/TestRaster.java | 151 +++++++ .../org/apache/sis/storage/sql/SQLStoreTest.java | 46 +- .../org/apache/sis/test/suite/SQLTestSuite.java | 5 +- .../sis/internal/sql/feature/hexa_ewkb_4326.csv | 21 - .../sis/internal/sql/feature/hexa_ewkb_4326.sql | 43 -- .../sis/internal/sql/postgis/SpatialFeatures.sql | 70 +++ .../sis/internal/sql/postgis/raster-ushort.wkb | Bin 0 -> 115 bytes .../sis/internal/storage/GridResourceWrapper.java | 15 +- .../sis/internal/storage/TiledDeferredImage.java | 110 +++++ .../sis/internal/storage/TiledGridCoverage.java | 66 ++- .../sis/internal/storage/TiledGridResource.java | 23 +- .../internal/storage/xml/GeographicEnvelope.java | 2 +- .../main/java/org/apache/sis/storage/DataSet.java | 7 +- .../apache/sis/storage/GridCoverageResource.java | 2 +- .../sis/internal/storage/MetadataBuilderTest.java | 2 +- 160 files changed, 7573 insertions(+), 1064 deletions(-) diff --cc core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java index be341ad,89714ff..057e829 --- a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java +++ b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java @@@ -140,7 -138,9 +142,9 @@@ abstract class PropertyValue<V> extend * An expression fetching property values as {@code Object}. * This expression does not need to apply any type conversion. */ - private static final class AsObject extends PropertyValue<Object> { + private static final class AsObject extends PropertyValue<Object> - implements Optimization.OnExpression<Feature,Object> ++ implements Optimization.OnExpression<AbstractFeature,Object> + { /** For cross-version compatibility. */ private static final long serialVersionUID = 2854731969723006038L; @@@ -165,6 -165,23 +169,23 @@@ } return null; } + + /** + * If the evaluated property is a link, replaces this expression by a more direct reference + * to the target property. This optimization is important for allowing {@code SQLStore} to + * put the column name in the SQL {@code WHERE} clause. It makes the difference between + * using or not the database index. + */ + @Override - public Expression<Feature,?> optimize(final Optimization optimization) { - final FeatureType type = optimization.getFeatureType(); ++ public Expression<AbstractFeature,?> optimize(final Optimization optimization) { ++ final DefaultFeatureType type = optimization.getFeatureType(); + if (type != null) try { + return Features.getLinkTarget(type.getProperty(name)).map(AsObject::new).orElse(this); - } catch (PropertyNotFoundException e) { ++ } catch (IllegalArgumentException e) { + warning(e, true); + } + return this; + } } @@@ -223,17 -248,29 +252,29 @@@ * then a specialized expression is returned. Otherwise this method returns {@code this}. */ @Override - public final Expression<Feature, ? extends V> optimize(final Optimization optimization) { - final FeatureType featureType = optimization.getFeatureType(); + public final Expression<AbstractFeature, ? extends V> optimize(final Optimization optimization) { + final DefaultFeatureType featureType = optimization.getFeatureType(); if (featureType != null) try { - final AbstractIdentifiedType property = featureType.getProperty(name); + String targetName = name; - PropertyType property = featureType.getProperty(targetName); ++ AbstractIdentifiedType property = featureType.getProperty(targetName); + Optional<String> target = Features.getLinkTarget(property); + if (target.isPresent()) try { + targetName = target.get(); + property = featureType.getProperty(targetName); - } catch (PropertyNotFoundException e) { ++ } catch (IllegalArgumentException e) { + targetName = name; + warning(e, true); + } - if (property instanceof AttributeType<?>) { - final Class<?> source = ((AttributeType<?>) property).getValueClass(); + if (property instanceof DefaultAttributeType<?>) { + final Class<?> source = ((DefaultAttributeType<?>) property).getValueClass(); if (source != null && source != Object.class && !source.isAssignableFrom(getSourceClass())) { - return new CastedAndConverted<>(source, type, name); + return new CastedAndConverted<>(source, type, targetName); } } + if (!targetName.equals(name)) { + return rename(targetName); + } - } catch (PropertyNotFoundException e) { + } catch (IllegalArgumentException e) { warning(e, true); } return this; @@@ -305,8 -342,24 +346,24 @@@ converter = ObjectConverters.find(source, type); } + /** Creates a new expression derived from an existing one except for the target name. */ + private CastedAndConverted(final CastedAndConverted<S,V> other, final String name) { + super(other.type, name); + source = other.source; + converter = other.converter; + } + + /** + * Creates a new {@code CastedAndConverted} fetching values for a property of different name. + * The given name should be the target of a link that the caller has resolved. + */ + @Override + protected Converted<V> rename(final String target) { + return new CastedAndConverted<>(this, target); + } + /** - * Returns the type of values fetched from {@link Feature} instance. + * Returns the type of values fetched from {@link AbstractFeature} instance. */ @Override protected Class<S> getSourceClass() { diff --cc storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java index 0f31f5b,fc3a6f2..8a9cc2f --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java @@@ -322,11 -323,11 +323,11 @@@ final class FeatureAdapter * @return the feature with attribute values initialized. * @throws Exception if an error occurred while reading the database or converting values. */ - final AbstractFeature createFeature(final ResultSet result) throws Exception { - final Feature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception { - final Feature feature = featureType.newInstance(); ++ final AbstractFeature createFeature(final InfoStatements stmts, final ResultSet result) throws Exception { + final AbstractFeature feature = featureType.newInstance(); for (int i=0; i<attributes.length; i++) { final Column column = attributes[i]; - final Object value = column.valueGetter.getValue(result, i+1); + final Object value = column.valueGetter.getValue(stmts, result, i+1); if (value != null) { feature.setPropertyValue(column.propertyName, value); } diff --cc storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java index 5c79121,844821a..53e2b1e --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureIterator.java @@@ -234,9 -249,9 +249,9 @@@ final class FeatureIterator implements * @param all {@code true} for reading all remaining feature instances, or {@code false} for only the next one. * @return {@code true} if we have read an instance and {@code all} is {@code false} (so there is maybe other instances). */ - private boolean fetch(final Consumer<? super Feature> action, final boolean all) throws Exception { + private boolean fetch(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception { while (result.next()) { - final AbstractFeature feature = adapter.createFeature(result); - final Feature feature = adapter.createFeature(spatialInformation, result); ++ final AbstractFeature feature = adapter.createFeature(spatialInformation, result); for (int i=0; i < dependencies.length; i++) { WeakValueHashMap<?,Object> instances = null; Object key = null, value = null; diff --cc storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java index ca764b2,850cde5..2197dc9 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java @@@ -390,10 -391,9 +391,9 @@@ final class FeatureStream extends Defer * @throws SQLException if an error occurs while executing the SQL statement. */ @Override - protected Spliterator<Feature> createSourceIterator() throws Exception { + protected Spliterator<AbstractFeature> createSourceIterator() throws Exception { final String filter = (selection != null && !selection.isEmpty()) ? selection.toString() : null; - selection = null; // Let the garbage collector do its work. - filterToSQL = null; + selection = null; // Let the garbage collector do its work. final Connection connection = getConnection(); setCloseHandler(connection); // Executed only if `FeatureIterator` creation fails, discarded later otherwise. diff --cc storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java index 613724c,46a1e59..23806e2 --- 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 @@@ -25,6 -29,8 +29,8 @@@ import java.sql.Connection import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import org.opengis.metadata.Identifier; ++import org.opengis.referencing.ReferenceIdentifier; + import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.NoSuchAuthorityCodeException; @@@ -401,7 -440,110 +440,110 @@@ public class InfoStatements implements } /** - * Closes all prepared statements.This method does <strong>not</strong> close the connection. + * Finds a SRID code from the spatial reference systems table for the given CRS. + * + * @param crs the CRS for which to find a SRID, or {@code null}. + * @return SRID for the given CRS, or 0 if the given CRS was null. + * @throws Exception if an SQL error, parsing error or other error occurred. + */ + public final int findSRID(final CoordinateReferenceSystem crs) throws Exception { + if (crs == null) { + return 0; + } + synchronized (database.cacheOfSRID) { + final Integer srid = database.cacheOfSRID.get(crs); + if (srid != null) { + return srid; + } + } + final Set<SimpleImmutableEntry<String,String>> done = new HashSet<>(); + Iterator<IdentifiedObject> alternatives = null; + IdentifiedObject candidate = crs; + Exception error = null; + for (;;) { + /* + * First, iterate over the identifiers declared in the CRS object. + * If we can not find an identifier that we can map to a SRID, then this loop may be + * executed more times with CRS from EPSG database that are equal, ignore axis order. + */ - for (final Identifier id : candidate.getIdentifiers()) { ++ for (final ReferenceIdentifier id : candidate.getIdentifiers()) { + final String authority = id.getCodeSpace(); + if (authority == null) continue; + final String code = id.getCode(); + if (!done.add(new SimpleImmutableEntry<>(authority, code))) { + continue; // Skip "authority:code" that we already tried. + } + final int codeValue; + try { + codeValue = Integer.parseInt(code); + } catch (NumberFormatException e) { + if (error == null) error = e; + else error.addSuppressed(e); + continue; // Ignore codes that are not integers. + } + /* + * Found an "authority:code" pair that we did not tested before. + * Get the WKT and verifies if the CRS is approximately equal. + */ + if (sridFromCRS == null) { + final SQLBuilder sql = new SQLBuilder(database); + sql.append("SELECT srtext, srid"); + appendFrom(sql, SPATIAL_REF_SYS); + sql.append("auth_name=? AND auth_srid=?"); + sridFromCRS = connection.prepareStatement(sql.toString()); + } + sridFromCRS.setString(1, authority); + sridFromCRS.setInt(2, codeValue); + try (ResultSet result = sridFromCRS.executeQuery()) { + while (result.next()) { + final String wkt = result.getString(1); + if (wkt != null && !wkt.isEmpty()) try { + final Object parsed = wktReader().parseObject(wkt); + if (Utilities.equalsApproximately(parsed, crs)) { + final int srid = result.getInt(2); + synchronized (database.cacheOfSRID) { + database.cacheOfSRID.put(crs, srid); + } + return srid; + } + } catch (ParseException e) { + if (error == null) error = e; + else error.addSuppressed(e); + } + } + } + } + /* + * Tried all identifiers associated to the CRS and found no match. + * It may be because the CRS has no identifier at all. Search for + * possible identifiers in the EPSG database, then try them. + */ + if (alternatives == null) { + final IdentifiedObjectFinder finder = IdentifiedObjects.newFinder(Constants.EPSG); + finder.setIgnoringAxes(true); + alternatives = finder.find(crs).iterator(); + } + if (!alternatives.hasNext()) break; + candidate = alternatives.next(); + } + throw new DataStoreReferencingException(Resources.format( + Resources.Keys.CanNotFindSRID_1, IdentifiedObjects.getDisplayName(crs, null)), error); + } + + /** + * Returns the object to use for parsing Well Known Text (CRS). + * The parser is created when first needed. + */ + private WKTFormat wktReader() { + if (wktReader == null) { + wktReader = new WKTFormat(null, null); + wktReader.setConvention(Convention.WKT1_COMMON_UNITS); + } + return wktReader; + } + + /** + * Closes all prepared statements. This method does <strong>not</strong> close the connection. * * @throws SQLException if an error occurred while closing a connection. */ diff --cc storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/PostgresTest.java index 7a43465,abcf98d..4747684 --- 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 @@@ -20,6 -48,13 +48,13 @@@ import org.apache.sis.test.TestCase import org.apache.sis.util.Version; import org.junit.Test; + // Branch-dependent imports -import org.opengis.feature.Feature; ++import org.apache.sis.feature.AbstractFeature; + + // Optional dependencies + import org.locationtech.jts.geom.Point; + import org.locationtech.jts.geom.Geometry; + import static org.junit.Assert.*; @@@ -42,4 -79,123 +79,123 @@@ public final strictfp class PostgresTes assertEquals(1, version.getMinor()); assertNull ( version.getRevision()); } + + /** + * Tests reading and writing features and rasters. + * + * @throws Exception if an error occurred while testing the database. + */ + @Test + public void testSpatialFeatures() throws Exception { + try (TestDatabase database = TestDatabase.createOnPostgreSQL(SQLStoreTest.SCHEMA, true)) { + database.executeSQL(PostgresTest.class, "file:SpatialFeatures.sql"); + final StorageConnector connector = new StorageConnector(database.source); + connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.JTS); + final ResourceDefinition table = ResourceDefinition.table(null, SQLStoreTest.SCHEMA, "SpatialData"); + try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, table)) { + /* + * Invoke the private `model()` method. We have to use reflection because the class + * is not in the same package and we do not want to expose the method in public API. + */ + final Method modelAccessor = SQLStore.class.getDeclaredMethod("model"); + modelAccessor.setAccessible(true); + final Postgres<?> pg = (Postgres<?>) modelAccessor.invoke(store); + try (Connection connection = database.source.getConnection(); + ExtendedInfo info = new ExtendedInfo(pg, connection)) + { + testInfoStatements(info); + testGeometryGetter(info, connection); + testRasterReader(TestRaster.USHORT, info, connection); + } + /* + * Tests through public API. + */ + final FeatureSet resource = store.findResource("SpatialData"); - try (Stream<Feature> features = resource.features(false)) { ++ try (Stream<AbstractFeature> features = resource.features(false)) { + features.forEach(PostgresTest::validate); + } + final Envelope envelope = resource.getEnvelope().get(); + assertEquals(envelope.getMinimum(0), -72, 1); + assertEquals(envelope.getMaximum(1), 43, 1); + } + } + } + + /** + * Tests {@link org.apache.sis.internal.sql.feature.InfoStatements}. + * + * @throws Exception if an error occurred while testing the database. + */ + private static void testInfoStatements(final ExtendedInfo info) throws Exception { + assertEquals("findSRID", 4326, info.findSRID(HardCodedCRS.WGS84)); + assertSame("fetchCRS", CRS.forCode("EPSG:3395"), info.fetchCRS(3395)); + } + + /** + * 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 static void testGeometryGetter(final ExtendedInfo info, final Connection connection) throws Exception { + final GeometryGetterTest test = new GeometryGetterTest(); + test.testFromDatabase(connection, info, BinaryEncoding.HEXADECIMAL); + } + + /** + * Tests {@link RasterReader} in the context of a database. + */ + private static 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 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()); + } + } + + /** + * Invoked for each feature instances for performing some checks on the feature. + * This method performs only a superficial verification of geometries. + */ - private static void validate(final Feature feature) { ++ private static void validate(final AbstractFeature feature) { + final String filename = feature.getPropertyValue("filename").toString(); + final Geometry geometry = (Geometry) feature.getPropertyValue("geometry"); + final GridCoverage raster = (GridCoverage) feature.getPropertyValue("image"); + final int geomSRID; + switch (filename) { + case "raster-ushort.wkb": { + assertNull(geometry); + RasterReaderTest.compareReadResult(TestRaster.USHORT, raster); + assertSame(CommonCRS.WGS84.normalizedGeographic(), raster.getCoordinateReferenceSystem()); + return; + } + case "point-prj": { + final Point p = (Point) geometry; + assertEquals(2, p.getX(), STRICT); + assertEquals(3, p.getY(), STRICT); + geomSRID = 3395; + break; + } + case "polygon-prj": geomSRID = 3395; break; + case "linestring": + case "polygon": + case "multi-linestring": + case "multi-polygon": geomSRID = 4326; break; + default: throw new AssertionError(filename); + } + try { + assertEquals(GeometryGetterTest.getExpectedCRS(geomSRID), JTS.getCoordinateReferenceSystem(geometry)); + } catch (FactoryException e) { + throw new AssertionError(e); + } + assertNull(raster); + } } diff --cc storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java index 6c4729d,cd2cb95..d5d274e --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java @@@ -428,6 -443,35 +439,35 @@@ public final strictfp class SQLStoreTes } /** + * Requests a new set of features filtered by a condition on the "sis:identifier" property. + * The optimizer should replace that link by a condition on the actual column. + * + * @param dataset the store on which to query the features. + * @throws DataStoreException if an error occurred during query execution. + */ + private void verifyWhereOnLink(SQLStore dataset) throws Exception { + final String desiredProperty = "native_name"; + final String[] expectedValues = {"Canada"}; + final FeatureSet countries = dataset.findResource("Countries"); + final FeatureQuery query = new FeatureQuery(); + query.setSelection(FF.equal(FF.property("sis:identifier"), FF.literal("CAN"))); + final String executionMode; + final Object[] names; - try (Stream<Feature> features = countries.subset(query).features(false)) { ++ try (Stream<AbstractFeature> features = countries.subset(query).features(false)) { + executionMode = features.toString(); + names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray(); + } + assertArrayEquals(expectedValues, names); + /* + * Verify that the query is executed with a SQL statement, not with Java code. + * The use of SQL is made possible by the replacement of "sis:identifier" link + * by a reference to "code" column. If that replacement is not properly done, + * then the "predicates" value would be "Java" instead of "SQL". + */ + assertEquals("FeatureStream[table=“Countries”, predicates=“SQL”]", executionMode); + } + + /** * Checks that operations stacked on feature stream are well executed. * This test focuses on mapping and peeking actions overloaded by SQL streams. * Operations used here are meaningless; we just want to ensure that the pipeline does not skip any operation.