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.

Reply via email to