You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/12/13 18:23:29 UTC

[sis] branch geoapi-4.0 updated (9c4a34851d -> 7490457b36)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 9c4a34851d Add `since` tag into `@Deprecated` annotations. Remove 4 deprecated methods that where not used anymore.
     new f83c833020 Post-merge cleanup.
     new 871d8fd9a3 Replace most of `org.apache.sis.storage.geotiff.Tags` by `javax.imageio.plugins.tiff`. The latter provides a more complete set of tags that we can use as labels in metadata.
     new 7490457b36 Fix an exception when GeoTIFF metadata contains rational numbers.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sis/gui/dataset/ExpandedFeature.java    |   2 +
 .../apache/sis/metadata/PropertyInformation.java   |   7 +-
 .../org/apache/sis/internal/util/Numerics.java     |  20 ++
 .../apache/sis/internal/geotiff/Compression.java   |  36 ++--
 .../org/apache/sis/internal/geotiff/Predictor.java |   8 +-
 .../apache/sis/internal/geotiff/package-info.java  |   2 +-
 .../org/apache/sis/storage/geotiff/DataCube.java   |   6 +-
 .../apache/sis/storage/geotiff/GeoKeysLoader.java  |  23 +--
 .../sis/storage/geotiff/ImageFileDirectory.java    | 222 +++++++++++----------
 .../sis/storage/geotiff/ImageMetadataBuilder.java  |  18 +-
 .../apache/sis/storage/geotiff/NativeMetadata.java |  26 +--
 .../java/org/apache/sis/storage/geotiff/Tags.java  | 192 ++++--------------
 .../java/org/apache/sis/storage/geotiff/Type.java  |  76 ++++---
 .../apache/sis/storage/geotiff/package-info.java   |   2 +-
 .../org/apache/sis/storage/geotiff/TagsTest.java   |  24 ++-
 .../apache/sis/test/suite/GeoTiffTestSuite.java    |   3 +-
 16 files changed, 315 insertions(+), 352 deletions(-)
 copy core/sis-feature/src/test/java/org/apache/sis/filter/XPathTest.java => storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TagsTest.java (60%)


[sis] 03/03: Fix an exception when GeoTIFF metadata contains rational numbers.

Posted by de...@apache.org.
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 7490457b360472dce2414f7094999687540ba1ba
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Dec 13 19:21:37 2022 +0100

    Fix an exception when GeoTIFF metadata contains rational numbers.
---
 .../org/apache/sis/internal/util/Numerics.java     | 20 ++++++
 .../java/org/apache/sis/storage/geotiff/Type.java  | 76 +++++++++++++---------
 2 files changed, 66 insertions(+), 30 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index a16dabd699..8886018831 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -29,6 +29,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.math.Statistics;
+import org.apache.sis.math.Fraction;
 
 import static java.lang.Math.min;
 import static java.lang.Math.max;
@@ -328,6 +329,25 @@ public final class Numerics extends Static {
         return (int) value;
     }
 
+    /**
+     * Returns the given fraction as a {@link Fraction} instance if possible,
+     * or as a {@link Double} approximation otherwise.
+     *
+     * @param  numerator    numerator of the fraction to return.
+     * @param  denominator  denominator of the fraction to return.
+     * @return the fraction as a {@link Fraction} or {@link Double} object.
+     */
+    public static Number fraction(long numerator, long denominator) {
+        final int simplify = Math.min(Long.numberOfTrailingZeros(numerator), Long.numberOfTrailingZeros(denominator));
+        final int num = (int) (numerator   >>= simplify);
+        final int den = (int) (denominator >>= simplify);
+        if (num == numerator && den == denominator) {
+            return new Fraction(num, den).unique();
+        } else {
+            return valueOf(numerator / (double) denominator);
+        }
+    }
+
     /**
      * If the given value is presents in the cache, returns the cached value.
      * Otherwise returns the given value as-is.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
index 8737f7e806..8da57cbcbd 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
@@ -37,7 +37,7 @@ import org.apache.sis.util.resources.Errors;
  * This enumeration rather match the Java primitive type names.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
  */
@@ -296,21 +296,16 @@ enum Type {
      * </ul>
      */
     RATIONAL(10, (2*Integer.BYTES), false) {
-        private Fraction readFraction(final ChannelDataInput input, final long count) throws IOException {
-            final Fraction value = new Fraction(input.readInt(), input.readInt());
-            for (long i=1; i<count; i++) {
-                ensureSingleton(value.doubleValue(), input.readInt() / (double) input.readInt(), count);
-            }
-            return value;
-        }
-
         @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
             return readFraction(input, count).doubleValue();
         }
 
-        /** Returns the value as a {@link Fraction}. */
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
+            return readFractions(input, count);
+        }
+
         @Override public Object readObject(final ChannelDataInput input, final long count) throws IOException {
-            return readFraction(input, count);
+            return (count == 1) ? readFraction(input, count) : super.readObject(input, count);
         }
     },
 
@@ -322,25 +317,16 @@ enum Type {
      * </ul>
      */
     URATIONAL(5, (2*Integer.BYTES), true) {
-        private Number readFraction(final ChannelDataInput input, final long count) throws IOException {
-            final long n  = input.readUnsignedInt();
-            final long d  = input.readUnsignedInt();
-            final int  ni = (int) n;
-            final int  di = (int) d;
-            final Number value = (ni == n && di == d) ? new Fraction(ni, di) : Double.valueOf(n / (double) d);
-            for (long i=1; i<count; i++) {
-                ensureSingleton(value.doubleValue(), input.readUnsignedInt() / (double) input.readUnsignedInt(), count);
-            }
-            return value;
-        }
-
         @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
             return readFraction(input, count).doubleValue();
         }
 
-        /** Returns the value as {@link Faction} if possible or {@link Double} otherwise. */
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
+            return readFractions(input, count);
+        }
+
         @Override public Object readObject(final ChannelDataInput input, final long count) throws IOException {
-            return readFraction(input, count);
+            return (count == 1) ? readFraction(input, count) : super.readObject(input, count);
         }
     },
 
@@ -451,10 +437,38 @@ enum Type {
     }
 
     /**
-     * Formats a rational number. This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
+     * Reads the next rational number. This is either a single value or a member of a vector.
+     */
+    private Number nextFraction(final ChannelDataInput input) throws IOException {
+        if (isUnsigned) {
+            return Numerics.fraction(input.readUnsignedInt(), input.readUnsignedInt());
+        } else {
+            return new Fraction(input.readInt(), input.readInt()).unique();
+        }
+    }
+
+    /**
+     * Reads an array of rational numbers.
+     * This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
      */
-    static String toString(final long numerator, final long denominator) {
-        return new StringBuilder().append(numerator).append('/').append(denominator).toString();
+    final Number[] readFractions(final ChannelDataInput input, int count) throws IOException {
+        final Number[] values = new Number[count];
+        for (int i=0; i<count; i++) {
+            values[i] = nextFraction(input);
+        }
+        return values;
+    }
+
+    /**
+     * Reads a rational number, making sure that the value is unique if repeated.
+     * This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
+     */
+    final Number readFraction(final ChannelDataInput input, final long count) throws IOException {
+        final Number value = nextFraction(input);
+        for (long i=1; i<count; i++) {
+            ensureSingleton(value.doubleValue(), nextFraction(input).doubleValue(), count);
+        }
+        return value;
     }
 
     /**
@@ -470,7 +484,7 @@ enum Type {
      * @param  count     the number of values to read.
      * @throws IllegalArgumentException if {@code count} does not have the expected value.
      */
-    static void ensureSingleton(final long previous, final long actual, final long count) {
+    private static void ensureSingleton(final long previous, final long actual, final long count) {
         if (previous != actual) {
             // Even if the methods did not expected an array in argument, we are conceptually reading an array.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
@@ -480,7 +494,7 @@ enum Type {
     /**
      * Same as {@link #ensureSingleton(long, long, long)} but with floating-point values.
      */
-    static void ensureSingleton(final double previous, final double actual, final long count) {
+    private static void ensureSingleton(final double previous, final double actual, final long count) {
         if (Double.doubleToLongBits(previous) != Double.doubleToLongBits(actual)) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
         }
@@ -638,6 +652,8 @@ enum Type {
 
     /**
      * Reads an arbitrary number of values as a Java array.
+     * This is usually (but not necessarily) an array of primitive type.
+     * It may be unsigned values packed in their signed counterpart.
      *
      * @param  input  the input from where to read the values.
      * @param  count  the amount of values.


[sis] 02/03: Replace most of `org.apache.sis.storage.geotiff.Tags` by `javax.imageio.plugins.tiff`. The latter provides a more complete set of tags that we can use as labels in metadata.

Posted by de...@apache.org.
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 871d8fd9a33739ae10d8716c30ad1e56a390b174
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Dec 13 18:05:28 2022 +0100

    Replace most of `org.apache.sis.storage.geotiff.Tags` by `javax.imageio.plugins.tiff`.
    The latter provides a more complete set of tags that we can use as labels in metadata.
---
 .../apache/sis/internal/geotiff/Compression.java   |  36 ++--
 .../org/apache/sis/internal/geotiff/Predictor.java |   8 +-
 .../apache/sis/internal/geotiff/package-info.java  |   2 +-
 .../org/apache/sis/storage/geotiff/DataCube.java   |   6 +-
 .../apache/sis/storage/geotiff/GeoKeysLoader.java  |  23 +--
 .../sis/storage/geotiff/ImageFileDirectory.java    | 222 +++++++++++----------
 .../sis/storage/geotiff/ImageMetadataBuilder.java  |  18 +-
 .../apache/sis/storage/geotiff/NativeMetadata.java |  26 +--
 .../java/org/apache/sis/storage/geotiff/Tags.java  | 192 ++++--------------
 .../apache/sis/storage/geotiff/package-info.java   |   2 +-
 .../org/apache/sis/storage/geotiff/TagsTest.java}  |  33 ++-
 .../apache/sis/test/suite/GeoTiffTestSuite.java    |   3 +-
 12 files changed, 255 insertions(+), 316 deletions(-)

diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Compression.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Compression.java
index e3d00695c9..384d6f9e51 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Compression.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Compression.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.geotiff;
 
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
+
 
 /**
  * Possible values for {@link org.apache.sis.storage.geotiff.Tags#Compression}.
@@ -30,7 +32,7 @@ package org.apache.sis.internal.geotiff;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
  */
@@ -43,7 +45,7 @@ public enum Compression {
      *   <li>Name in WCS response: "None"</li>
      * </ul>
      */
-    NONE(1),
+    NONE(COMPRESSION_NONE),
 
     /**
      * CCITT Group 3, 1-Dimensional Modified Huffman run length encoding.
@@ -52,7 +54,7 @@ public enum Compression {
      *   <li>Name in WCS response: "CCITTRLE"</li>
      * </ul>
      */
-    CCITTRLE(2),
+    CCITTRLE(COMPRESSION_CCITT_RLE),
 
     /**
      * PackBits compression, a simple byte-oriented run length scheme.
@@ -61,7 +63,7 @@ public enum Compression {
      *   <li>Name in WCS response: "PackBits"</li>
      * </ul>
      */
-    PACKBITS(32773),
+    PACKBITS(COMPRESSION_PACKBITS),
 
     // ---- End of baseline GeoTIFF. Remaining are extensions cited by OGC standard ----
 
@@ -72,7 +74,7 @@ public enum Compression {
      *   <li>Name in WCS response: "LZW"</li>
      * </ul>
      */
-    LZW(5),
+    LZW(COMPRESSION_LZW),
 
     /**
      * Deflate compression, like ZIP format. This is sometimes named {@code "ADOBE_DEFLATE"},
@@ -83,7 +85,7 @@ public enum Compression {
      *   <li>Other name:           "ADOBE_DEFLATE"</li>
      * </ul>
      */
-    DEFLATE(8),
+    DEFLATE(COMPRESSION_ZLIB),
 
     /**
      * JPEG compression.
@@ -93,12 +95,12 @@ public enum Compression {
      *   <li>Name of old JPEG:     "OJPEG" (code 6)</li>
      * </ul>
      */
-    JPEG(7),
+    JPEG(COMPRESSION_JPEG),
 
     // ---- Remaining are extension to both baseline and OGC standard ----
 
-    /** Unsupported. */ CCITTFAX3(3),
-    /** Unsupported. */ CCITTFAX4(4),
+    /** Unsupported. */ CCITTFAX3(COMPRESSION_CCITT_T_4),
+    /** Unsupported. */ CCITTFAX4(COMPRESSION_CCITT_T_6),
     /** Unsupported. */ NEXT(32766),
     /** Unsupported. */ CCITTRLEW(32771),
     /** Unsupported. */ THUNDERSCAN(32809),
@@ -139,14 +141,14 @@ public enum Compression {
      */
     public static Compression valueOf(final int code) {
         switch (code) {
-            case 1:     return NONE;
-            case 2:     return CCITTRLE;
-            case 5:     return LZW;
-            case 6:     // "old-style" JPEG, later overriden in Technical Notes 2.
-            case 7:     return JPEG;
-            case 8:
-            case 32946: return DEFLATE;
-            case 32773: return PACKBITS;
+            case 32946:                 // Fall through
+            case COMPRESSION_ZLIB:      return DEFLATE;
+            case COMPRESSION_OLD_JPEG:  // "old-style" JPEG, later overriden in Technical Notes 2.
+            case COMPRESSION_JPEG:      return JPEG;
+            case COMPRESSION_CCITT_RLE: return CCITTRLE;
+            case COMPRESSION_LZW:       return LZW;
+            case COMPRESSION_PACKBITS:  return PACKBITS;
+            case COMPRESSION_NONE:      return NONE;
             default: {
                 // Fallback for uncommon formats.
                 for (final Compression c : values()) {
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Predictor.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Predictor.java
index 7066cef3f9..43d8b9ebeb 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Predictor.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/Predictor.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.geotiff;
 
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
+
 
 /**
  * Possible values for {@link org.apache.sis.storage.geotiff.Tags#Predictor}.
@@ -23,7 +25,7 @@ package org.apache.sis.internal.geotiff;
  * before an encoding scheme is applied.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  * @module
  */
@@ -56,8 +58,8 @@ public enum Predictor {
      */
     public static Predictor valueOf(final int code) {
         switch (code) {
-            case 1:  return NONE;
-            case 2:  return HORIZONTAL;
+            case PREDICTOR_NONE: return NONE;
+            case PREDICTOR_HORIZONTAL_DIFFERENCING: return HORIZONTAL;
             case 3:  return FLOAT;
             default: return UNKNOWN;
         }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java
index 3c0a7251cc..ac3c05f342 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java
@@ -24,7 +24,7 @@
  * may change in incompatible ways in any future version without notice.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
  */
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index c7cbc41f52..6db58e1d26 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -36,6 +36,8 @@ import org.apache.sis.internal.storage.ResourceOnFileSystem;
 import org.apache.sis.internal.storage.StoreResource;
 import org.apache.sis.math.Vector;
 
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.TAG_COMPRESSION;
+
 
 /**
  * One or many GeoTIFF images packaged as a single resource.
@@ -47,7 +49,7 @@ import org.apache.sis.math.Vector;
  * as it may cause an infinite loop in {@code listeners.getLocale()} call.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   1.1
  * @module
  */
@@ -214,7 +216,7 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste
                 final Compression compression = getCompression();
                 if (compression == null) {
                     throw new DataStoreContentException(reader.resources().getString(
-                            Resources.Keys.MissingValue_2, Tags.name(Tags.Compression)));
+                            Resources.Keys.MissingValue_2, Tags.name((short) TAG_COMPRESSION)));
                 }
                 /*
                  * The `DataSubset` parent class is the most efficient but has many limitations
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeysLoader.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeysLoader.java
index abc728eba7..59f7bb95e8 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeysLoader.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeysLoader.java
@@ -21,15 +21,17 @@ import org.apache.sis.math.Vector;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.internal.geotiff.Resources;
 
+import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
+
 
 /**
  * Loads GeoTIFF keys in a hash map, but without performing any interpretation.
  * A {@code GeoKeysLoader} receives as inputs the values of the following TIFF tags:
  *
  * <ul>
- *   <li>{@link Tags#GeoKeyDirectory} — array of unsigned {@code short} values grouped into blocks of 4.</li>
- *   <li>{@link Tags#GeoDoubleParams} — array of {@double} values referenced by {@code GeoKeyDirectory} elements.</li>
- *   <li>{@link Tags#GeoAsciiParams}  — array of characters referenced by {@code GeoKeyDirectory} elements.</li>
+ *   <li>{@code GeoKeyDirectory} — array of unsigned {@code short} values grouped into blocks of 4.</li>
+ *   <li>{@code GeoDoubleParams} — array of {@double} values referenced by {@code GeoKeyDirectory} elements.</li>
+ *   <li>{@code GeoAsciiParams}  — array of characters referenced by {@code GeoKeyDirectory} elements.</li>
  * </ul>
  *
  * For example, consider the following values for the above-cited tags:
@@ -65,7 +67,7 @@ import org.apache.sis.internal.geotiff.Resources;
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   1.2
  * @module
  */
@@ -83,16 +85,12 @@ class GeoKeysLoader {
     /**
      * References the {@link GeoKeys} needed for building the Coordinate Reference System.
      * Cannot be null when invoking {@link #load(Map)}.
-     *
-     * @see Tags#GeoKeyDirectory
      */
     public Vector keyDirectory;
 
     /**
      * The numeric values referenced by the {@link #keyDirectory}.
      * Can be {@code null} if none.
-     *
-     * @see Tags#GeoDoubleParams
      */
     public Vector numericParameters;
 
@@ -100,7 +98,6 @@ class GeoKeysLoader {
      * The characters referenced by the {@link #keyDirectory}.
      * Can be {@code null} if none.
      *
-     * @see Tags#GeoAsciiParams
      * @see #setAsciiParameters(String[])
      */
     public String asciiParameters;
@@ -125,7 +122,7 @@ class GeoKeysLoader {
     }
 
     /**
-     * Sets the value of {@link #asciiParameters} from {@link Tags#GeoAsciiParams} value.
+     * Sets the value of {@link #asciiParameters} from {@code GeoAsciiParams} value.
      */
     final void setAsciiParameters(final String[] values) {
         switch (values.length) {
@@ -208,7 +205,7 @@ class GeoKeysLoader {
                  * the specification does not allocate a separated vector for them. We use the
                  * `int` type if needed for allowing storage of unsigned short values.
                  */
-                case Tags.GeoKeyDirectory & 0xFFFF: {
+                case TAG_GEO_KEY_DIRECTORY: {
                     if (valueOffset + count > keyDirectory.size()) {
                         missingValue(key);
                         continue;
@@ -231,7 +228,7 @@ class GeoKeysLoader {
                  * Values of type `double` are read from a separated vector, `numericParameters`.
                  * Result is stored in a Double wrapper or in an array of type 'double[]'.
                  */
-                case Tags.GeoDoubleParams & 0xFFFF: {
+                case TAG_GEO_DOUBLE_PARAMS: {
                     if (valueOffset + count > numberOfDoubles) {
                         missingValue(key);
                         continue;
@@ -254,7 +251,7 @@ class GeoKeysLoader {
                  * ASCII encoding use the pipe ('|') character as a replacement for the NUL character
                  * used in C/C++ programming languages. We need to omit those trailing characters.
                  */
-                case Tags.GeoAsciiParams & 0xFFFF: {
+                case TAG_GEO_ASCII_PARAMS: {
                     int upper = valueOffset + count;
                     if (upper > numberOfChars) {
                         missingValue(key);
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 2a8f14615e..9b77520cf4 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -59,6 +59,9 @@ import org.apache.sis.math.Vector;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.image.DataType;
 
+import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
+
 
 /**
  * An Image File Directory (FID) in a TIFF image.
@@ -72,7 +75,7 @@ import org.apache.sis.image.DataType;
  * @author  Johann Sorel (Geomatys)
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a>
  *
@@ -89,7 +92,7 @@ final class ImageFileDirectory extends DataCube {
 
     /**
      * Possible value for {@link #sampleFormat} specifying how to interpret each data sample in a pixel.
-     * Those values are not necessarily the same than the ones documented in {@link Tags#SampleFormat}.
+     * Those values are not necessarily the same than the ones documented in {@code TAG_SAMPLE_FORMAT}.
      * Default value is {@link #UNSIGNED}.
      */
     private static final byte SIGNED = 1, UNSIGNED = 0, FLOAT = 3;
@@ -537,33 +540,33 @@ final class ImageFileDirectory extends DataCube {
              * 1 = Chunky format. The component values for each pixel are stored contiguously (for example RGBRGBRGB).
              * 2 = Planar format. For example, one plane of Red components, one plane of Green and one plane if Blue.
              */
-            case Tags.PlanarConfiguration: {
+            case TAG_PLANAR_CONFIGURATION: {
                 final int value = type.readInt(input(), count);
                 switch (value) {
-                    case 1:  isPlanar = false; break;
-                    case 2:  isPlanar = true;  break;
-                    default: return value;                  // Cause a warning to be reported by the caller.
+                    case PLANAR_CONFIGURATION_CHUNKY: isPlanar = false; break;
+                    case PLANAR_CONFIGURATION_PLANAR: isPlanar = true;  break;
+                    default: return value;      // Cause a warning to be reported by the caller.
                 }
                 break;
             }
             /*
              * The number of columns in the image, i.e., the number of pixels per row.
              */
-            case Tags.ImageWidth: {
+            case TAG_IMAGE_WIDTH: {
                 imageWidth = type.readUnsignedLong(input(), count);
                 break;
             }
             /*
              * The number of rows of pixels in the image.
              */
-            case Tags.ImageLength: {
+            case TAG_IMAGE_LENGTH: {
                 imageHeight = type.readUnsignedLong(input(), count);
                 break;
             }
             /*
              * The tile width in pixels. This is the number of columns in each tile.
              */
-            case Tags.TileWidth: {
+            case TAG_TILE_WIDTH: {
                 setTileTagFamily(TILE);
                 tileWidth = type.readInt(input(), count);
                 break;
@@ -571,7 +574,7 @@ final class ImageFileDirectory extends DataCube {
             /*
              * The tile length (height) in pixels. This is the number of rows in each tile.
              */
-            case Tags.TileLength: {
+            case TAG_TILE_LENGTH: {
                 setTileTagFamily(TILE);
                 tileHeight = type.readInt(input(), count);
                 break;
@@ -580,7 +583,7 @@ final class ImageFileDirectory extends DataCube {
              * The number of rows per strip. This is considered by SIS as a special kind of tiles.
              * From this point of view, TileLength = RowPerStrip and TileWidth = ImageWidth.
              */
-            case Tags.RowsPerStrip: {
+            case TAG_ROWS_PER_STRIP: {
                 setTileTagFamily(STRIP);
                 tileHeight = type.readInt(input(), count);
                 break;
@@ -588,7 +591,7 @@ final class ImageFileDirectory extends DataCube {
             /*
              * The tile length (height) in pixels. This is the number of rows in each tile.
              */
-            case Tags.TileOffsets: {
+            case TAG_TILE_OFFSETS: {
                 setTileTagFamily(TILE);
                 tileOffsets = type.readVector(input(), count);
                 break;
@@ -597,7 +600,7 @@ final class ImageFileDirectory extends DataCube {
              * For each strip, the byte offset of that strip relative to the beginning of the TIFF file.
              * In Apache SIS implementation, strips are considered as a special kind of tiles.
              */
-            case Tags.StripOffsets: {
+            case TAG_STRIP_OFFSETS: {
                 setTileTagFamily(STRIP);
                 tileOffsets = type.readVector(input(), count);
                 break;
@@ -605,7 +608,7 @@ final class ImageFileDirectory extends DataCube {
             /*
              * The tile width in pixels. This is the number of columns in each tile.
              */
-            case Tags.TileByteCounts: {
+            case TAG_TILE_BYTE_COUNTS: {
                 setTileTagFamily(TILE);
                 tileByteCounts = type.readVector(input(), count);
                 break;
@@ -614,7 +617,7 @@ final class ImageFileDirectory extends DataCube {
              * For each strip, the number of bytes in the strip after compression.
              * In Apache SIS implementation, strips are considered as a special kind of tiles.
              */
-            case Tags.StripByteCounts: {
+            case TAG_STRIP_BYTE_COUNTS: {
                 setTileTagFamily(STRIP);
                 tileByteCounts = type.readVector(input(), count);
                 break;
@@ -622,12 +625,12 @@ final class ImageFileDirectory extends DataCube {
             /*
              * Legacy tags for JPEG formats, to be also interpreted as a tile.
              */
-            case Tags.JPEGInterchangeFormat: {
+            case TAG_JPEG_INTERCHANGE_FORMAT: {
                 setTileTagFamily(JPEG);
                 tileOffsets = type.readVector(input(), count);
                 break;
             }
-            case Tags.JPEGInterchangeFormatLength: {
+            case TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: {
                 setTileTagFamily(JPEG);
                 tileByteCounts = type.readVector(input(), count);
                 break;
@@ -643,7 +646,7 @@ final class ImageFileDirectory extends DataCube {
             /*
              * Compression scheme used on the image data.
              */
-            case Tags.Compression: {
+            case TAG_COMPRESSION: {
                 final int value = type.readInt(input(), count);
                 compression = Compression.valueOf(value);
                 if (compression == Compression.UNKNOWN) {
@@ -655,7 +658,7 @@ final class ImageFileDirectory extends DataCube {
              * Mathematical operator that is applied to the image data before an encoding scheme is applied.
              * 1=none, 2=horizontal differencing. More values may be added in the future.
              */
-            case Tags.Predictor: {
+            case TAG_PREDICTOR: {
                 final int value = type.readInt(input(), count);
                 predictor = Predictor.valueOf(value);
                 if (predictor == Predictor.UNKNOWN) {
@@ -667,12 +670,12 @@ final class ImageFileDirectory extends DataCube {
              * The logical order of bits within a byte. If this value is 2, then
              * bits order shall be reversed in every bytes before decompression.
              */
-            case Tags.FillOrder: {
+            case TAG_FILL_ORDER: {
                 final int value = type.readInt(input(), count);
                 switch (value) {
-                    case 1: isBitOrderReversed = false; break;
-                    case 2: isBitOrderReversed = true;  break;
-                    default: return value;                  // Cause a warning to be reported by the caller.
+                    case FILL_ORDER_LEFT_TO_RIGHT: isBitOrderReversed = false; break;
+                    case FILL_ORDER_RIGHT_TO_LEFT: isBitOrderReversed = true;  break;
+                    default: return value;      // Cause a warning to be reported by the caller.
                 }
                 break;
             }
@@ -680,14 +683,17 @@ final class ImageFileDirectory extends DataCube {
              * How to interpret each data sample in a pixel. The size of data samples is still
              * specified by the BitsPerSample field.
              */
-            case Tags.SampleFormat: {
+            case TAG_SAMPLE_FORMAT: {
                 final int value = type.readInt(input(), count);
                 switch (value) {
-                    default: return value;                          // Warning to be reported by the caller.
-                    case 1: sampleFormat = UNSIGNED; break;         // Unsigned integer data (default).
-                    case 2: sampleFormat = SIGNED;   break;         // Two’s complement signed integer data.
-                    case 3: sampleFormat = FLOAT;    break;         // IEEE floating point data.
-                    case 4: warning(Level.WARNING, Resources.Keys.UndefinedDataFormat_1, filename()); break;
+                    default: return value;      // Warning to be reported by the caller.
+                    case SAMPLE_FORMAT_UNSIGNED_INTEGER: sampleFormat = UNSIGNED; break;
+                    case SAMPLE_FORMAT_SIGNED_INTEGER:   sampleFormat = SIGNED;   break;
+                    case SAMPLE_FORMAT_FLOATING_POINT:   sampleFormat = FLOAT;    break;
+                    case SAMPLE_FORMAT_UNDEFINED: {
+                        warning(Level.WARNING, Resources.Keys.UndefinedDataFormat_1, filename());
+                        break;
+                    }
                 }
                 break;
             }
@@ -696,7 +702,7 @@ final class ImageFileDirectory extends DataCube {
              * pixel (e.g. 3 for RGB values). Typically, all components have the same number of bits.
              * But the TIFF specification allows different values.
              */
-            case Tags.BitsPerSample: {
+            case TAG_BITS_PER_SAMPLE: {
                 final Vector values = type.readVector(input(), count);
                 /*
                  * The current implementation requires that all `bitsPerSample` elements have the same value.
@@ -717,7 +723,7 @@ final class ImageFileDirectory extends DataCube {
              * The number of components per pixel. Usually 1 for bilevel, grayscale, and palette-color images,
              * and 3 for RGB images. Default value is 1.
              */
-            case Tags.SamplesPerPixel: {
+            case TAG_SAMPLES_PER_PIXEL: {
                 samplesPerPixel = type.readShort(input(), count);
                 break;
             }
@@ -727,7 +733,7 @@ final class ImageFileDirectory extends DataCube {
              * image normally has SamplesPerPixel=3. If SamplesPerPixel is greater than 3, then the ExtraSamples field
              * describes the meaning of the extra samples. It may be an alpha channel, but not necessarily.
              */
-            case Tags.ExtraSamples: {
+            case TAG_EXTRA_SAMPLES: {
                 extraSamples = type.readVector(input(), count);
                 break;
             }
@@ -747,7 +753,7 @@ final class ImageFileDirectory extends DataCube {
              * 3 = Palette color. The value of the component is used as an index into the RGB values of the ColorMap.
              * 4 = Transparency Mask. Defines an irregularly shaped region of another image in the same TIFF file.
              */
-            case Tags.PhotometricInterpretation: {
+            case TAG_PHOTOMETRIC_INTERPRETATION: {
                 final short value = type.readShort(input(), count);
                 if (value < 0 || value > Byte.MAX_VALUE) return value;
                 photometricInterpretation = (byte) value;
@@ -760,7 +766,7 @@ final class ImageFileDirectory extends DataCube {
              * The number of values for each color is (1 << BitsPerSample) where 0 represents the minimum intensity
              * (black is 0,0,0) and 65535 represents the maximum intensity.
              */
-            case Tags.ColorMap: {
+            case TAG_COLOR_MAP: {
                 colorMap = type.readVector(input(), count);
                 break;
             }
@@ -768,8 +774,8 @@ final class ImageFileDirectory extends DataCube {
              * The minimum component value used. MinSampleValue is a single value that apply to all bands
              * while SMinSampleValue lists separated values for each band. Default is 0.
              */
-            case Tags.MinSampleValue:
-            case Tags.SMinSampleValue: {
+            case TAG_MIN_SAMPLE_VALUE:
+            case TAG_S_MIN_SAMPLE_VALUE: {
                 minValues = extremum(minValues, type.readVector(input(), count), false);
                 isMinSpecified = true;
                 break;
@@ -779,8 +785,8 @@ final class ImageFileDirectory extends DataCube {
              * This field is for statistical purposes and should not to be used to affect the
              * visual appearance of an image, unless a map styling is applied.
              */
-            case Tags.MaxSampleValue:
-            case Tags.SMaxSampleValue: {
+            case TAG_MAX_SAMPLE_VALUE:
+            case TAG_S_MAX_SAMPLE_VALUE: {
                 maxValues = extremum(maxValues, type.readVector(input(), count), true);
                 isMaxSpecified = true;
                 break;
@@ -801,7 +807,7 @@ final class ImageFileDirectory extends DataCube {
              * Bit 2 is 1 if the image defines a transparency mask for another image in this TIFF file (see PhotometricInterpretation).
              * Bit 4 indicates MRC imaging model as described in ITU-T recommendation T.44 [T.44] (See ImageLayer tag) - RFC 2301.
              */
-            case Tags.NewSubfileType: {
+            case TAG_NEW_SUBFILE_TYPE: {
                 subfileType = type.readInt(input(), count);
                 break;
             }
@@ -811,13 +817,13 @@ final class ImageFileDirectory extends DataCube {
              * 2 = reduced-resolution image data
              * 3 = a single page of a multi-page image (see PageNumber).
              */
-            case Tags.SubfileType: {
+            case TAG_SUBFILE_TYPE: {
                 final int value = type.readInt(input(), count);
                 switch (value) {
-                    default: return value;                          // Warning to be reported by the caller.
-                    case 1:  subfileType &= ~1; break;
-                    case 2:  subfileType |=  1; break;
-                    case 3:  subfileType |=  2; break;
+                    default: return value;                // Warning to be reported by the caller.
+                    case SUBFILE_TYPE_FULL_RESOLUTION:    subfileType &= ~NEW_SUBFILE_TYPE_REDUCED_RESOLUTION; break;
+                    case SUBFILE_TYPE_REDUCED_RESOLUTION: subfileType |=  NEW_SUBFILE_TYPE_REDUCED_RESOLUTION; break;
+                    case SUBFILE_TYPE_SINGLE_PAGE:        subfileType |=  NEW_SUBFILE_TYPE_SINGLE_PAGE;        break;
                 }
                 break;
             }
@@ -833,14 +839,14 @@ final class ImageFileDirectory extends DataCube {
              * An array of unsigned SHORT values, which are primarily grouped into blocks of 4.
              * The first 4 values are special, and contain GeoKey directory header information.
              */
-            case Tags.GeoKeyDirectory: {
+            case (short) TAG_GEO_KEY_DIRECTORY: {
                 referencing().keyDirectory = type.readVector(input(), count);
                 break;
             }
             /*
              * Stores all of the `double` valued GeoKeys, referenced by the GeoKeyDirectory.
              */
-            case Tags.GeoDoubleParams: {
+            case (short) TAG_GEO_DOUBLE_PARAMS: {
                 referencing().numericParameters = type.readVector(input(), count);
                 break;
             }
@@ -849,7 +855,7 @@ final class ImageFileDirectory extends DataCube {
              * which will be splitted by CRSBuilder, but we allow an arbitrary amount as a paranoiac check.
              * Note that TIFF files use 0 as the end delimiter in strings (C/C++ convention).
              */
-            case Tags.GeoAsciiParams: {
+            case (short) TAG_GEO_ASCII_PARAMS: {
                 referencing().setAsciiParameters(type.readString(input(), count, encoding()));
                 break;
             }
@@ -857,16 +863,19 @@ final class ImageFileDirectory extends DataCube {
              * The orientation of the image with respect to the rows and columns.
              * This is an integer numeroted from 1 to 7 inclusive (see TIFF specification for meaning).
              */
-            case Tags.Orientation: {
+            case TAG_ORIENTATION: {
                 // TODO
                 break;
             }
             /*
-             * The "grid to CRS" conversion as a 4×4 matrix in row-major fashion. The third matrix row and
-             * the third matrix column may contain only zero values; this block does not reduce the number
-             * of dimensions from 3 to 2.
+             * Specifies the "grid to CRS" conversion between the raster space and the model space.
+             * If specified, the tag shall have the 16 values of a 4×4 matrix in row-major fashion.
+             * The last matrix row (i.e. the last 4 values) should be [0 0 0 1].
+             * The row before should be [0 0 0 0] if the conversion is two-dimensional.
+             * This block does not reduce the number of dimensions from 3 to 2.
+             * Only one of `ModelPixelScaleTag` and `ModelTransformation` should be used.
              */
-            case Tags.ModelTransformation: {
+            case (short) TAG_MODEL_TRANSFORMATION: {
                 final Vector m = type.readVector(input(), count);
                 final int n;
                 switch (m.size()) {
@@ -880,10 +889,20 @@ final class ImageFileDirectory extends DataCube {
                 break;
             }
             /*
-             * The "grid to CRS" conversion with only the scale factor specified. This block sets the
-             * translation column to NaN, meaning that it will need to be computed from the tie point.
+             * A vector of 3 floating-point values defining the "grid to CRS" conversion without rotation.
+             * The conversion is defined as below, when (I,J,K,X,Y,Z) is the tie point singleton record:
+             *
+             * ┌                       ┐
+             * │   Sx   0    0    Tx   │       Tx = X - I/Sx
+             * │   0   -Sy   0    Ty   │       Ty = Y + J/Sy
+             * │   0    0    Sz   Tz   │       Tz = Z - K/Sz  (if not 0)
+             * │   0    0    0    1    │
+             * └                       ┘
+             *
+             * This block sets the translation column to NaN, meaning that it will need to be computed from
+             * the tie point. Only one of `ModelPixelScaleTag` and `ModelTransformation` should be used.
              */
-            case Tags.ModelPixelScaleTag: {
+            case (short) TAG_MODEL_PIXEL_SCALE: {
                 final Vector m = type.readVector(input(), count);
                 final int size = m.size();
                 if (size < 2 || size > 3) {     // Length should be exactly 3, but we make this reader tolerant.
@@ -894,8 +913,9 @@ final class ImageFileDirectory extends DataCube {
             }
             /*
              * The mapping from pixel coordinates to CRS coordinates as a sequence of (I,J,K, X,Y,Z) records.
+             * This tag is also known as `Georeference`.
              */
-            case Tags.ModelTiePoints: {
+            case (short) TAG_MODEL_TIE_POINT: {
                 referencing().modelTiePoints = type.readVector(input(), count);
                 break;
             }
@@ -913,7 +933,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/series/name
              */
-            case Tags.DocumentName: {
+            case TAG_DOCUMENT_NAME: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addSeries(value);
                 }
@@ -924,7 +944,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/series/page
              */
-            case Tags.PageName: {
+            case TAG_PAGE_NAME: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addPage(value);
                 }
@@ -937,7 +957,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/series/page
              */
-            case Tags.PageNumber: {
+            case TAG_PAGE_NUMBER: {
                 final Vector v = type.readVector(input(), count);
                 int p = 0, n = 0;
                 switch (v.size()) {
@@ -954,7 +974,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/title
              */
-            case Tags.ImageDescription: {
+            case TAG_IMAGE_DESCRIPTION: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addTitle(Strings.singleLine(" ", value));
                 }
@@ -966,7 +986,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/party/name
              */
-            case Tags.Artist: {
+            case TAG_ARTIST: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addAuthor(value);
                 }
@@ -978,7 +998,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/resourceConstraint
              */
-            case Tags.Copyright: {
+            case (short) TAG_COPYRIGHT: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.parseLegalNotice(value);
                 }
@@ -989,7 +1009,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/identificationInfo/citation/date
              */
-            case Tags.DateTime: {
+            case TAG_DATE_TIME: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addCitationDate(reader.getDateFormat().parse(value),
                             DateType.CREATION, MetadataBuilder.Scope.RESOURCE);
@@ -1001,7 +1021,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/resourceLineage/processStep/processingInformation/procedureDescription
              */
-            case Tags.HostComputer: {
+            case TAG_HOST_COMPUTER: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addHostComputer(value);
                 }
@@ -1012,7 +1032,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/resourceLineage/processStep/processingInformation/softwareReference/title
              */
-            case Tags.Software: {
+            case TAG_SOFTWARE: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addSoftwareReference(value);
                 }
@@ -1022,7 +1042,7 @@ final class ImageFileDirectory extends DataCube {
              * Manufacturer of the scanner, video digitizer, or other type of equipment used to generate the image.
              * Synthetic images should not include this field.
              */
-            case Tags.Make: {
+            case TAG_MAKE: {
                 // TODO: is Instrument.citation.citedResponsibleParty.party.name an appropriate place?
                 // what would be the citation title? A copy of Tags.Model?
                 break;
@@ -1033,7 +1053,7 @@ final class ImageFileDirectory extends DataCube {
              *
              * Destination: metadata/acquisitionInformation/platform/instrument/identifier
              */
-            case Tags.Model: {
+            case TAG_MODEL: {
                 for (final String value : type.readString(input(), count, encoding())) {
                     metadata.addInstrument(null, value);
                 }
@@ -1042,8 +1062,8 @@ final class ImageFileDirectory extends DataCube {
             /*
              * The number of pixels per ResolutionUnit in the ImageWidth or ImageHeight direction.
              */
-            case Tags.XResolution:
-            case Tags.YResolution: {
+            case TAG_X_RESOLUTION:
+            case TAG_Y_RESOLUTION: {
                 metadata.setResolution(type.readDouble(input(), count));
                 break;
             }
@@ -1054,7 +1074,7 @@ final class ImageFileDirectory extends DataCube {
              *   2 = Inch (default).
              *   3 = Centimeter.
              */
-            case Tags.ResolutionUnit: {
+            case TAG_RESOLUTION_UNIT: {
                 return metadata.setResolutionUnit(type.readInt(input(), count));
                 // Non-null return value cause a warning to be reported by the caller.
             }
@@ -1066,7 +1086,7 @@ final class ImageFileDirectory extends DataCube {
              *   2 = An ordered dither or halftone technique has been applied to the image data.
              *   3 = A randomized process such as error diffusion has been applied to the image data.
              */
-            case Tags.Threshholding: {
+            case TAG_THRESHHOLDING: {
                 return metadata.setThreshholding(type.readShort(input(), count));
                 // Non-null return value cause a warning to be reported by the caller.
             }
@@ -1074,9 +1094,9 @@ final class ImageFileDirectory extends DataCube {
              * The width and height of the dithering or halftoning matrix used to create
              * a dithered or halftoned bilevel file. Meaningful only if Threshholding = 2.
              */
-            case Tags.CellWidth:
-            case Tags.CellLength: {
-                metadata.setCellSize(type.readShort(input(), count), tag == Tags.CellWidth);
+            case TAG_CELL_WIDTH:
+            case TAG_CELL_LENGTH: {
+                metadata.setCellSize(type.readShort(input(), count), tag == TAG_CELL_WIDTH);
                 break;
             }
 
@@ -1090,14 +1110,14 @@ final class ImageFileDirectory extends DataCube {
              * For each string of contiguous unused bytes in a TIFF file, the number of bytes and the byte offset
              * in the string. Those tags are deprecated and do not need to be supported.
              */
-            case Tags.FreeByteCounts:
-            case Tags.FreeOffsets:
+            case TAG_FREE_BYTE_COUNTS:
+            case TAG_FREE_OFFSETS:
             /*
              * For grayscale data, the optical density of each possible pixel value, plus the precision of that
              * information. This is ignored by most TIFF readers.
              */
-            case Tags.GrayResponseCurve:
-            case Tags.GrayResponseUnit: {
+            case TAG_GRAY_RESPONSE_CURVE:
+            case TAG_GRAY_RESPONSE_UNIT: {
                 warning(Level.FINE, Resources.Keys.IgnoredTag_1, Tags.name(tag));
                 break;
             }
@@ -1214,21 +1234,21 @@ final class ImageFileDirectory extends DataCube {
      */
     final boolean validateMandatoryTags() throws DataStoreContentException {
         if (isValidated) return false;
-        if (imageWidth  < 0) throw missingTag(Tags.ImageWidth);
-        if (imageHeight < 0) throw missingTag(Tags.ImageLength);
+        if (imageWidth  < 0) throw missingTag((short) TAG_IMAGE_WIDTH);
+        if (imageHeight < 0) throw missingTag((short) TAG_IMAGE_LENGTH);
         final short offsetsTag, byteCountsTag;
         switch (tileTagFamily) {
             case JPEG:                      // Handled as strips.
             case STRIP: {
                 if (tileWidth  < 0) tileWidth  = Math.toIntExact(imageWidth);
                 if (tileHeight < 0) tileHeight = Math.toIntExact(imageHeight);
-                offsetsTag    = Tags.StripOffsets;
-                byteCountsTag = Tags.StripByteCounts;
+                offsetsTag    = TAG_STRIP_OFFSETS;
+                byteCountsTag = TAG_STRIP_BYTE_COUNTS;
                 break;
             }
             case TILE:  {
-                offsetsTag    = Tags.TileOffsets;
-                byteCountsTag = Tags.TileByteCounts;
+                offsetsTag    = TAG_TILE_OFFSETS;
+                byteCountsTag = TAG_TILE_BYTE_COUNTS;
                 break;
             }
             default: {
@@ -1241,14 +1261,14 @@ final class ImageFileDirectory extends DataCube {
         }
         if (samplesPerPixel == 0) {
             samplesPerPixel = 1;
-            missingTag(Tags.SamplesPerPixel, 1, false, false);
+            missingTag((short) TAG_SAMPLES_PER_PIXEL, 1, false, false);
         }
         if (bitsPerSample == 0) {
             bitsPerSample = 1;
-            missingTag(Tags.BitsPerSample, 1, false, false);
+            missingTag((short) TAG_BITS_PER_SAMPLE, 1, false, false);
         }
         if (colorMap != null) {
-            ensureSameLength(Tags.ColorMap, Tags.BitsPerSample, colorMap.size(),  3 * (1 << bitsPerSample));
+            ensureSameLength((short) TAG_COLOR_MAP, (short) TAG_BITS_PER_SAMPLE, colorMap.size(),  3 * (1 << bitsPerSample));
         }
         if (sampleFormat != FLOAT) {
             long minValue, maxValue;
@@ -1286,12 +1306,12 @@ final class ImageFileDirectory extends DataCube {
             }
             case 0b0001: {          // Compute missing tile width.
                 tileWidth = computeTileSize(tileHeight);
-                missingTag(Tags.TileWidth, tileWidth, true, true);
+                missingTag((short) TAG_TILE_WIDTH, tileWidth, true, true);
                 break;
             }
             case 0b0010: {          // Compute missing tile height.
                 tileHeight = computeTileSize(tileWidth);
-                missingTag(Tags.TileLength, tileHeight, true, true);
+                missingTag((short) TAG_TILE_LENGTH, tileHeight, true, true);
                 break;
             }
             case 0b0100: {          // Compute missing tile byte count in uncompressed case.
@@ -1308,8 +1328,8 @@ final class ImageFileDirectory extends DataCube {
             default: {
                 final short tag;
                 switch (Integer.lowestOneBit(missing)) {
-                    case 0b0001: tag = Tags.TileWidth;  break;
-                    case 0b0010: tag = Tags.TileLength; break;
+                    case 0b0001: tag = TAG_TILE_WIDTH;  break;
+                    case 0b0010: tag = TAG_TILE_LENGTH; break;
                     default:     tag = byteCountsTag;   break;
                 }
                 throw missingTag(tag);
@@ -1334,7 +1354,7 @@ final class ImageFileDirectory extends DataCube {
          * the translation terms now.
          */
         if (referencing != null && !referencing.validateMandatoryTags()) {
-            throw missingTag(Tags.ModelTiePoints);
+            throw missingTag((short) TAG_MODEL_TIE_POINT);
         }
         isValidated = true;
         return true;
@@ -1405,7 +1425,7 @@ final class ImageFileDirectory extends DataCube {
      * of another image in this TIFF file.
      */
     final boolean isReducedResolution() {
-        return (subfileType & 1) != 0;
+        return (subfileType & NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) != 0;
     }
 
     /**
@@ -1612,17 +1632,17 @@ final class ImageFileDirectory extends DataCube {
             short missing = 0;              // Non-zero if there is a warning about missing information.
             switch (photometricInterpretation) {
                 default: {                  // For any unrecognized code, fallback on grayscale with 0 as black.
-                    unsupportedTagValue(Tags.PhotometricInterpretation, photometricInterpretation);
+                    unsupportedTagValue((short) TAG_PHOTOMETRIC_INTERPRETATION, photometricInterpretation);
                     break;
                 }
                 case -1: {
-                    missing = Tags.PhotometricInterpretation;
+                    missing = TAG_PHOTOMETRIC_INTERPRETATION;
                     break;
                 }
-                case  0:                   // WhiteIsZero: 0 is imaged as white.
-                case  1: {                 // BlackIsZero: 0 is imaged as black.
+                case  PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO:
+                case  PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO: {
                     final Color[] colors = {Color.BLACK, Color.WHITE};
-                    if (photometricInterpretation == 0) {
+                    if (photometricInterpretation == PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
                         ArraysExt.swap(colors, 0, 1);
                     }
                     double min = 0;
@@ -1636,7 +1656,7 @@ final class ImageFileDirectory extends DataCube {
                     colorModel = ColorModelFactory.createColorScale(dataType, samplesPerPixel, visibleBand, min, max, colors);
                     break;
                 }
-                case 2: {                   // RGB: (0,0,0) is black and (255,255,255) is white.
+                case PHOTOMETRIC_INTERPRETATION_RGB: {
                     final int numBands = sm.getNumBands();
                     if (numBands < 3 || numBands > 4) {
                         throw new DataStoreContentException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "numBands", numBands));
@@ -1646,9 +1666,9 @@ final class ImageFileDirectory extends DataCube {
                     colorModel = ColorModelFactory.createRGB(bitsPerSample, packed, hasAlpha);
                     break;
                 }
-                case  3: {                  // PaletteColor
+                case PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR: {
                     if (colorMap == null) {
-                        missing = Tags.ColorMap;
+                        missing = TAG_COLOR_MAP;
                         break;
                     }
                     int gi = colorMap.size() / 3;
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
index 157a2d8405..9c2addcfd5 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageMetadataBuilder.java
@@ -27,6 +27,8 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.measure.Units;
 
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
+
 
 /**
  * A temporary object for building the metadata for a single GeoTIFF image.
@@ -39,7 +41,7 @@ import org.apache.sis.measure.Units;
  * discard them (which save a little bit of space) when no longer needed.</div>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   1.2
  * @module
  */
@@ -98,10 +100,16 @@ final class ImageMetadataBuilder extends MetadataBuilder {
     @SuppressWarnings("fallthrough")
     Integer setThreshholding(final int value) {
         switch (value) {
-            default: return value;                              // Cause a warning to be reported by the caller.
-            case 2:  if ((cellWidth & cellHeight) >= 0) break;  // Exit if at least one value is positive, else fallthrough.
-            case 1:  // Fall through
-            case 3:  cellWidth = cellHeight = (short) -value; break;
+            default: return value;                      // Cause a warning to be reported by the caller.
+            case THRESHHOLDING_ORDERED_DITHER: {
+                if ((cellWidth & cellHeight) >= 0) break;
+                // Exit if at least one value is positive, else fallthrough.
+            }
+            case THRESHHOLDING_RANDOMIZED_DITHER:       // Fall through
+            case THRESHHOLDING_NONE:  {
+                cellWidth = cellHeight = (short) -value;
+                break;
+            }
         }
         return null;
     }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/NativeMetadata.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/NativeMetadata.java
index 3a4e3ef376..ab71e8017c 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/NativeMetadata.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/NativeMetadata.java
@@ -34,6 +34,8 @@ import org.apache.sis.internal.geotiff.Compression;
 import org.apache.sis.internal.geotiff.Predictor;
 
 import static java.lang.Math.addExact;
+import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
 
 
 /**
@@ -48,7 +50,7 @@ import static java.lang.Math.addExact;
  * <p>This class is thread-safe if the user does not try to write in the tree.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   1.2
  * @module
  */
@@ -82,8 +84,6 @@ final class NativeMetadata extends GeoKeysLoader {
 
     /**
      * The node for GeoKeys, or {@code null} if none.
-     *
-     * @see Tags#GeoKeyDirectory
      */
     private TreeTable.Node geoNode;
 
@@ -141,10 +141,10 @@ final class NativeMetadata extends GeoKeysLoader {
                      * This switch is only about tags to skip; special handlings of some tags are done later.
                      */
                     switch (tag) {
-                        case Tags.TileOffsets:
-                        case Tags.StripOffsets:
-                        case Tags.TileByteCounts:
-                        case Tags.StripByteCounts: visible = false; break;
+                        case TAG_TILE_OFFSETS:
+                        case TAG_STRIP_OFFSETS:
+                        case TAG_TILE_BYTE_COUNTS:
+                        case TAG_STRIP_BYTE_COUNTS: visible = false; break;
                         default: visible = (size != 0); break;
                     }
                     if (visible) {
@@ -159,18 +159,18 @@ final class NativeMetadata extends GeoKeysLoader {
                         Object value = null;
                         XMLMetadata children = null;
                         switch (tag) {
-                            case Tags.GeoKeyDirectory: {
+                            case (short) TAG_GEO_KEY_DIRECTORY: {
                                 writeGeoKeys();             // Flush previous keys if any (should never happen).
                                 keyDirectory = type.readVector(input, count);
                                 value = "GeoTIFF";
                                 break;
                             }
-                            case Tags.GeoDoubleParams: {
+                            case (short) TAG_GEO_DOUBLE_PARAMS: {
                                 numericParameters = type.readVector(input, count);
                                 visible = false;
                                 break;
                             }
-                            case Tags.GeoAsciiParams: {
+                            case (short) TAG_GEO_ASCII_PARAMS: {
                                 setAsciiParameters(type.readString(input, count, reader.store.encoding));
                                 visible = false;
                                 break;
@@ -199,8 +199,8 @@ final class NativeMetadata extends GeoKeysLoader {
                                  * an enumeration.
                                  */
                                 switch (tag) {
-                                    case Tags.Compression: value = toString(value, Compression::valueOf, Compression.UNKNOWN); break;
-                                    case Tags.Predictor:   value = toString(value,   Predictor::valueOf,   Predictor.UNKNOWN); break;
+                                    case TAG_COMPRESSION: value = toString(value, Compression::valueOf, Compression.UNKNOWN); break;
+                                    case TAG_PREDICTOR:   value = toString(value,   Predictor::valueOf,   Predictor.UNKNOWN); break;
                                 }
                             }
                         }
@@ -215,7 +215,7 @@ final class NativeMetadata extends GeoKeysLoader {
                                 node.setValue(VALUE, value);
                             }
                             node.setValue(CODE, Short.toUnsignedInt(tag));
-                            if (tag == Tags.GeoKeyDirectory) {
+                            if (tag == (short) TAG_GEO_KEY_DIRECTORY) {
                                 geoNode = node;
                             }
                         }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java
index c0a47affc5..e3d2d2fe67 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java
@@ -17,121 +17,48 @@
 package org.apache.sis.storage.geotiff;
 
 import java.lang.reflect.Field;
+import java.util.function.Supplier;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
 
 
 /**
  * Numerical values of GeoTIFF tags, as <strong>unsigned</strong> short integers.
- * In this class, field names are identical to TIFF tag names.
- * For that reason, many of those field names do not follow usual Java convention for constants.
+ * This class provides only the tags that are not already provided by {@link TIFFTagSet}.
+ *
+ * <p>In this class, field names are identical to TIFF tag names.
+ * For that reason, some field names do not follow usual Java convention for constants.</p>
  *
  * <p>A useful (but unofficial) reference is the
  * <a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a> page.</p>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
- *
- * @todo Use {@code javax.imageio.plugins.tiff.BaselineTIFFTagSet} with JDK9.
  */
 final class Tags {
+    /**
+     * XML packet containing metadata such as descriptions, titles, keywords, author and copyright information.
+     *
+     * @see <a href="https://www.adobe.com/products/xmp.html">Adobe XMP technote 9-14-02</a>
+     * @see <a href="https://www.iso.org/standard/75163.html">ISO 16684-1:2019 Graphic technology — Extensible metadata platform (XMP)</a>
+     */
+    public static final short XML_Packet = 0x02BC;
 
-    //////////////////////////////////////////////////////////
-    //                  BASELINE TIFF TAGS                  //
-    //////////////////////////////////////////////////////////
-
-    public static final short NewSubfileType              = 0x00FE;
-    public static final short SubfileType                 = 0x00FF;
-    public static final short ImageWidth                  = 0x0100;
-    public static final short ImageLength                 = 0x0101;
-    public static final short BitsPerSample               = 0x0102;
-    public static final short Compression                 = 0x0103;
-    public static final short PhotometricInterpretation   = 0x0106;
-    public static final short Threshholding               = 0x0107;
-    public static final short CellWidth                   = 0x0108;
-    public static final short CellLength                  = 0x0109;
-    public static final short FillOrder                   = 0x010A;
-    public static final short DocumentName                = 0x010D;
-    public static final short ImageDescription            = 0x010E;
-    public static final short Make                        = 0x010F;
-    public static final short Model                       = 0x0110;
-    public static final short StripOffsets                = 0x0111;
-    public static final short Orientation                 = 0x0112;
-    public static final short SamplesPerPixel             = 0x0115;
-    public static final short RowsPerStrip                = 0x0116;
-    public static final short StripByteCounts             = 0x0117;
-    public static final short MinSampleValue              = 0x0118;
-    public static final short MaxSampleValue              = 0x0119;
-    public static final short XResolution                 = 0x011A;
-    public static final short YResolution                 = 0x011B;
-    public static final short PlanarConfiguration         = 0x011C;
-    public static final short PageName                    = 0x011D;
-    public static final short XPosition                   = 0x011E;
-    public static final short YPosition                   = 0x011F;
-    public static final short FreeOffsets                 = 0x0120;
-    public static final short FreeByteCounts              = 0x0121;
-    public static final short GrayResponseUnit            = 0x0122;
-    public static final short GrayResponseCurve           = 0x0123;
-    public static final short T4Options                   = 0x0124;
-    public static final short T6Options                   = 0x0125;
-    public static final short ResolutionUnit              = 0x0128;
-    public static final short PageNumber                  = 0x0129;
-    public static final short TransferFunction            = 0x012D;
-    public static final short Software                    = 0x0131;
-    public static final short DateTime                    = 0x0132;
-    public static final short DateTimeOriginal    = (short) 0x9003;
-    public static final short DateTimeDigitized   = (short) 0x9004;
-    public static final short Artist                      = 0x013B;
-    public static final short HostComputer                = 0x013C;
-    public static final short Predictor                   = 0x013D;
-    public static final short WhitePoint                  = 0x013E;
-    public static final short PrimaryChromaticities       = 0x013F;
-    public static final short ColorMap                    = 0x0140;
-    public static final short HalftoneHints               = 0x0141;
-    public static final short TileWidth                   = 0x0142;
-    public static final short TileLength                  = 0x0143;
-    public static final short TileOffsets                 = 0x0144;
-    public static final short TileByteCounts              = 0x0145;
-    public static final short InkSet                      = 0x014C;
-    public static final short InkNames                    = 0x014D;
-    public static final short NumberOfInks                = 0x014E;
-    public static final short DotRange                    = 0x0150;
-    public static final short TargetPrinter               = 0x0151;
-    public static final short ExtraSamples                = 0x0152;
-    public static final short SampleFormat                = 0x0153;
-    public static final short SMinSampleValue             = 0x0154;
-    public static final short SMaxSampleValue             = 0x0155;
-    public static final short TransferRange               = 0x0156;
-    public static final short JPEGProc                    = 0x0200;
-    public static final short JPEGInterchangeFormat       = 0x0201;
-    public static final short JPEGInterchangeFormatLength = 0x0202;
-    public static final short JPEGRestartInterval         = 0x0203;
-    public static final short JPEGLosslessPredictors      = 0x0205;
-    public static final short JPEGPointTransforms         = 0x0206;
-    public static final short JPEGQTables                 = 0x0207;
-    public static final short JPEGDCTables                = 0x0208;
-    public static final short JPEGACTables                = 0x0209;
-    public static final short YCbCrCoefficients           = 0x0211;
-    public static final short YCbCrSubSampling            = 0x0212;
-    public static final short YCbCrPositioning            = 0x0213;
-    public static final short ReferenceBlackWhite         = 0x0214;
-    public static final short Copyright           = (short) 0x8298;
-
-
-    /////////////////////////////////////////////////////////
-    //              OGC DGIWG EXTENSION TAGS               //
-    /////////////////////////////////////////////////////////
+    /**
+     * Collection of Photoshop "Image Resource Blocks".
+     *
+     * @see <a href="https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html">PhotoShop private TIFF Tag</a>
+     */
+    public static final short PhotoshopImageResources = (short) 0x8649;
 
     /**
      * Embedded XML-encoded instance documents prepared using 19139-based schema.
+     * This is an OGC DGIWG extension tag.
      */
     public static final short GEO_METADATA = (short) 0xC6DD;
 
-
-    /////////////////////////////////////////////////////////
-    //                 GDAL EXTENSION TAGS                 //
-    /////////////////////////////////////////////////////////
-
     /**
      * Holds an XML list of name=value 'metadata' values about the image as a whole, and about specific samples.
      *
@@ -146,60 +73,20 @@ final class Tags {
      */
     public static final short GDAL_NODATA = (short) 0xA481;               // 42113
 
-
-    /////////////////////////////////////////////////////////
-    //                 GEOTIFF EXTENSION TAGS              //
-    /////////////////////////////////////////////////////////
-
-    /**
-     * References all "GeoKeys" needed for building the Coordinate Reference System.
-     * GeoTIFF keys are stored in a kind of directory inside the TIFF directory, with
-     * the keys enumerated in the {@link CRSBuilder} class.
-     *
-     * @see GeoKeys
-     */
-    public static final short GeoKeyDirectory = (short) 0x87AF;           // 34735
-
-    /**
-     * References all {@code double} values referenced by the {@link GeoKeys}.
-     * The keys are stored in the entry referenced by {@link #GeoKeyDirectory}.
-     */
-    public static final short GeoDoubleParams = (short) 0x87B0;           // 34736
-
     /**
-     * References all {@link String} values referenced by the {@link GeoKeys}.
-     * The keys are stored in the entry referenced by {@link #GeoKeyDirectory}.
+     * Supplier of TIFF tag sets, in preference order.
+     * The sets that are most likely to be used (for the kind of data handled by SIS) should be first.
      */
-    public static final short GeoAsciiParams = (short) 0x87B1;            // 34737
-
-    /**
-     * The tie points as (I,J,K,X,Y,Z) records in an array of floating-point numbers.
-     * This tag is also known as {@code Georeference}.
-     */
-    public static final short ModelTiePoints = (short) 0x8482;            // 33922
-
-    /**
-     * A vector of 3 floating-point values defining the "grid to CRS" conversion without rotation.
-     * The conversion is defined as below, when (I,J,K,X,Y,Z) is the tie point singleton record:
-     *
-     * ┌                       ┐
-     * │   Sx   0    0    Tx   │       Tx = X - I/Sx
-     * │   0   -Sy   0    Ty   │       Ty = Y + J/Sy
-     * │   0    0    Sz   Tz   │       Tz = Z - K/Sz  (if not 0)
-     * │   0    0    0    1    │
-     * └                       ┘
-     *
-     * Only one of {@code ModelPixelScaleTag} and {@link #ModelTransformation} should be used.
-     */
-    public static final short ModelPixelScaleTag = (short) 0x830E;        // 33550
-
-    /**
-     * Specifies the "grid to CRS" conversion (the transformation matrix between the raster space and the model space).
-     * If specified, the tag shall have the 16 values of a 4×4 matrix in row-major fashion. The last matrix row (i.e.
-     * the last 4 values) should be [0 0 0 1]. The row before should be [0 0 0 0] if the conversion is two-dimensional.
-     * Only one of {@link #ModelPixelScaleTag} and {@code ModelTransformation} should be used.
-     */
-    public static final short ModelTransformation = (short) 0x85D8;       // 34264
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static Supplier<TIFFTagSet>[] TAG_SETS = new Supplier[] {
+        javax.imageio.plugins.tiff.BaselineTIFFTagSet::getInstance,
+        javax.imageio.plugins.tiff.GeoTIFFTagSet::getInstance,
+        javax.imageio.plugins.tiff.ExifGPSTagSet::getInstance,
+        javax.imageio.plugins.tiff.ExifParentTIFFTagSet::getInstance,
+        javax.imageio.plugins.tiff.ExifTIFFTagSet::getInstance,
+        javax.imageio.plugins.tiff.ExifInteroperabilityTagSet::getInstance,
+        javax.imageio.plugins.tiff.FaxTIFFTagSet::getInstance
+    };
 
     /**
      * Do not allow instantiation of this class.
@@ -208,10 +95,15 @@ final class Tags {
     }
 
     /**
-     * Returns the name of the given tag. Implementation of this method is inefficient,
-     * but it should rarely be invoked (mostly for formatting error messages).
+     * Returns the name of the given tag.
+     * This method should be rarely invoked (mostly for formatting error messages).
      */
     static String name(final short tag) {
+        final int ti = Short.toUnsignedInt(tag);
+        for (final Supplier<TIFFTagSet> s : TAG_SETS) {
+            final TIFFTag t = s.get().getTag(ti);
+            if (t != null) return t.getName();
+        }
         try {
             for (final Field field : Tags.class.getFields()) {
                 if (field.getType() == Short.TYPE) {
@@ -223,6 +115,6 @@ final class Tags {
         } catch (IllegalAccessException e) {
             throw new AssertionError(e);        // Should never happen because we asked only for public fields.
         }
-        return Integer.toHexString(Short.toUnsignedInt(tag));
+        return "Tag #" + Integer.toHexString(ti);
     }
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
index 129ebd3913..70bf297c01 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
@@ -32,7 +32,7 @@
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.8
  * @module
  */
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TagsTest.java
similarity index 52%
copy from storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java
copy to storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TagsTest.java
index 3c0a7251cc..1cf1d50e34 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/package-info.java
+++ b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/TagsTest.java
@@ -14,18 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.storage.geotiff;
+
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
+import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
+import static org.junit.Assert.*;
+
 
 /**
- * Utility classes for the implementation of GeoTIFF reader and writer.
- *
- * <STRONG>Do not use!</STRONG>
- *
- * This package is for internal use by SIS only. Classes in this package
- * may change in incompatible ways in any future version without notice.
+ * Tests {@link Tags}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
- * @since   0.8
+ * @version 1.4
+ * @since   1.4
  * @module
  */
-package org.apache.sis.internal.geotiff;
+public final strictfp class TagsTest extends TestCase {
+    /**
+     * Tests {@link Tags#name(short)}.
+     */
+    @Test
+    public void testName() {
+        assertEquals("Artist",             Tags.name((short) TAG_ARTIST));
+        assertEquals("Copyright",          Tags.name((short) TAG_COPYRIGHT));
+        assertEquals("GeoKeyDirectoryTag", Tags.name((short) TAG_GEO_KEY_DIRECTORY));
+        assertEquals("GEO_METADATA",       Tags.name(Tags.GEO_METADATA));
+    }
+}
diff --git a/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java b/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
index 7540ccba7e..f08cb590ba 100644
--- a/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
+++ b/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
@@ -25,7 +25,7 @@ import org.junit.BeforeClass;
  * All tests from the {@code sis-geotiff} module, in rough dependency order.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
  */
@@ -34,6 +34,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.storage.inflater.InflaterTest.class,
     org.apache.sis.internal.storage.inflater.CCITTRLETest.class,
     org.apache.sis.storage.geotiff.TypeTest.class,
+    org.apache.sis.storage.geotiff.TagsTest.class,
     org.apache.sis.storage.geotiff.GeoKeysTest.class,
     org.apache.sis.storage.geotiff.CRSBuilderTest.class,
     org.apache.sis.storage.geotiff.XMLMetadataTest.class,


[sis] 01/03: Post-merge cleanup.

Posted by de...@apache.org.
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 f83c833020601517d36e42afb330ede853016e87
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Dec 13 15:28:56 2022 +0100

    Post-merge cleanup.
---
 .../src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java  | 2 ++
 .../src/main/java/org/apache/sis/metadata/PropertyInformation.java | 7 +++----
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
index 26a78bc4ea..7b2a5c078c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ExpandedFeature.java
@@ -20,6 +20,8 @@ import java.util.Arrays;
 import java.util.Map;
 import java.util.Collection;
 import java.util.List;
+
+// Branch-dependent imports
 import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.Property;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
index ab15076f17..dfb2593c6b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
@@ -17,8 +17,8 @@
 package org.apache.sis.metadata;
 
 import java.util.Locale;
-import java.util.Set;
 import java.util.Collection;
+import java.util.Collections;
 import java.lang.reflect.Method;
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Obligation;
@@ -64,7 +64,6 @@ import static java.util.logging.Logger.getLogger;
  *
  * @see InformationMap
  * @see MetadataStandard#asInformationMap(Class, KeyNamePolicy)
- * @see <a href="https://issues.apache.org/jira/browse/SIS-80">SIS-80</a>
  *
  * @since 0.3
  * @module
@@ -134,7 +133,7 @@ final class PropertyInformation<E> extends SimpleIdentifier           // Impleme
      */
     @SuppressWarnings({"unchecked","rawtypes"})
     PropertyInformation(final Citation standard, final String property, final Method getter,
-            final Class<E> elementType, final ValueRange range)
+                        final Class<E> elementType, final ValueRange range)
     {
         super(standard, property, getter.isAnnotationPresent(Deprecated.class));
         parent = getter.getDeclaringClass();
@@ -292,7 +291,7 @@ final class PropertyInformation<E> extends SimpleIdentifier           // Impleme
      */
     @Override
     public Collection<String> getParentEntity() {
-        return Set.of(getCodeSpace());
+        return Collections.singleton(getCodeSpace());
     }
 
     /**