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

commit b4d5305eec7694ed140062941f3f3da10fa73907
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Jun 28 15:43:54 2022 +0200

    Add a `GridGeometry.createImageCRS(…)` method.
    It gives a CRS that we can use with `CRS.findOperation(…)`.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  10 +-
 .../apache/sis/coverage/grid/GridExtentCRS.java    | 266 ++++++++++++++++++---
 .../org/apache/sis/coverage/grid/GridGeometry.java |  39 +++
 .../org/apache/sis/internal/feature/Resources.java |   5 +
 .../sis/internal/feature/Resources.properties      |   1 +
 .../sis/internal/feature/Resources_fr.properties   |   1 +
 .../apache/sis/coverage/grid/GridGeometryTest.java |  28 ++-
 .../org/apache/sis/parameter/ParameterFormat.java  |  18 +-
 .../org/apache/sis/parameter/package-info.java     |   2 +-
 .../main/java/org/apache/sis/io/package-info.java  |   2 +-
 10 files changed, 331 insertions(+), 41 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 838e443331..6b40ce9100 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -961,6 +961,14 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
         return Optional.ofNullable((types != null) ? types[index] : null);
     }
 
+    /**
+     * Returns the {@link #types} array or a default array of arbitrary length 
if {@link #types} is null.
+     * This method returns directly the arrays without cloning; do not modify.
+     */
+    final DimensionNameType[] getAxisTypes() {
+        return (types != null) ? types : DEFAULT_TYPES;
+    }
+
     /**
      * Returns the axis number followed by the localized axis type if 
available.
      * This is used for error messages only.
@@ -1028,7 +1036,7 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
         final GeneralEnvelope envelope = toEnvelope(cornerToCRS, cornerToCRS, 
null);
         final Matrix gridToCRS = MathTransforms.getMatrix(cornerToCRS);
         if (gridToCRS != null && Matrices.isAffine(gridToCRS)) try {
-            
envelope.setCoordinateReferenceSystem(GridExtentCRS.build(gridToCRS, (types != 
null) ? types : DEFAULT_TYPES, null));
+            
envelope.setCoordinateReferenceSystem(GridExtentCRS.forExtentAlone(gridToCRS, 
getAxisTypes()));
         } catch (FactoryException e) {
             throw new TransformException(e);
         }
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
index 887e02d002..72e9e2a838 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtentCRS.java
@@ -17,51 +17,200 @@
 package org.apache.sis.coverage.grid;
 
 import java.util.Map;
+import java.util.HashMap;
 import java.util.Collections;
 import java.util.Locale;
-import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.FactoryException;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.crs.EngineeringCRS;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.NamedIdentifier;
+import org.apache.sis.referencing.crs.DefaultDerivedCRS;
+import org.apache.sis.referencing.operation.DefaultConversion;
+import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.AxisDirections;
+import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Characters;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.measure.Units;
-import org.apache.sis.util.Characters;
 
 
 /**
- * Builds the engineering coordinate reference system of a {@link GridExtent}.
- * This is used only in the rare cases where we need to represent an extent as 
an envelope.
- * This class converts {@link DimensionNameType} codes into axis names, 
abbreviations and directions.
- * It is the converse of {@link 
GridExtent#typeFromAxes(CoordinateReferenceSystem, int)}.
+ * Builder for coordinate reference system which is derived from the coverage 
CRS by the inverse
+ * of the "grid to CRS" transform. Those CRS describe coordinates associated 
to the grid extent.
+ * This class provides two factory methods:
+ *
+ * <ul>
+ *   <li>{@link #forCoverage(String, GridGeometry)}</li>
+ *   <li>{@link #forExtentAlone(Matrix, DimensionNameType[])}</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.0
  * @module
  */
 final class GridExtentCRS {
+    /**
+     * Name of the parameter where to store the grid coverage name.
+     */
+    private static final String NAME_PARAM = "Target grid name";
+
+    /**
+     * Name of the parameter specifying the way image indices are
+     * associated with the coverage data attributes.
+     */
+    private static final String ANCHOR_PARAM = "Pixel in cell";
+
+    /**
+     * Description of "CRS to grid indices" operation method.
+     */
+    private static final OperationMethod METHOD;
+    static {
+        final ParameterBuilder b = new ParameterBuilder().setRequired(true);
+        final ParameterDescriptor<?>   name   = 
b.addName(NAME_PARAM).create(String.class, null);
+        final ParameterDescriptor<?>   anchor = 
b.addName(ANCHOR_PARAM).create(PixelInCell.class, PixelInCell.CELL_CENTER);
+        final ParameterDescriptorGroup params = b.addName("CRS to grid 
indices").createGroup(name, anchor);
+        METHOD = new DefaultOperationMethod(properties(params.getName()), 
params);
+    }
+
+    /**
+     * Scope of usage of the CRS.
+     * This is a localized text saying "Conversions from coverage CRS to grid 
cell indices."
+     */
+    private static final InternationalString SCOPE = 
Resources.formatInternational(Resources.Keys.CrsToGridConversion);
+
+    /**
+     * Name of the coordinate systems created by this class.
+     */
+    private static final NamedIdentifier CS_NAME = new NamedIdentifier(
+            Citations.SIS, 
Vocabulary.formatInternational(Vocabulary.Keys.GridExtent));
+
     /**
      * Do not allow instantiation of this class.
      */
     private GridExtentCRS() {
     }
 
+    /**
+     * Creates a derived CRS for the grid extent of a grid coverage.
+     *
+     * <h4>Limitation</h4>
+     * If the CRS is compound, then this method takes only the first single 
CRS element.
+     * This is a restriction imposed by {@link DerivedCRS} API.
+     * As a result, the returned CRS may cover only the 2 or 3 first grid 
dimensions.
+     *
+     * @param  name    name of the CRS to create.
+     * @param  gg      grid geometry of the coverage.
+     * @param  anchor  the cell part to map (center or corner).
+     * @param  locale  locale to use for axis names, or {@code null} for 
default.
+     * @return a derived CRS for coordinates (cell indices) associated to the 
grid extent.
+     * @throws FactoryException if an error occurred during the use of {@link 
CSFactory} or {@link CRSFactory}.
+     */
+    static DerivedCRS forCoverage(final String name, final GridGeometry gg, 
final PixelInCell anchor, final Locale locale)
+            throws FactoryException, NoninvertibleTransformException
+    {
+        /*
+         * Get the first `SingleCRS` instance (see "limitations" in method 
javadoc).
+         */
+        CoordinateReferenceSystem crs = gg.getCoordinateReferenceSystem();
+        boolean reduce = false;
+        while (!(crs instanceof SingleCRS)) {
+            if (!(crs instanceof CompoundCRS)) {
+                throw unsupported(locale, crs);
+            }
+            crs = ((CompoundCRS) crs).getComponents().get(0);
+            reduce = true;
+        }
+        /*
+         * If we took only a subset of CRS dimensions, take the same subset
+         * of "grid to CRS" dimensions and list of grid axes.
+         */
+        MathTransform gridToCRS = gg.getGridToCRS(anchor);
+        DimensionNameType[] types = gg.getExtent().getAxisTypes();
+        if (reduce) {
+            final TransformSeparator s = new TransformSeparator(gridToCRS);
+            s.addTargetDimensionRange(0, 
crs.getCoordinateSystem().getDimension());
+            gridToCRS = s.separate();
+            final int[] src = s.getSourceDimensions();
+            final DimensionNameType[] allTypes = types;
+            types = new DimensionNameType[src.length];
+            for (int i=0; i<src.length; i++) {
+                final int j = src[i];
+                if (j < allTypes.length) {
+                    types[i] = allTypes[j];
+                }
+            }
+        }
+        /*
+         * Build the coordinate system assuming a null (identity) "grid to 
CRS" matrix
+         * because we are building the CS for the grid, not for the 
transformed envelope.
+         */
+        final CoordinateSystem cs = createCS(gridToCRS.getSourceDimensions(), 
null, types, locale);
+        if (cs == null) {
+            throw unsupported(locale, crs);
+        }
+        /*
+         * Put everything together: parameters, conversion and finally the 
derived CRS.
+         */
+        final HashMap<String,Object> properties = new HashMap<>(8);
+        properties.put(IdentifiedObject.NAME_KEY, METHOD.getName());
+        properties.put(DefaultConversion.LOCALE_KEY, locale);
+        properties.put(Conversion.SCOPE_KEY, SCOPE);
+        gg.getGeographicExtent().ifPresent((domain) -> {
+            properties.put(Conversion.DOMAIN_OF_VALIDITY_KEY,
+                    new DefaultExtent(null, domain, null, null));
+        });
+        final ParameterValueGroup params = 
METHOD.getParameters().createValue();
+        params.parameter(NAME_PARAM).setValue(name);
+        params.parameter(ANCHOR_PARAM).setValue(anchor);
+        final Conversion conversion = new DefaultConversion(properties, 
METHOD, gridToCRS.inverse(), params);
+        properties.put(IdentifiedObject.NAME_KEY, name);
+        return DefaultDerivedCRS.create(properties, (SingleCRS) crs, 
conversion, cs);
+    }
+
+    /**
+     * Returns the exception to throw for an unsupported CRS.
+     */
+    private static FactoryException unsupported(final Locale locale, final 
CoordinateReferenceSystem crs) {
+        return new FactoryException(Errors.getResources(locale)
+                .getString(Errors.Keys.UnsupportedType_1, 
Classes.getShortClassName(crs)));
+    }
+
     /**
      * Creates a properties map to give to CS, CRS or datum constructors.
      */
     private static Map<String,?> properties(final Object name) {
-        return Collections.singletonMap(CoordinateSystemAxis.NAME_KEY, name);
+        return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
     }
 
     /**
@@ -85,22 +234,24 @@ final class GridExtentCRS {
     }
 
     /**
-     * Builds a coordinate reference system for the given axis types. The CRS 
type is always engineering.
-     * We can not create temporal CRS because we do not know the temporal 
datum origin.
+     * Creates the coordinate system for engineering CRS.
      *
+     * @param  tgtDim     number of dimensions of the coordinate system to 
create.
      * @param  gridToCRS  matrix of the transform used for converting grid 
cell indices to envelope coordinates.
      *         It does not matter whether it maps pixel center or corner 
(translation coefficients are ignored).
+     *         A {@code null} means to handle as an identity transform.
      * @param  types   the value of {@link GridExtent#types} or a default 
value (shall not be {@code null}).
      * @param  locale  locale to use for axis names, or {@code null} for 
default.
-     * @return CRS for the grid, or {@code null}.
-     *
-     * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
+     * @return coordinate system for the grid extent, or {@code null} if it 
can not be inferred.
+     * @throws FactoryException if an error occurred during the use of {@link 
CSFactory}.
      */
-    static EngineeringCRS build(final Matrix gridToCRS, final 
DimensionNameType[] types, final Locale locale)
-            throws FactoryException
+    private static CoordinateSystem createCS(final int tgtDim, final Matrix 
gridToCRS,
+            final DimensionNameType[] types, final Locale locale) throws 
FactoryException
     {
-        final int tgtDim = gridToCRS.getNumRow() - 1;
-        final int srcDim = Math.min(gridToCRS.getNumCol() - 1, types.length);
+        int srcDim = types.length;      // Used only for inspecting names. No 
need to be accurate.
+        if (gridToCRS != null) {
+            srcDim = Math.min(gridToCRS.getNumCol() - 1, srcDim);
+        }
         final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[tgtDim];
         final CSFactory csFactory = 
DefaultFactories.forBuildin(CSFactory.class);
         boolean hasVertical = false;
@@ -115,20 +266,23 @@ final class GridExtentCRS {
                  * Current version does not accept scale factors, but we could 
revisit
                  * in a future version if there is a need for it.
                  */
-                int target = -1;
+                int target = i;
                 double scale = 0;
-                for (int j=0; j<tgtDim; j++) {
-                    final double m = gridToCRS.getElement(j, i);
-                    if (m != 0) {
-                        if (target >= 0 || axes[j] != null || Math.abs(m) != 
1) {
-                            return null;
+                if (gridToCRS != null) {
+                    target = -1;
+                    for (int j=0; j<tgtDim; j++) {
+                        final double m = gridToCRS.getElement(j, i);
+                        if (m != 0) {
+                            if (target >= 0 || axes[j] != null || Math.abs(m) 
!= 1) {
+                                return null;
+                            }
+                            target = j;
+                            scale  = m;
                         }
-                        target = j;
-                        scale  = m;
                     }
-                }
-                if (target < 0) {
-                    return null;
+                    if (target < 0) {
+                        return null;
+                    }
                 }
                 /*
                  * This hard-coded set of axis directions is the converse of
@@ -189,7 +343,7 @@ final class GridExtentCRS {
          * If no specialized type seems to fit, use an unspecified ("abstract")
          * coordinate system type in last resort.
          */
-        final Map<String,?> properties = properties("Grid extent");
+        final Map<String,?> properties = properties(CS_NAME);
         final CoordinateSystem cs;
         if (hasOther || (tgtDim > (hasTime ? 1 : 3))) {
             cs = new AbstractCS(properties, axes);
@@ -205,9 +359,55 @@ final class GridExtentCRS {
                 }
                 break;
             }
-            case 2:  cs = csFactory.createAffineCS(properties, axes[0], 
axes[1]); break;
-            case 3:  cs = csFactory.createAffineCS(properties, axes[0], 
axes[1], axes[2]); break;
-            default: return null;
+            case 2: {
+                /*
+                 * A null `gridToCRS` means that we are creating a CS for the 
grid, which is assumed a
+                 * Cartesian space. A non-null value means that we are 
creating a CRS for a transformed
+                 * envelope, in which case the CS type is not really known.
+                 */
+                cs = (gridToCRS == null)
+                        ? csFactory.createCartesianCS(properties, axes[0], 
axes[1])
+                        : csFactory.createAffineCS   (properties, axes[0], 
axes[1]);
+                break;
+            }
+            case 3: {
+                cs = (gridToCRS == null)
+                        ? csFactory.createCartesianCS(properties, axes[0], 
axes[1], axes[2])
+                        : csFactory.createAffineCS   (properties, axes[0], 
axes[1], axes[2]);
+                break;
+            }
+            default: {
+                cs = null;
+                break;
+            }
+        }
+        return cs;
+    }
+
+    /**
+     * Builds the engineering coordinate reference system of a {@link 
GridExtent}.
+     * This is used only in the rare cases where we need to represent an 
extent as an envelope.
+     * This class converts {@link DimensionNameType} codes into axis names, 
abbreviations and directions.
+     * It is the converse of {@link 
GridExtent#typeFromAxes(CoordinateReferenceSystem, int)}.
+     *
+     * <p>The CRS type is always engineering.
+     * We can not create temporal CRS because we do not know the temporal 
datum origin.</p>
+     *
+     * @param  gridToCRS  matrix of the transform used for converting grid 
cell indices to envelope coordinates.
+     *         It does not matter whether it maps pixel center or corner 
(translation coefficients are ignored).
+     * @param  types   the value of {@link GridExtent#types} or a default 
value (shall not be {@code null}).
+     * @param  locale  locale to use for axis names, or {@code null} for 
default.
+     * @return CRS for the grid, or {@code null}.
+     * @throws FactoryException if an error occurred during the use of {@link 
CSFactory} or {@link CRSFactory}.
+     *
+     * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
+     */
+    static EngineeringCRS forExtentAlone(final Matrix gridToCRS, final 
DimensionNameType[] types)
+            throws FactoryException
+    {
+        final CoordinateSystem cs = createCS(gridToCRS.getNumRow() - 1, 
gridToCRS, types, null);
+        if (cs == null) {
+            return null;
         }
         return 
DefaultFactories.forBuildin(CRSFactory.class).createEngineeringCRS(
                 properties(cs.getName()), CommonCRS.Engineering.GRID.datum(), 
cs);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index dffde720ea..f3b1730a0e 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -35,7 +35,9 @@ import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.math.MathFunctions;
@@ -1468,6 +1470,43 @@ public class GridGeometry implements LenientComparable, 
Serializable {
         return this;
     }
 
+    /**
+     * Creates a one-, two- or three-dimensional coordinate reference system 
for cell indices in the grid.
+     * This method returns a CRS which is derived from the "real world" CRS or 
a subset of it.
+     * If the "real world" CRS is an instance of {@link 
org.opengis.referencing.crs.SingleCRS},
+     * then the derived CRS has the following properties:
+     *
+     * <ul>
+     *   <li>{@link DerivedCRS#getBaseCRS()} is {@link 
#getCoordinateReferenceSystem()}.</li>
+     *   <li>{@link DerivedCRS#getConversionFromBase()} is the inverse of 
{@link #getGridToCRS(PixelInCell)}.</li>
+     * </ul>
+     *
+     * Otherwise if the "real world" CRS is an instance of {@link 
org.opengis.referencing.crs.CompoundCRS},
+     * then only the first {@link org.opengis.referencing.crs.SingleCRS} (the 
head) is used.
+     * This is usually (but not necessarily) the horizontal component of the 
spatial CRS.
+     * The result is usually two-dimensional, but 1 and 3 dimensions are also 
possible.
+     *
+     * <p>Because of above relationship, it is possible to use the derived CRS 
in a chain of operations
+     * with (for example) {@link org.apache.sis.referencing.CRS#findOperation 
CRS.findOperation(…)}.</p>
+     *
+     * @param  name    name of the CRS to create.
+     * @param  anchor  the cell part to map (center or corner).
+     * @return a derived CRS for coordinates (cell indices) associated to the 
grid extent.
+     * @throws IncompleteGridGeometryException if the CRS, grid extent or 
"grid to CRS" transform is missing.
+     *
+     * @since 1.3
+     */
+    public DerivedCRS createImageCRS(final String name, final PixelInCell 
anchor) {
+        ArgumentChecks.ensureNonEmpty("name", name);
+        try {
+            return GridExtentCRS.forCoverage(name, this, anchor, null);
+        } catch (FactoryException e) {
+            throw new BackingStoreException(e);
+        } catch (NoninvertibleTransformException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
     /**
      * Creates a transform from cell coordinates in this grid to cell 
coordinates in the given grid.
      * The returned transform handles change of Coordinate Reference System 
and wraparound axes
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
index 71d22be341..d3072e00a9 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
@@ -149,6 +149,11 @@ public final class Resources extends IndexedResourceBundle 
{
          */
         public static final short CharacteristicsNotFound_2 = 17;
 
+        /**
+         * Conversions from coverage CRS to grid cell indices.
+         */
+        public static final short CrsToGridConversion = 79;
+
         /**
          * Operation “{0}” requires a “{1}” property, but no such property has 
been found in “{2}”.
          */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
index b2a78dd762..87a953b6da 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
@@ -37,6 +37,7 @@ CanNotVisit_2                     = Can not visit a 
\u201c{1}\u201d {0,choice,0#
 CategoryRangeOverlap_4            = The two categories \u201c{0}\u201d and 
\u201c{2}\u201d have overlapping ranges: {1} and {3} respectively.
 CharacteristicsAlreadyExists_2    = Characteristics \u201c{1}\u201d already 
exists in attribute \u201c{0}\u201d.
 CharacteristicsNotFound_2         = No characteristics named \u201c{1}\u201d 
has been found in \u201c{0}\u201d attribute.
+CrsToGridConversion               = Conversions from coverage CRS to grid cell 
indices.
 DependencyNotFound_3              = Operation \u201c{0}\u201d requires a 
\u201c{1}\u201d property, but no such property has been found in 
\u201c{2}\u201d.
 EmptyImage                        = Image has zero pixel.
 EmptyTileOrImageRegion            = Empty tile or image region.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
index 40d9f79027..951a7ad66b 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
@@ -42,6 +42,7 @@ CanNotVisit_2                     = Ne peut pas visiter 
{0,choice,0#un filtre|1#
 CategoryRangeOverlap_4            = Les deux cat\u00e9gories 
\u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{2}\u202f\u00bb ont des plages de 
valeurs qui se chevauchent\u00a0: {1} et {3} respectivement.
 CharacteristicsAlreadyExists_2    = La caract\u00e9ristique 
\u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019attribut 
\u00ab\u202f{0}\u202f\u00bb.
 CharacteristicsNotFound_2         = Aucune caract\u00e9ristique nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans 
l\u2019attribut \u00ab\u202f{0}\u202f\u00bb.
+CrsToGridConversion               = Conversions des coordonn\u00e9es de la 
couverture de donn\u00e9es vers les indices des cellules de la grille.
 DependencyNotFound_3              = L\u2019op\u00e9ration 
\u00ab\u202f{0}\u202f\u00bb n\u00e9cessite une propri\u00e9t\u00e9 
\u00ab\u202f{1}\u202f\u00bb, mais cette propri\u00e9t\u00e9 n\u2019a pas 
\u00e9t\u00e9 trouv\u00e9e dans \u00ab\u202f{2}\u202f\u00bb.
 EmptyImage                        = L\u2019image a z\u00e9ro pixel.
 EmptyTileOrImageRegion            = La tuile ou la r\u00e9gion de l\u2019image 
est vide.
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index be70a646f1..87ef02e1ac 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.coverage.grid;
 
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.crs.DerivedCRS;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
@@ -61,7 +62,7 @@ public final strictfp class GridGeometryTest extends TestCase 
{
      * Verifies the shift between the two {@code gridToCRS} transforms.
      * This method should be invoked when the transforms are linear.
      *
-     * @param  grid  the grid geoemtry to validate.
+     * @param  grid  the grid geometry to validate.
      */
     private static void verifyGridToCRS(final GridGeometry grid) {
         final Matrix tr1 = 
MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER));
@@ -602,6 +603,31 @@ public final strictfp class GridGeometryTest extends 
TestCase {
         assertMatrixEquals("gridToCRS", new Matrix2(0, 3, 0, 1), 
MathTransforms.getMatrix(tr), STRICT);
     }
 
+    /**
+     * Tests {@link GridGeometry#createImageCRS(String, PixelInCell)}.
+     */
+    @Test
+    public void testCreateImageCRS() {
+        final GridGeometry gg = new GridGeometry(
+                new GridExtent(null, null, new long[] {17, 10, 4}, true),
+                PixelInCell.CELL_CENTER,
+                MathTransforms.linear(new Matrix4(
+                    1,   0,  0, -7,
+                    0,  -1,  0, 50,
+                    0,   0,  8, 20,
+                    0,   0,  0,  1)),
+                HardCodedCRS.WGS84_WITH_TIME);
+
+        final DerivedCRS crs = gg.createImageCRS("Horizontal part", 
PixelInCell.CELL_CENTER);
+        assertEquals("Horizontal part", crs.getName().getCode());
+        final Matrix mt = 
MathTransforms.getMatrix(crs.getConversionFromBase().getMathTransform());
+        assertSame(HardCodedCRS.WGS84, crs.getBaseCRS());
+        assertMatrixEquals("CRS to grid",
+                new Matrix3(1,  0,  7,      // Opposite sign because this is 
the inverse transform.
+                            0, -1, 50,      // Opposite sign cancelled by -1 
scale factor.
+                            0,  0,  1), mt, STRICT);
+    }
+
     /**
      * Tests {@link GridGeometry#createTransformTo(GridGeometry, PixelInCell)}.
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
index 15c32e6bda..1f5b3b8b2d 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
@@ -39,6 +39,8 @@ import javax.measure.Unit;
 import org.opengis.parameter.*;
 import org.opengis.util.ScopedName;
 import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
+import org.opengis.util.ControlledVocabulary;
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.operation.OperationMethod;
@@ -47,6 +49,7 @@ import org.apache.sis.measure.Range;
 import org.apache.sis.io.wkt.Colors;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.io.TabularFormat;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
@@ -105,7 +108,7 @@ import static 
org.apache.sis.util.collection.Containers.hashMapCapacity;
  * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.6
+ * @version 1.3
  * @since   0.4
  * @module
  */
@@ -692,7 +695,7 @@ public class ParameterFormat extends TabularFormat<Object> {
                 table.setCellAlignment(alignment);
                 final int length = row.values.size();
                 for (int i=0; i<length; i++) {
-                    Object value = row.values.get(i);
+                    final Object value = row.values.get(i);
                     if (value != null) {
                         if (i != 0) {
                             /*
@@ -715,13 +718,20 @@ public class ParameterFormat extends 
TabularFormat<Object> {
                          * + unit tuple.
                          */
                         final Format format = getFormat(value.getClass());
+                        final CharSequence text;
                         if (format != null) {
                             if (format instanceof NumberFormat && value 
instanceof Number) {
                                 configure((NumberFormat) format, 
Math.abs(((Number) value).doubleValue()));
                             }
-                            value = format.format(value, buffer, dummyFP);
+                            text = format.format(value, buffer, dummyFP);
+                        } else if (value instanceof ControlledVocabulary) {
+                            text = Types.getCodeTitle((ControlledVocabulary) 
value).toString(getLocale());
+                        } else if (value instanceof InternationalString) {
+                            text = ((InternationalString) 
value).toString(getLocale());
+                        } else {
+                            text = value.toString();
                         }
-                        table.append(value.toString());
+                        table.append(text);
                         buffer.setLength(0);
                         int pad = unitWidth;
                         final String unit = (String) row.units.get(i);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/package-info.java 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/package-info.java
index 24297290e0..1f12c19016 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/package-info.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/package-info.java
@@ -84,7 +84,7 @@
  * if the given value is not assignable to the expected class or is not inside 
the value domain.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.4
  * @module
  */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/package-info.java 
b/core/sis-utility/src/main/java/org/apache/sis/io/package-info.java
index 70ff2333a9..13d69245ce 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/package-info.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/package-info.java
@@ -41,7 +41,7 @@
  * Unicode supplementary characters}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.3
  * @module
  */

Reply via email to