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 aeaadf2f05 GeoTIFF writer should throw an exception instead of logging 
a warning when the CRS or the "grid to CRS" transform cannot be encoded.
aeaadf2f05 is described below

commit aeaadf2f05a610a22af587cc7581adf335f5f404
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Dec 17 16:17:03 2024 +0100

    GeoTIFF writer should throw an exception instead of logging a warning
    when the CRS or the "grid to CRS" transform cannot be encoded.
---
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |   6 +-
 .../sis/storage/geotiff/writer/GeoEncoder.java     | 115 +++++++++++----------
 .../sis/storage/IncompatibleResourceException.java |  56 +++++++++-
 .../sis/storage/base/WritableAggregateSupport.java |   2 +-
 .../storage/base/WritableGridCoverageSupport.java  |   6 +-
 .../apache/sis/storage/esri/AsciiGridStore.java    |  27 ++---
 .../org/apache/sis/storage/esri/WritableStore.java |   4 +-
 .../apache/sis/storage/image/WritableStore.java    |  27 ++---
 .../apache/sis/storage/geopackage/GpkgStore.java   |   2 +-
 9 files changed, 151 insertions(+), 94 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 0c5cd17ce6..5dabda1abe 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -683,8 +683,8 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
      * @param  metadata  title, author and other information, or {@code null} 
if none.
      * @return the effectively added resource. Using this resource may cause 
data to be reloaded.
      * @throws ReadOnlyStorageException if this data store is read-only.
-     * @throws DataStoreException if the given {@code image} has a property 
which is not supported by this writer,
-     *         or if an error occurred while writing to the output stream.
+     * @throws IncompatibleResourceException if the given {@code image} has a 
property which is not supported by this writer.
+     * @throws DataStoreException if an error occurred while writing to the 
output stream.
      *
      * @since 1.5
      */
@@ -708,7 +708,7 @@ public class GeoTiffStore extends DataStore implements 
Aggregate {
             }
             index = writer.imageIndex++;
         } catch (RasterFormatException | ArithmeticException e) {
-            throw new IncompatibleResourceException(cannotWrite(), e);
+            throw new IncompatibleResourceException(cannotWrite(), 
e).addAspect("raster");
         } catch (IOException e) {
             throw new DataStoreException(cannotWrite(), e);
         }
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index a88c8b21d4..25536bca7b 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -34,6 +34,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CartesianCS;
@@ -63,6 +64,7 @@ import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.PixelInCell;
 import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
+import org.apache.sis.storage.IncompatibleResourceException;
 import org.apache.sis.storage.base.MetadataFetcher;
 import org.apache.sis.storage.geotiff.base.UnitKey;
 import org.apache.sis.storage.geotiff.base.GeoKeys;
@@ -223,26 +225,31 @@ public final class GeoEncoder {
      * @throws ArithmeticException if a short value cannot be stored as an 
unsigned 16 bits integer.
      * @throws IncommensurableException if a measure uses an unexpected unit 
of measurement.
      * @throws IncompleteGridGeometryException if the grid geometry is 
incomplete.
+     * @throws IncompatibleResourceException if the grid geometry cannot be 
encoded.
      */
     public void write(final GridGeometry grid, final MetadataFetcher<?> 
metadata)
-                  throws FactoryException, IncommensurableException
+            throws FactoryException, IncommensurableException, 
IncompatibleResourceException
     {
         citation  = CollectionsExt.first(metadata.transformationDimension);
         isPoint   = CollectionsExt.first(metadata.cellGeometry) == 
CellGeometry.POINT;
         gridToCRS = MathTransforms.getMatrix(grid.getGridToCRS(isPoint ? 
PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER));
         if (gridToCRS == null) {
-            
warning(resources().getString(Resources.Keys.CanNotEncodeNonLinearModel), null);
+            String message = 
resources().getString(Resources.Keys.CanNotEncodeNonLinearModel);
+            throw new 
IncompatibleResourceException(message).addAspect("gridToCRS");
         }
         if (grid.isDefined(GridGeometry.CRS)) {
             fullCRS = grid.getCoordinateReferenceSystem();
             final CoordinateReferenceSystem crs = 
CRS.getHorizontalComponent(fullCRS);
-            if ((crs instanceof ProjectedCRS && writeCRS((ProjectedCRS) crs)) 
||
-                (crs instanceof GeodeticCRS  && writeCRS((GeodeticCRS) crs, 
false)))
-            {
+            if (crs instanceof ProjectedCRS) {
+                writeCRS((ProjectedCRS) crs);
                 writeCRS(CRS.getVerticalComponent(fullCRS, true));
-            } else {
-                unsupportedType(fullCRS);
+            } else if (crs instanceof GeodeticCRS) {
+                writeCRS((GeodeticCRS) crs, false);
+                writeCRS(CRS.getVerticalComponent(fullCRS, true));
+            } else if (fullCRS instanceof EngineeringCRS && 
ReferencingUtilities.getDimension(fullCRS) == 2) {
                 writeModelType(GeoCodes.userDefined);
+            } else {
+                throw unsupportedType(fullCRS);
             }
         } else {
             writeModelType(GeoCodes.undefined);
@@ -270,8 +277,9 @@ public final class GeoEncoder {
      *
      * @param  crs  the CRS to write, or {@code null} if none.
      * @throws FactoryException if an error occurred while fetching an EPSG 
code.
+     * @throws IncompatibleResourceException if a unit of measurement cannot 
be encoded.
      */
-    private void writeCRS(final VerticalCRS crs) throws FactoryException {
+    private void writeCRS(final VerticalCRS crs) throws FactoryException, 
IncompatibleResourceException {
         if (crs != null) {
             hasVerticalAxis = true;
             if (writeEPSG(GeoKeys.Vertical, crs)) {
@@ -298,24 +306,25 @@ public final class GeoEncoder {
      *
      * @param  crs        the CRS to write.
      * @param  isBaseCRS  whether to write the base CRS of a projected CRS.
-     * @return whether this method has been able to write the CRS.
      * @throws FactoryException if an error occurred while fetching an EPSG 
code.
      * @throws IncommensurableException if a measure uses an unexpected unit 
of measurement.
+     * @throws IncompatibleResourceException if the <abbr>CRS</abbr> has an 
incompatible property.
      */
-    private boolean writeCRS(final GeodeticCRS crs, final boolean isBaseCRS) 
throws FactoryException, IncommensurableException {
+    private void writeCRS(final GeodeticCRS crs, final boolean isBaseCRS)
+            throws FactoryException, IncommensurableException, 
IncompatibleResourceException
+    {
         final short type;
         final CoordinateSystem cs = crs.getCoordinateSystem();
         addUnits(UnitKey.ANGULAR, cs);
         if (cs instanceof EllipsoidalCS) {
             type = GeoCodes.ModelTypeGeographic;
         } else if (isBaseCRS) {
-            
warning(resources().getString(Resources.Keys.CanNotEncodeNonGeographicBase), 
null);
-            return false;
+            String message = 
resources().getString(Resources.Keys.CanNotEncodeNonGeographicBase);
+            throw new IncompatibleResourceException(message).addAspect("crs");
         } else if (cs instanceof CartesianCS) {
             type = GeoCodes.ModelTypeGeocentric;
         } else {
-            unsupportedType(cs);
-            return false;
+            throw unsupportedType(cs);
         }
         /*
          * Start writing GeoTIFF keys for the geodetic CRS, potentially 
followed by datum, prime meridian and ellipsoid
@@ -361,7 +370,6 @@ public final class GeoEncoder {
         } else if (isBaseCRS) {
             writeUnit(UnitKey.ANGULAR);         // Map projection parameters 
may need this unit.
         }
-        return true;
     }
 
     /**
@@ -371,23 +379,27 @@ public final class GeoEncoder {
      * @return whether this method has been able to write the CRS.
      * @throws FactoryException if an error occurred while fetching an EPSG or 
GeoTIFF code.
      * @throws IncommensurableException if a measure uses an unexpected unit 
of measurement.
+     * @throws IncompatibleResourceException if the <abbr>CRS</abbr> has an 
incompatible property.
      */
-    private boolean writeCRS(final ProjectedCRS crs) throws FactoryException, 
IncommensurableException {
-        if (!writeCRS(crs.getBaseCRS(), true)) {
-            return false;
-        }
+    private boolean writeCRS(final ProjectedCRS crs)
+            throws FactoryException, IncommensurableException, 
IncompatibleResourceException
+    {
+        writeCRS(crs.getBaseCRS(), true);
         if (writeEPSG(GeoKeys.ProjectedCRS, crs)) {
             writeName(GeoKeys.ProjectedCitation, null, crs);
             addUnits(UnitKey.PROJECTED, crs.getCoordinateSystem());
             final Conversion projection = crs.getConversionFromBase();
             if (writeEPSG(GeoKeys.Projection, projection)) {
                 final var method = projection.getMethod();
-                final short projCode = getGeoCode(method);
+                final short projCode = getGeoCode(0, method);
                 writeShort(GeoKeys.ProjMethod, projCode);
                 writeUnit(UnitKey.PROJECTED);
                 switch (projCode) {
-                    case GeoCodes.undefined:   
missingValue(GeoKeys.ProjMethod); return true;
-                    case GeoCodes.userDefined: cannotEncode(0, name(method), 
null); break;
+                    case GeoCodes.userDefined:  // Should not happen.
+                    case GeoCodes.undefined: {
+                        missingValue(GeoKeys.ProjMethod);
+                        return true;
+                    }
                     /*
                      * TODO: GeoTIFF requirement 27.4 said that 
ProjectedCitationGeoKey shall be provided,
                      * But how? Using the same multiple-names convention ("GCS 
Name") as for geodetic CRS?
@@ -400,12 +412,12 @@ public final class GeoEncoder {
                 RuntimeException cause = null;
                 final var descriptor = p.getDescriptor();
                 if (p instanceof ParameterValue<?>) {
-                    final short key = getGeoCode(descriptor);
+                    final short key = getGeoCode(1, descriptor);
                     if (key != GeoCodes.undefined && key != 
GeoCodes.userDefined) {
                         final var pv = (ParameterValue<?>) p;
                         final UnitKey type = 
UnitKey.ofProjectionParameter(key);
                         if (type == UnitKey.LINEAR) {
-                            continue;                   // Skip the "cannot 
encode" warning.
+                            continue;                   // Skip the "cannot 
encode" error.
                         }
                         if (type != UnitKey.NULL) try {
                             final Unit<?> unit = units.getOrDefault(type, 
type.defaultUnit());
@@ -416,7 +428,7 @@ public final class GeoEncoder {
                         }
                     }
                 }
-                cannotEncode(1, name(descriptor), cause);
+                throw cannotEncode(1, name(descriptor), cause);
             }
         }
         return true;
@@ -428,18 +440,20 @@ public final class GeoEncoder {
      *
      * @param  main  the main kind of units expected in the coordinate system.
      * @param  cs    the coordinate system to analyze.
+     * @throws IncompatibleResourceException if the unit of measurement cannot 
be encoded.
      */
-    private void addUnits(final UnitKey main, final CoordinateSystem cs) {
+    private void addUnits(final UnitKey main, final CoordinateSystem cs) 
throws IncompatibleResourceException {
         for (int i = cs.getDimension(); --i >= 0;) {
             final Unit<?> unit = cs.getAxis(i).getUnit();
             final UnitKey type = main.validate(unit);
             if (type != null) {
                 final Unit<?> previous = units.putIfAbsent(type, unit);
                 if (previous != null && !previous.equals(unit)) {
-                    
warning(errors().getString(Errors.Keys.HeterogynousUnitsIn_1, name(cs)), null);
+                    String message = 
errors().getString(Errors.Keys.HeterogynousUnitsIn_1, name(cs));
+                    throw new 
IncompatibleResourceException(message).addAspect("crs");
                 }
             } else {
-                cannotEncode(2, unit.toString(), null);
+                throw cannotEncode(2, unit.toString(), null).addAspect("unit");
             }
         }
     }
@@ -449,8 +463,9 @@ public final class GeoEncoder {
      * This method should be invoked only once per unit key.
      *
      * @param  key  identification of the unit to write.
+     * @throws IncompatibleResourceException if the unit of measurement cannot 
be encoded.
      */
-    private void writeUnit(final UnitKey key) {
+    private void writeUnit(final UnitKey key) throws 
IncompatibleResourceException {
         final Unit<?> unit = units.get(key);
         if (unit != null) {
             final short epsg = toShortEPSG(Units.getEpsgCode(unit, 
key.isAxis));
@@ -460,7 +475,7 @@ public final class GeoEncoder {
                 writeShort(key.codeKey, epsg);
                 writeDouble(key.scaleKey, Units.toStandardUnit(unit));
             } else {
-                cannotEncode(2, unit.toString(), null);
+                throw cannotEncode(2, unit.toString(), null).addAspect("unit");
             }
         }
     }
@@ -529,21 +544,26 @@ public final class GeoEncoder {
      * Fetches the GeoTIFF code of the given object. If {@code null}, returns 
{@link GeoCodes#undefined}.
      * If the object has no GeTIFF identifier, returns {@value 
GeoCodes#userDefined}.
      *
+     * @param  type    object type: 0 = operation method, 1 = parameter.
      * @param  object  the object for which to get the GeoTIFF code.
      * @return the GeoTIFF code, or {@link GeoCodes#undefined} or {@link 
GeoCodes#userDefined} if none.
      * @throws FactoryException if an error occurred while fetching the 
GeoTIFF code.
+     * @throws IncompatibleResourceException if the GeoTIFF identifier cannot 
be obtained.
      */
-    private short getGeoCode(final IdentifiedObject object) throws 
FactoryException {
+    private short getGeoCode(final int type, final IdentifiedObject object)
+            throws FactoryException, IncompatibleResourceException
+    {
         if (object == null) {
             return GeoCodes.undefined;
         }
         final Identifier id = IdentifiedObjects.getIdentifier(object, 
Citations.GEOTIFF);
+        NumberFormatException cause = null;
         if (id != null) try {
             return Short.parseShort(id.getCode());
         } catch (NumberFormatException e) {
-            warning(errors().getString(Errors.Keys.CanNotParse_1, 
IdentifiedObjects.toString(id)), e);
+            cause = e;
         }
-        return GeoCodes.userDefined;
+        throw cannotEncode(type, name(object), cause);
     }
 
     /**
@@ -770,38 +790,29 @@ public final class GeoEncoder {
      * @param  key  the GeoKey for which we found no value.
      */
     private void missingValue(final short key) {
-        warning(resources().getString(Resources.Keys.MissingGeoValue_1, 
GeoKeys.name(key)), null);
+        
listeners.warning(resources().getString(Resources.Keys.MissingGeoValue_1, 
GeoKeys.name(key)));
     }
 
     /**
-     * Logs a warning saying that the given object cannot be encoded becasuse 
of its type.
+     * Prepares an exception saying that the given object cannot be encoded 
because of its type.
      *
      * @param  object  object that cannot be encoded.
      */
-    private void unsupportedType(final IdentifiedObject object) {
-        warning(resources().getString(Resources.Keys.CanNotEncodeObjectType_1, 
ReferencingUtilities.getInterface(object)), null);
+    private IncompatibleResourceException unsupportedType(final 
IdentifiedObject object) {
+        String message = 
resources().getString(Resources.Keys.CanNotEncodeObjectType_1, 
ReferencingUtilities.getInterface(object));
+        return new IncompatibleResourceException(message).addAspect("crs");
     }
 
     /**
-     * Logs a warning saying that an object of the given name cannot be 
encoded.
+     * Prepares an exception saying that an object of the given name cannot be 
encoded.
      *
      * @param  type   object type: 0 = operation method, 1 = parameter, 2 = 
unit of measurement.
      * @param  name   name of the object that cannot be encoded.
-     * @param  cause  the reason why a warning occurred, or {@code null} if 
none.
-     */
-    private void cannotEncode(final int type, final String name, final 
Exception cause) {
-        
warning(resources().getString(Resources.Keys.CanNotEncodeNamedObject_2, type, 
name), cause);
-    }
-
-    /**
-     * Reports a warning that occurred while analyzing the CRS.
-     * This warning may prevent readers to reconstruct the CRS correctly.
-     *
-     * @param  message  the warning message.
-     * @param  cause    the reason why a warning occurred, or {@code null} if 
none.
+     * @param  cause  the reason why an error occurred, or {@code null} if 
none.
      */
-    private void warning(final String message, final Exception cause) {
-        listeners.warning(message, cause);
+    private IncompatibleResourceException cannotEncode(final int type, final 
String name, final Exception cause) {
+        String message = 
resources().getString(Resources.Keys.CanNotEncodeNamedObject_2, type, name);
+        return new IncompatibleResourceException(message, 
cause).addAspect("crs");
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IncompatibleResourceException.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IncompatibleResourceException.java
index 2c8a9288a0..4540cfe6f6 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IncompatibleResourceException.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/IncompatibleResourceException.java
@@ -16,13 +16,18 @@
  */
 package org.apache.sis.storage;
 
+import java.util.Set;
+
 
 /**
  * Thrown when a write operation cannot be performed because the resource to 
write
  * is incompatible with the data store.
+ * For example, the file format may have restrictions that prevent the 
encoding of the coordinate
+ * reference system used by the resource. The {@link #getAspects()} method can 
help to identify
+ * which aspects (class, <abbr>CRS</abbr>, <i>etc.</i>) are the causes of the 
incompatibility.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.5
  * @since   1.2
  */
 public class IncompatibleResourceException extends DataStoreException {
@@ -31,6 +36,13 @@ public class IncompatibleResourceException extends 
DataStoreException {
      */
     private static final long serialVersionUID = -1833794980891065300L;
 
+    /**
+     * Identification of which aspects are incompatible, or {@code null} if 
none.
+     * If non-null, this is usually a singleton set.
+     */
+    @SuppressWarnings("serial")
+    private Set<String> aspects;
+
     /**
      * Creates an exception with no cause and no details message.
      */
@@ -55,4 +67,46 @@ public class IncompatibleResourceException extends 
DataStoreException {
     public IncompatibleResourceException(String message, Throwable cause) {
         super(message, cause);
     }
+
+    /**
+     * Adds an identification of the aspect which is the cause of the 
incompatibility.
+     * It should be the name of a property such as {@code "crs"} or {@code 
"gridToCRS"}.
+     * See {@link #getAspects()} for a list of suggested values.
+     *
+     * @param  name  an identification of the aspect which is incompatible.
+     * @return {@code this} for method call chaining.
+     * @since 1.5
+     */
+    public IncompatibleResourceException addAspect(final String name) {
+        if (aspects == null) {
+            aspects = Set.of(name);
+        } else {
+            // Inefficient, but rarely used.
+            final int n = aspects.size();
+            String[] names = aspects.toArray(new String[n+1]);
+            names[n] = name;
+            aspects = Set.of(names);
+        }
+        return this;
+    }
+
+    /**
+     * Returns identifications of the aspects which are causes of the 
incompatibility.
+     * Some values are:
+     *
+     * <ul>
+     *   <li>{@code "class"}:        the resources is not an instance of the 
class expected by the writer.</li>
+     *   <li>{@code "crs"}:          the coordinate reference system cannot be 
encoded.</li>
+     *   <li>{@code "gridToCRS"}:    the "grid to <abbr>CRS</abbr>" component 
of the grid geometry of a raster cannot be encoded.</li>
+     *   <li>{@code "gridGeometry"}: the grid geometry of a raster cannot be 
encoded for reason less specific than {@code gridToCRS}.</li>
+     *   <li>{@code "raster"}:       the raster data cannot be encoded.</li>
+     *   <li>{@code "unit"}:         the unit of measurement cannot be 
encoded.</li>
+     * </ul>
+     *
+     * @return identifications of aspects which are incompatible.
+     * @since 1.5
+     */
+    public Set<String> getAspects() {
+        return (aspects != null) ? aspects : Set.of();
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableAggregateSupport.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableAggregateSupport.java
index 6e68cd3f6d..2752231cee 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableAggregateSupport.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableAggregateSupport.java
@@ -93,7 +93,7 @@ public final class WritableAggregateSupport implements 
Localized {
         if (Objects.requireNonNull(resource) instanceof GridCoverageResource) {
             return (GridCoverageResource) resource;
         }
-        throw new 
IncompatibleResourceException(message(GridCoverageResource.class, resource));
+        throw new 
IncompatibleResourceException(message(GridCoverageResource.class, 
resource)).addAspect("class");
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
index 37ad5a39a8..c9d27905c9 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/WritableGridCoverageSupport.java
@@ -173,7 +173,7 @@ public final class WritableGridCoverageSupport implements 
Localized {
      */
     public final GridCoverage update(final GridCoverage coverage) throws 
DataStoreException {
         final GridCoverage existing = target.read(null, null);
-        final CoverageCombiner combiner = new CoverageCombiner(existing);
+        final var combiner = new CoverageCombiner(existing);
         try {
             if (!combiner.acceptAll(coverage)) {
                 throw new ReadOnlyStorageException(canNotWrite());
@@ -196,14 +196,14 @@ public final class WritableGridCoverageSupport implements 
Localized {
     public final AffineTransform getAffineTransform2D(final GridExtent extent, 
final MathTransform gridToCRS)
             throws DataStoreException
     {
-        final TransformSeparator s = new TransformSeparator(gridToCRS);
+        final var s = new TransformSeparator(gridToCRS);
         try {
             s.addSourceDimensions(extent.getSubspaceDimensions(2));
             return AffineTransforms2D.castOrCopy(s.separate());
         } catch (FactoryException | CannotEvaluateException e) {
             throw new DataStoreReferencingException(canNotWrite(), e);
         } catch (IllegalArgumentException e) {
-            throw new IncompatibleResourceException(canNotWrite(), e);
+            throw new IncompatibleResourceException(canNotWrite(), 
e).addAspect("gridToCRS");
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
index 222f869de2..f7de0e522a 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/AsciiGridStore.java
@@ -65,48 +65,39 @@ import org.apache.sis.util.resources.Errors;
  *     <th>Keyword</th>
  *     <th>Value type</th>
  *     <th>Obligation</th>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code NCOLS}</td>
  *     <td>{@link java.lang.Integer}</td>
  *     <td>Mandatory</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code NROWS}</td>
  *     <td>{@link java.lang.Integer}</td>
  *     <td>Mandatory</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code XLLCORNER} or {@code XLLCENTER}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Mandatory</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code YLLCORNER} or {@code YLLCENTER}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Mandatory</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code CELLSIZE}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Mandatory, unless an alternative below is present</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code XCELLSIZE} and {@code YCELLSIZE}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Non-standard alternative to {@code CELLSIZE}</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code XDIM} and {@code YDIM}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Non-standard alternative to {@code CELLSIZE}</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code DX} and {@code DY}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Non-standard alternative to {@code CELLSIZE}</td>
- *   </tr>
- *   <tr>
+ *   </tr><tr>
  *     <td>{@code NODATA_VALUE}</td>
  *     <td>{@link java.lang.Double}</td>
  *     <td>Optional</td>
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
index 10c0247fa9..66ca6828ea 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/WritableStore.java
@@ -131,7 +131,7 @@ final class WritableStore extends AsciiGridStore implements 
WritableGridCoverage
         }
         final AffineTransform at = h.getAffineTransform2D(gg.getExtent(), 
gridToCRS);
         if (at.getShearX() != 0 || at.getShearY() != 0) {
-            throw new 
IncompatibleResourceException(h.rotationNotSupported(AsciiGridStoreProvider.NAME));
+            throw new 
IncompatibleResourceException(h.rotationNotSupported(AsciiGridStoreProvider.NAME)).addAspect("gridToCRS");
         }
         double scaleX =  at.getScaleX();
         double scaleY = -at.getScaleY();
@@ -144,7 +144,7 @@ final class WritableStore extends AsciiGridStore implements 
WritableGridCoverage
              * TODO: future version could support other signs, provided that
              * we implement `PixelIterator` for other `SequenceType` values.
              */
-            throw new IncompatibleResourceException(h.canNotWrite());
+            throw new 
IncompatibleResourceException(h.canNotWrite()).addAspect("gridToCRS");
         }
         header.put(xll, x);
         header.put(yll, y);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableStore.java
index a55ac4c04a..cf94e852c2 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/image/WritableStore.java
@@ -151,9 +151,9 @@ class WritableStore extends WorldFileStore {
     @Override
     public String[] getImageFormat(final boolean asMimeType) {
         if (writer != null) {
-            final ImageWriterSpi provider = writer.getOriginatingProvider();
-            if (provider != null) {
-                final String[] names = asMimeType ? provider.getMIMETypes() : 
provider.getFormatNames();
+            final ImageWriterSpi codec = writer.getOriginatingProvider();
+            if (codec != null) {
+                final String[] names = asMimeType ? codec.getMIMETypes() : 
codec.getFormatNames();
                 if (names != null) {
                     return names;
                 }
@@ -223,8 +223,8 @@ class WritableStore extends WorldFileStore {
          */
         if (index != MAIN_IMAGE || isMultiImages() > 1) {
             if (!getGridGeometry(MAIN_IMAGE).equals(gg, 
ComparisonMode.IGNORE_METADATA)) {
-                throw new IncompatibleResourceException(
-                        
resources().getString(Resources.Keys.IncompatibleGridGeometry));
+                String message = 
resources().getString(Resources.Keys.IncompatibleGridGeometry);
+                throw new 
IncompatibleResourceException(message).addAspect("gridGeometry");
             }
         }
         /*
@@ -236,17 +236,17 @@ class WritableStore extends WorldFileStore {
         if (gg.isDefined(GridGeometry.GRID_TO_CRS)) try {
             gridToCRS = 
AffineTransforms2D.castOrCopy(gg.getGridToCRS(CELL_ANCHOR));
         } catch (IllegalArgumentException e) {
-            throw new IncompatibleResourceException(e.getLocalizedMessage(), 
e);
+            throw new IncompatibleResourceException(e.getLocalizedMessage(), 
e).addAspect("gridToCRS");
         }
-        final String suffix = super.setGridGeometry(index, gg);         // May 
throw `ArithmeticException`.
+        final String suffixWLD = super.setGridGeometry(index, gg);      // May 
throw `ArithmeticException`.
         /*
          * If the image is the main one, overwrite (possibly with same 
content) the previous auxiliary files.
          * Otherwise above checks should have ensured that the existing 
auxiliary files are applicable.
          */
-        if (suffix != null) {
+        if (suffixWLD != null) {
             if (gridToCRS == null) {
-                deleteAuxiliaryFile(suffix);
-            } else try (BufferedWriter out = writeAuxiliaryFile(suffix)) {
+                deleteAuxiliaryFile(suffixWLD);
+            } else try (BufferedWriter out = writeAuxiliaryFile(suffixWLD)) {
 writeCoeffs:    for (int i=0;; i++) {
                     final double c;
                     switch (i) {
@@ -264,7 +264,7 @@ writeCoeffs:    for (int i=0;; i++) {
             }
             writePRJ();
         }
-        return suffix;
+        return suffixWLD;
     }
 
     /**
@@ -311,7 +311,7 @@ writeCoeffs:    for (int i=0;; i++) {
             if (domain == null) {
                 domain = coverage.getGridGeometry();        // We are adding 
the first image.
             }
-            final WritableResource image = new WritableResource(this, 
listeners, numImages, domain);
+            final var image = new WritableResource(this, listeners, numImages, 
domain);
             image.write(coverage);
             components.added(image);        // Must be invoked only after 
above succeeded.
             numImages++;
@@ -335,7 +335,7 @@ writeCoeffs:    for (int i=0;; i++) {
     public synchronized void remove(final Resource resource) throws 
DataStoreException {
         Exception cause = null;
         if (resource instanceof WritableResource) {
-            final WritableResource image = (WritableResource) resource;
+            final var image = (WritableResource) resource;
             if (image.store() == this) try {
                 final int imageIndex = image.getImageIndex();
                 writer().removeImage(imageIndex);
@@ -376,6 +376,7 @@ writeCoeffs:    for (int i=0;; i++) {
      */
     @Override
     ImageReader prepareReader(ImageReader current) throws IOException {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final ImageWriter writer = this.writer;
         if (writer != null) {
             final Object output = writer.getOutput();
diff --git 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
index ea4db47c69..dc3f7b2eaa 100644
--- 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
+++ 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
@@ -371,7 +371,7 @@ public class GpkgStore extends SQLStore implements 
WritableAggregate {
          * Note: Geopackage requires that each feature table has exactly one 
geometry column,
          * while SQLStore accepts any number of geometry columns (including 
zero).
          */
-        throw new IncompatibleResourceException("Unsupported resource type");
+        throw new IncompatibleResourceException("Unsupported resource 
type").addAspect("class");
     }
 
     /**


Reply via email to