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 0cdc177a1bb1569263bd668afa7b6cb7811c95e9
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Sep 20 16:59:30 2023 +0200

    Rename GeoKeys from GeoTIFF 1.0 to GeoTIFF 1.1 names.
    The keys related to units of measure are now handled in a separated class.
---
 .../org/apache/sis/storage/geotiff/CRSBuilder.java | 169 +++++++++++--------
 .../org/apache/sis/storage/geotiff/GeoKeys.java    | 152 ++++++++---------
 .../apache/sis/storage/geotiff/GeoKeysLoader.java  |   4 +-
 .../org/apache/sis/storage/geotiff/UnitKey.java    | 186 +++++++++++++++++++++
 .../apache/sis/storage/geotiff/GeoKeysTest.java    |   2 +-
 5 files changed, 353 insertions(+), 160 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CRSBuilder.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CRSBuilder.java
index c99a30f41a..c8cc438544 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CRSBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CRSBuilder.java
@@ -97,9 +97,9 @@ import static 
org.apache.sis.util.Utilities.equalsIgnoreMetadata;
 final class CRSBuilder extends ReferencingFactoryContainer {
     /**
      * Index where to store the name of the geodetic CRS, the datum, the 
ellipsoid and the prime meridian.
-     * The GeoTIFF specification has only one key, {@link 
GeoKeys#GeogCitation}, for the geographic CRS and
-     * its components. But some GeoTIFF files encode the names of all 
components in the value associated to
-     * that key, as in the following example:
+     * The GeoTIFF specification has only one key, {@link 
GeoKeys#GeodeticCitation}, for the geographic CRS
+     * and its components. But some GeoTIFF files encode the names of all 
components in the value associated
+     * to that key, as in the following example:
      *
      * <pre class="text">
      *   GCS Name = wgs84|Datum = unknown|Ellipsoid = WGS_1984|Primem = 
Greenwich|</pre>
@@ -111,7 +111,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
     // PRIMEM must be first and GCRS must be last.
 
     /**
-     * Keys that may be used in the value associated to {@link 
GeoKeys#GeogCitation}.
+     * Keys that may be used in the value associated to {@link 
GeoKeys#GeodeticCitation}.
      * For each element in this array at index {@code i}, the {@code i/2} 
value is equal to the
      * {@code DATUM}, {@code ELLIPSOID} or {@code PRIMEM} constant for the 
corresponding type.
      */
@@ -503,7 +503,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
          * At this point we finished copying all GeoTIFF keys in the `geoKeys` 
map. Before to create the CRS,
          * store a few metadata. The first one is an ASCII reference to 
published documentation on the overall
          * configuration of the GeoTIFF file. In practice it seems to be often 
the projected CRS name, despite
-         * GeoKeys.PCSCitation being already for that purpose.
+         * GeoKeys.ProjectedCitation being already for that purpose.
          */
         description = getAsString(GeoKeys.Citation);
         int code = getAsInteger(GeoKeys.RasterType);
@@ -531,7 +531,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
             final VerticalCRS vertical = createVerticalCRS();
             if (vertical != null) {
                 if (crs == null) {
-                    missingValue(GeoKeys.GeographicType);
+                    missingValue(GeoKeys.GeodeticCRS);
                 } else {
                     crs = 
getCRSFactory().createCompoundCRS(Map.of(IdentifiedObject.NAME_KEY, 
crs.getName()), crs, vertical);
                 }
@@ -605,26 +605,49 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
         return (EllipsoidalCS) CoordinateSystems.replaceAngularUnit(cs, unit);
     }
 
+    /**
+     * Shortcut for {@code createUnit(…)} when the unit is angular.
+     * If no unit is specified, the default is degrees.
+     *
+     * @param  key  key of the unit to fetch.
+     * @return the unit of measurement associated to the given key, or the 
default value.
+     */
+    private Unit<Angle> createAngularUnit(final UnitKey key) throws 
FactoryException {
+        return createUnit(key, Angle.class, Units.DEGREE);
+    }
+
+    /**
+     * Shortcut for {@code createUnit(…)} when the unit is linear.
+     * If no unit is specified, the default is metre.
+     *
+     * @param  key  key of the unit to fetch.
+     * @return the unit of measurement associated to the given key, or the 
default value.
+     */
+    private Unit<Length> createLinearUnit(final UnitKey key) throws 
FactoryException {
+        return createUnit(key, Length.class, Units.METRE);
+    }
+
     /**
      * Creates units of measurement for projected or geographic coordinate 
reference systems.
      * The units may either be specified as a standard EPSG recognized unit, 
or may be user defined.
-     * If the first case (EPSG code), the {@code keyUser} is ignored. In the 
second case (user-defined),
+     * In the first case (EPSG code), the {@code scaleKey} is ignored. In the 
second case (user-defined),
      * the unit of measurement is defined by a conversion factor from metre or 
radian base unit.
      *
-     * @param  codeKey       the {@link GeoKeys} for a unit of measurement 
defined by an EPSG code.
-     * @param  scaleKey      the {@link GeoKeys} for a unit of measurement 
defined by a scale applied on a base unit.
+     * @param  <Q>           the compile-time value of {@code quantity}.
+     * @param  key           information about the keys to use for fetching 
the unit of measurement.
      * @param  quantity      {@link Length} for a linear unit, or {@link 
Angle} for an angular unit.
      * @param  defaultValue  the unit of measurement to return if no value is 
found in the GeoTIFF file.
-     * @return the unit of measurement associated to the given {@link 
GeoKeys}, or the default value.
+     * @return the unit of measurement associated to the given key, or the 
default value.
      *
-     * @throws NoSuchElementException if {@code keyEPSG} value is {@link 
GeoCodes#userDefined} and no value is associated to {@code keyUser}.
+     * @throws NoSuchElementException if {@code codeKey} value is {@link 
GeoCodes#userDefined} and no value is associated to {@code scaleKey}.
      * @throws NumberFormatException  if a numeric value was stored as a 
string and cannot be parsed.
      * @throws ClassCastException     if the unit of measurement identified by 
the EPSG code is not of the expected quantity.
      */
-    private <Q extends Quantity<Q>> Unit<Q> createUnit(final short codeKey, 
final short scaleKey,
+    private <Q extends Quantity<Q>> Unit<Q> createUnit(final UnitKey key,
             final Class<Q> quantity, final Unit<Q> defaultValue) throws 
FactoryException
     {
-        final int epsg = getAsInteger(codeKey);
+        final short scaleKey = key.scaleKey;
+        final int epsg = getAsInteger(key.codeKey);
         switch (epsg) {
             case GeoCodes.undefined: {
                 return defaultValue;
@@ -671,7 +694,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
      * <ul>
      *   <li>A code given by {@link GeoKeys#PrimeMeridian}.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
-     *     <li>a prime meridian value given by {@link 
GeoKeys#PrimeMeridianLong}.</li>
+     *     <li>a prime meridian value given by {@link 
GeoKeys#PrimeMeridianLongitude}.</li>
      *   </ul></li>
      * </ul>
      *
@@ -688,15 +711,15 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
         switch (epsg) {
             case GeoCodes.undefined:      // If not specified, should default 
to Greenwich but we nevertheless verify.
             case GeoCodes.userDefined: {
-                final double longitude = 
getAsDouble(GeoKeys.PrimeMeridianLong);
+                final double longitude = 
getAsDouble(GeoKeys.PrimeMeridianLongitude);
                 if (Double.isNaN(longitude)) {
                     if (epsg != GeoCodes.undefined) {
-                        missingValue(GeoKeys.PrimeMeridianLong);
+                        missingValue(GeoKeys.PrimeMeridianLongitude);
                     }
                 } else if (longitude != 0) {
                     /*
                      * If the prime meridian is not Greenwich, create that 
meridian but do not use the
-                     * GeoKeys.GeogCitation value (unless it had a sub-element 
for the prime meridian).
+                     * GeoKeys.GeodeticCitation value (unless it had a 
sub-element for the prime meridian).
                      * This is because the citation value is for the CRS (e.g. 
"WGS84") while the prime
                      * meridian names are very different (e.g. "Paris", 
"Madrid", etc).
                      */
@@ -727,7 +750,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
      * @param  unit  the unit of measurement declared in the GeoTIFF file.
      */
     private void verify(final PrimeMeridian pm, final Unit<Angle> unit) {
-        verify(pm, ReferencingUtilities.getGreenwichLongitude(pm, unit), 
GeoKeys.PrimeMeridianLong, unit);
+        verify(pm, ReferencingUtilities.getGreenwichLongitude(pm, unit), 
GeoKeys.PrimeMeridianLongitude, unit);
     }
 
     /**
@@ -737,7 +760,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
      * <ul>
      *   <li>A code given by {@link GeoKeys#Ellipsoid} tag.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
-     *     <li>a name given by {@link GeoKeys#GeogCitation},</li>
+     *     <li>a name given by {@link GeoKeys#GeodeticCitation},</li>
      *     <li>a semi major axis value given by {@link 
GeoKeys#SemiMajorAxis},</li>
      *     <li>one of:<ul>
      *       <li>an inverse flattening factor given by {@link 
GeoKeys#InvFlattening},</li>
@@ -817,7 +840,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
      * <ul>
      *   <li>A code given by {@link GeoKeys#GeodeticDatum}.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
-     *     <li>a name given by {@link GeoKeys#GeogCitation},</li>
+     *     <li>a name given by {@link GeoKeys#GeodeticCitation},</li>
      *     <li>all values required by {@link #createPrimeMeridian(String[], 
Unit)} (optional),</li>
      *     <li>all values required by {@link #createEllipsoid(String[], 
Unit)}.</li>
      *   </ul></li>
@@ -856,7 +879,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
                 String name = getOrDefault(names, DATUM);
                 if (name == null) {
                     // TODO: see https://issues.apache.org/jira/browse/SIS-536
-                    throw new 
NoSuchElementException(missingValue(GeoKeys.GeogCitation));
+                    throw new 
NoSuchElementException(missingValue(GeoKeys.GeodeticCitation));
                 }
                 final Ellipsoid     ellipsoid = createEllipsoid(names, 
linearUnit);
                 final PrimeMeridian meridian  = createPrimeMeridian(names, 
angularUnit);
@@ -916,7 +939,7 @@ final class CRSBuilder extends ReferencingFactoryContainer {
     
//////////////////////////////////////////////////////////////////////////////////////////////////
 
     /**
-     * Splits the {@link GeoKeys#GeogCitation} value into its prime meridian, 
ellipsoid, datum and CRS name components.
+     * Splits the {@link GeoKeys#GeodeticCitation} value into its prime 
meridian, ellipsoid, datum and CRS name components.
      * This method is intended to parse geographic CRS names written like 
below:
      *
      * <pre class="text">
@@ -986,7 +1009,7 @@ final class CRSBuilder extends ReferencingFactoryContainer 
{
      * Creates the main CRS in the case where that CRS is geographic.
      */
     private GeographicCRS createGeographicCRS() throws FactoryException {
-        return createGeographicCRS(true, createUnit(GeoKeys.AngularUnits, 
GeoKeys.AngularUnitSize, Angle.class, Units.DEGREE));
+        return createGeographicCRS(true, createAngularUnit(UnitKey.ANGULAR));
     }
 
     /**
@@ -994,14 +1017,14 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * The GeoTIFF values used by this method are:
      *
      * <ul>
-     *   <li>A code given by {@link GeoKeys#GeographicType}.</li>
+     *   <li>A code given by {@link GeoKeys#GeodeticCRS}.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
-     *     <li>a prime meridian value given by {@link 
GeoKeys#PrimeMeridianLong},</li>
-     *     <li>a CRS name given by {@link GeoKeys#GeogCitation},</li>
+     *     <li>a prime meridian value given by {@link 
GeoKeys#PrimeMeridianLongitude},</li>
+     *     <li>a CRS name given by {@link GeoKeys#GeodeticCitation},</li>
      *     <li>a datum definition.</li>
      *   </ul></li>
-     *   <li>A unit code given by {@link GeoKeys#AngularUnits} (optional).</li>
-     *   <li>A unit scale factor given by {@link GeoKeys#AngularUnitSize} 
(optional).</li>
+     *   <li>A unit code given by {@link GeoKeys#GeogAngularUnits} 
(optional).</li>
+     *   <li>A unit scale factor given by {@link GeoKeys#GeogAngularUnitSize} 
(optional).</li>
      * </ul>
      *
      * @param  isImageCRS   whether to force longitude before latitude axis.
@@ -1014,19 +1037,19 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @see #createGeodeticDatum(String[], Unit, Unit)
      */
     private GeographicCRS createGeographicCRS(final boolean isImageCRS, final 
Unit<Angle> angularUnit) throws FactoryException {
-        final int epsg = getAsInteger(GeoKeys.GeographicType);
+        final int epsg = getAsInteger(GeoKeys.GeodeticCRS);
         switch (epsg) {
             case GeoCodes.undefined: {
                 alreadyReported = true;
-                throw new 
NoSuchElementException(missingValue(GeoKeys.GeographicType));
+                throw new 
NoSuchElementException(missingValue(GeoKeys.GeodeticCRS));
             }
             case GeoCodes.userDefined: {
                 /*
                  * Creates the geodetic datum, then a geographic CRS assuming 
(longitude, latitude) axis order.
                  * We use the coordinate system of CRS:84 as a template and 
modify its unit of measurement if needed.
                  */
-                final String[] names = 
splitName(getAsString(GeoKeys.GeogCitation));
-                final Unit<Length> linearUnit = 
createUnit(GeoKeys.GeogLinearUnits, GeoKeys.GeogLinearUnitSize, Length.class, 
Units.METRE);
+                final String[] names = 
splitName(getAsString(GeoKeys.GeodeticCitation));
+                final Unit<Length> linearUnit = 
createLinearUnit(UnitKey.LINEAR);
                 final GeodeticDatum datum = createGeodeticDatum(names, 
angularUnit, linearUnit);
                 EllipsoidalCS cs = 
CommonCRS.defaultGeographic().getCoordinateSystem();
                 if (!Units.DEGREE.equals(angularUnit)) {
@@ -1065,11 +1088,11 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
          * Note: current createUnit(…) implementation does not allow us to 
distinguish whether METRE ou DEGREE units
          * were specified in the GeoTIFF file or if we got the default values. 
We do not compare units for that reason.
          */
-        final Unit<Length> linearUnit = createUnit(GeoKeys.GeogLinearUnits, 
GeoKeys.GeogLinearUnitSize, Length.class, Units.METRE);
+        final Unit<Length> linearUnit = createLinearUnit(UnitKey.LINEAR);
         final GeodeticDatum datum = crs.getDatum();
         verifyIdentifier(crs, datum, GeoKeys.GeodeticDatum);
         verify(datum, angularUnit, linearUnit);
-        geoKeys.remove(GeoKeys.GeogCitation);
+        geoKeys.remove(GeoKeys.GeodeticCitation);
     }
 
     /**
@@ -1084,20 +1107,20 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @see #createGeodeticDatum(String[], Unit, Unit)
      */
     private GeocentricCRS createGeocentricCRS() throws FactoryException {
-        final int epsg = getAsInteger(GeoKeys.GeographicType);
+        final int epsg = getAsInteger(GeoKeys.GeodeticCRS);
         switch (epsg) {
             case GeoCodes.undefined: {
                 alreadyReported = true;
-                throw new 
NoSuchElementException(missingValue(GeoKeys.GeographicType));
+                throw new 
NoSuchElementException(missingValue(GeoKeys.GeodeticCRS));
             }
             case GeoCodes.userDefined: {
                 /*
                  * Creates the geodetic datum, then a geocentric CRS. We use 
the coordinate system of
                  * the WGS84 geocentric CRS as a template and modify its unit 
of measurement if needed.
                  */
-                final String[] names = 
splitName(getAsString(GeoKeys.GeogCitation));
-                final Unit<Length> linearUnit = 
createUnit(GeoKeys.GeogLinearUnits, GeoKeys.GeogLinearUnitSize, Length.class, 
Units.METRE);
-                final Unit<Angle> angularUnit = 
createUnit(GeoKeys.AngularUnits, GeoKeys.AngularUnitSize, Angle.class, 
Units.DEGREE);
+                final String[] names = 
splitName(getAsString(GeoKeys.GeodeticCitation));
+                final Unit<Length> linearUnit = 
createLinearUnit(UnitKey.LINEAR);
+                final Unit<Angle> angularUnit = 
createAngularUnit(UnitKey.ANGULAR);
                 final GeodeticDatum datum = createGeodeticDatum(names, 
angularUnit, linearUnit);
                 CartesianCS cs = (CartesianCS) 
CommonCRS.WGS84.geocentric().getCoordinateSystem();
                 if (!Units.METRE.equals(linearUnit)) {
@@ -1132,8 +1155,8 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
          * Note: current createUnit(…) implementation does not allow us to 
distinguish whether METRE ou DEGREE units
          * were specified in the GeoTIFF file or if we got the default values. 
We do not compare units for that reason.
          */
-        final Unit<Length> linearUnit = createUnit(GeoKeys.GeogLinearUnits, 
GeoKeys.GeogLinearUnitSize, Length.class, Units.METRE);
-        final Unit<Angle> angularUnit = createUnit(GeoKeys.AngularUnits, 
GeoKeys.AngularUnitSize, Angle.class, Units.DEGREE);
+        final Unit<Length> linearUnit = createLinearUnit(UnitKey.LINEAR);
+        final Unit<Angle> angularUnit = createAngularUnit(UnitKey.ANGULAR);
         final GeodeticDatum datum = crs.getDatum();
         verifyIdentifier(crs, datum, GeoKeys.GeodeticDatum);
         verify(datum, angularUnit, linearUnit);
@@ -1198,14 +1221,14 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * Some GeoTIFF values used by this method are:
      *
      * <ul>
-     *   <li>A code given by {@link GeoKeys#ProjectedCSType}.</li>
+     *   <li>A code given by {@link GeoKeys#ProjectedCRS}.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
-     *     <li>a name given by {@link GeoKeys#PCSCitation},</li>
+     *     <li>a name given by {@link GeoKeys#ProjectedCitation},</li>
      *     <li>a {@link CoordinateOperation} given by {@link 
GeoKeys#Projection},</li>
-     *     <li>an {@link OperationMethod} given by {@link 
GeoKeys#CoordTrans}.</li>
+     *     <li>an {@link OperationMethod} given by {@link 
GeoKeys#ProjMethod}.</li>
      *   </ul></li>
-     *   <li>A unit code given by {@link GeoKeys#LinearUnits} (optional).</li>
-     *   <li>A unit scale factor given by {@link GeoKeys#LinearUnitSize} 
(optional).</li>
+     *   <li>A unit code given by {@link GeoKeys#ProjLinearUnits} 
(optional).</li>
+     *   <li>A unit scale factor given by {@link GeoKeys#ProjLinearUnitSize} 
(optional).</li>
      * </ul>
      *
      * @throws NoSuchElementException if a mandatory value is missing.
@@ -1217,25 +1240,25 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @see #createConversion(String, Unit, Unit)
      */
     private ProjectedCRS createProjectedCRS() throws FactoryException {
-        final int epsg = getAsInteger(GeoKeys.ProjectedCSType);
+        final int epsg = getAsInteger(GeoKeys.ProjectedCRS);
         switch (epsg) {
             case GeoCodes.undefined: {
                 alreadyReported = true;
-                throw new 
NoSuchElementException(missingValue(GeoKeys.ProjectedCSType));
+                throw new 
NoSuchElementException(missingValue(GeoKeys.ProjectedCRS));
             }
             case GeoCodes.userDefined: {
                 /*
                  * If the CRS is user-defined, we have to parse many 
components (base CRS, datum, unit, etc.)
                  * and build the projected CRS from them. Note that some 
GeoTIFF files put the projection name
-                 * in the Citation key instead of PCSCitation.
+                 * in the Citation key instead of ProjectedCitation.
                  */
-                String name = getAsString(GeoKeys.PCSCitation);
+                String name = getAsString(GeoKeys.ProjectedCitation);
                 if (name == null) {
                     name = getAsString(GeoKeys.Citation);
                     // Note that Citation has been removed from the map, so it 
will not be used by `complete(MetadataBuilder)`.
                 }
-                final Unit<Length>  linearUnit  = 
createUnit(GeoKeys.LinearUnits,  GeoKeys.LinearUnitSize, Length.class, 
Units.METRE);
-                final Unit<Angle>   angularUnit = 
createUnit(GeoKeys.AngularUnits, GeoKeys.AngularUnitSize, Angle.class, 
Units.DEGREE);
+                final Unit<Length>  linearUnit  = 
createLinearUnit(UnitKey.PROJECTED);
+                final Unit<Angle>   angularUnit = 
createAngularUnit(UnitKey.ANGULAR);
                 final GeographicCRS baseCRS     = createGeographicCRS(false, 
angularUnit);
                 final Conversion    projection  = createConversion(name, 
angularUnit, linearUnit);
                 CartesianCS cs = getStandardProjectedCS();
@@ -1268,10 +1291,10 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @param  crs  the CRS created from the EPSG geodetic dataset.
      */
     private void verify(final ProjectedCRS crs) throws FactoryException {
-        final Unit<Length> linearUnit  = createUnit(GeoKeys.LinearUnits,  
GeoKeys.LinearUnitSize, Length.class, Units.METRE);
-        final Unit<Angle>  angularUnit = createUnit(GeoKeys.AngularUnits, 
GeoKeys.AngularUnitSize, Angle.class, Units.DEGREE);
+        final Unit<Length> linearUnit  = createLinearUnit(UnitKey.PROJECTED);
+        final Unit<Angle>  angularUnit = createAngularUnit(UnitKey.ANGULAR);
         final GeographicCRS baseCRS = crs.getBaseCRS();
-        verifyIdentifier(crs, baseCRS, GeoKeys.GeographicType);
+        verifyIdentifier(crs, baseCRS, GeoKeys.GeodeticCRS);
         verify(baseCRS, angularUnit);
         final Conversion projection = crs.getConversionFromBase();
         verifyIdentifier(crs, projection, GeoKeys.Projection);
@@ -1284,7 +1307,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * For example there is an ambiguity between Polar Stereographic (variant 
A) and (variant B).
      */
     private String methodCode() {
-        final String code = getMandatoryString(GeoKeys.CoordTrans);
+        final String code = getMandatoryString(GeoKeys.ProjMethod);
         try {
             switch (Integer.parseInt(code)) {
                 case GeoCodes.PolarStereographic: {
@@ -1338,7 +1361,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
                 throw new 
NoSuchElementException(missingValue(GeoKeys.Projection));
             }
             case GeoCodes.userDefined: {
-                final Unit<Angle>         azimuthUnit = 
createUnit(GeoKeys.AzimuthUnits, (short) 0, Angle.class, Units.DEGREE);
+                final Unit<Angle>         azimuthUnit = 
createAngularUnit(UnitKey.AZIMUTH);
                 final OperationMethod     method      = 
getCoordinateOperationFactory().getOperationMethod(methodCode());
                 final ParameterValueGroup parameters  = 
method.getParameters().createValue();
                 final Map<Integer,String> toNames     = 
ReferencingUtilities.identifierToName(parameters.getDescriptor(), 
Citations.GEOTIFF);
@@ -1349,11 +1372,11 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
                     final Unit<?> unit;
                     final Map.Entry<Short,?> entry = it.next();
                     final Short key = entry.getKey();
-                    switch (GeoKeys.unitOf(key)) {
-                        case GeoKeys.RATIO:   unit = Units.UNITY; break;
-                        case GeoKeys.LINEAR:  unit = linearUnit;  break;
-                        case GeoKeys.ANGULAR: unit = angularUnit; break;
-                        case GeoKeys.AZIMUTH: unit = azimuthUnit; break;
+                    switch (UnitKey.ofProjectionParameter(key)) {
+                        case RATIO:     unit = Units.UNITY; break;
+                        case PROJECTED: unit = linearUnit;  break;
+                        case ANGULAR:   unit = angularUnit; break;
+                        case AZIMUTH:   unit = azimuthUnit; break;
                         default: continue;
                     }
                     final Number value = (Number) entry.getValue();
@@ -1420,8 +1443,8 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
     private void verify(final Conversion projection, final Unit<Angle> 
angularUnit, final Unit<Length> linearUnit)
             throws FactoryException
     {
-        final Unit<Angle> azimuthUnit = createUnit(GeoKeys.AzimuthUnits, 
(short) 0, Angle.class, Units.DEGREE);
-        final String type = getAsString(GeoKeys.CoordTrans);
+        final Unit<Angle> azimuthUnit = createAngularUnit(UnitKey.AZIMUTH);
+        final String type = getAsString(GeoKeys.ProjMethod);
         if (type != null) {
             /*
              * Compare the name of the map projection declared in the GeoTIFF 
file with the name
@@ -1434,7 +1457,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
                     expected = IdentifiedObjects.getIdentifier(method, null);
                 }
                 warning(Resources.Keys.NotTheEpsgValue_5, 
IdentifiedObjects.getIdentifierOrName(projection),
-                        expected.getCode(), GeoKeys.name(GeoKeys.CoordTrans), 
type, "");
+                        expected.getCode(), GeoKeys.name(GeoKeys.ProjMethod), 
type, "");
             }
             /*
              * Compare the parameter values with the ones declared in the EPSG 
geodetic dataset.
@@ -1442,11 +1465,11 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
             final ParameterValueGroup parameters = 
projection.getParameterValues();
             for (final short key : remainingKeys()) {
                 final Unit<?> unit;
-                switch (GeoKeys.unitOf(key)) {
-                    case GeoKeys.RATIO:   unit = Units.UNITY; break;
-                    case GeoKeys.LINEAR:  unit = linearUnit;  break;
-                    case GeoKeys.ANGULAR: unit = angularUnit; break;
-                    case GeoKeys.AZIMUTH: unit = azimuthUnit; break;
+                switch (UnitKey.ofProjectionParameter(key)) {
+                    case RATIO:     unit = Units.UNITY; break;
+                    case PROJECTED: unit = linearUnit;  break;
+                    case ANGULAR:   unit = angularUnit; break;
+                    case AZIMUTH:   unit = azimuthUnit; break;
                     default: continue;
                 }
                 try {
@@ -1490,7 +1513,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * may be defined <em>in addition</em> of another CRS. Some GeoTIFF values 
used by this method are:
      *
      * <ul>
-     *   <li>A code given by {@link GeoKeys#VerticalCSType}.</li>
+     *   <li>A code given by {@link GeoKeys#Vertical}.</li>
      *   <li>If above code is {@link GeoCodes#userDefined}, then:<ul>
      *     <li>a name given by {@link GeoKeys#VerticalCitation},</li>
      *     <li>a {@link VerticalDatum} given by {@link 
GeoKeys#VerticalDatum}.</li>
@@ -1504,7 +1527,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
      * @throws FactoryException if an error occurred during objects creation 
with the factories.
      */
     private VerticalCRS createVerticalCRS() throws FactoryException {
-        final int epsg = getAsInteger(GeoKeys.VerticalCSType);
+        final int epsg = getAsInteger(GeoKeys.Vertical);
         switch (epsg) {
             case GeoCodes.undefined: {
                 return null;
@@ -1512,7 +1535,7 @@ final class CRSBuilder extends 
ReferencingFactoryContainer {
             case GeoCodes.userDefined: {
                 final String name = getAsString(GeoKeys.VerticalCitation);
                 final VerticalDatum datum = createVerticalDatum();
-                final Unit<Length> unit = createUnit(GeoKeys.VerticalUnits, 
(short) 0, Length.class, Units.METRE);
+                final Unit<Length> unit = createLinearUnit(UnitKey.VERTICAL);
                 VerticalCS cs = 
CommonCRS.Vertical.MEAN_SEA_LEVEL.crs().getCoordinateSystem();
                 if (!Units.METRE.equals(unit)) {
                     cs = (VerticalCS) CoordinateSystems.replaceLinearUnit(cs, 
unit);
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeys.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeys.java
index 638aba4db6..2740943ba1 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeys.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeys.java
@@ -23,11 +23,25 @@ import org.opengis.referencing.operation.MathTransform;
 
 /**
  * GeoTIFF keys associated to values needed for building {@link 
CoordinateReferenceSystem} instances
- * and {@link MathTransform} "grid to CRS". In this class, field names are 
close to GeoTIFF key names
- * with the {@code "GeoKey"} suffix omitted. For that reason, many of those 
field names do not follow
- * usual Java convention for constants.
+ * and {@link MathTransform} "grid to CRS". In this class, field names are 
GeoTIFF key names, except
+ * for the following departures:
+ *
+ * <ul>
+ *   <li>The {@code "GeoKey"} suffix is omitted for all keys<.</li>
+ *   <li>The {@code "Proj"} prefix is omitted for all map projection 
parameters.
+ *       The resulting map projection parameter names are the same as 
published on
+ *       <a href="http://geotiff.maptools.org/proj_list/";>Map Tools projection 
list</a>.</li>
+ *   <li>The ellipsoid axis lengths and inverse flattening factor have the 
{@code Ellipsoid} prefix omitted.</li>
+ * </ul>
+ *
+ * Because of this convention, the field names do not follow usual Java 
convention for constants.
+ *
+ * <p>The current version of this class uses GeoTIFF 1.1 names.
+ * When they differ from GeoTIFF 1.0, the old name is written in comment.
+ * Key names may be changed again in any future version if the GeoTIFF 
specification changes.</p>
  *
  * @author  Rémi Maréchal (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  */
 final class GeoKeys {
     /**
@@ -36,90 +50,60 @@ final class GeoKeys {
     private GeoKeys() {
     }
 
-    // 6.2.1 GeoTIFF Configuration Keys
-    /** Section 6.3.1.1 Codes. */ public static final short ModelType          
  = 1024;
-    /** Section 6.3.1.2 Codes. */ public static final short RasterType         
  = 1025;
-    /** Documentation.         */ public static final short Citation           
  = 1026;
+    // GeoTIFF Configuration Keys
+    /** CRS type.              */ public static final short ModelType          
    = 1024;
+    /** Pixel area or point.   */ public static final short RasterType         
    = 1025;
+    /** Documentation.         */ public static final short Citation           
    = 1026;
 
-    // 6.2.2 Geographic CS Parameter Keys
-    /** Section 6.3.2.1 Codes. */ public static final short GeographicType     
  = 2048;
-    /** Documentation.         */ public static final short GeogCitation       
  = 2049;
-    /** Section 6.3.2.2 Codes. */ public static final short GeodeticDatum      
  = 2050;
-    /** Section 6.3.2.4 codes. */ public static final short PrimeMeridian      
  = 2051;
-    /** Section 6.3.1.3 Codes. */ public static final short GeogLinearUnits    
  = 2052;
-    /** Relative to meters.    */ public static final short GeogLinearUnitSize 
  = 2053;
-    /** Section 6.3.1.4 Codes. */ public static final short AngularUnits       
  = 2054;
-    /** Relative to radians.   */ public static final short AngularUnitSize    
  = 2055;
-    /** Section 6.3.2.3 Codes. */ public static final short Ellipsoid          
  = 2056;
-    /** In GeogLinearUnits.    */ public static final short SemiMajorAxis      
  = 2057;
-    /** In GeogLinearUnits.    */ public static final short SemiMinorAxis      
  = 2058;
-    /** A ratio.               */ public static final short InvFlattening      
  = 2059;
-    /** Section 6.3.1.4 Codes. */ public static final short AzimuthUnits       
  = 2060;
-    /** In AngularUnit.        */ public static final short PrimeMeridianLong  
  = 2061;
+    // Geographic CRS Parameter Keys
+    /** EPSG code.             */ public static final short GeodeticCRS        
    = 2048;    // Was `GeographicType`
+    /** Documentation.         */ public static final short GeodeticCitation   
    = 2049;    // Was `GeogCitation`
+    /** For user-defined CRS.  */ public static final short GeodeticDatum      
    = 2050;    // Was `GeogGeodeticDatum`
+    /** For user-defined CRS.  */ public static final short PrimeMeridian      
    = 2051;    // Was `GeogPrimeMeridian`
+    /** For geodetic axes.     */ public static final short GeogLinearUnits    
    = 2052;    // Actually geodetic, not necessarily geographic.
+    /** Relative to meters.    */ public static final short GeogLinearUnitSize 
    = 2053;
+    /** For geodetic axes.     */ public static final short GeogAngularUnits   
    = 2054;
+    /** Relative to radians.   */ public static final short 
GeogAngularUnitSize    = 2055;
+    /** For user-defined CRS.  */ public static final short Ellipsoid          
    = 2056;    // Was `GeogEllipsoid`
+    /** In GeogLinearUnits.    */ public static final short SemiMajorAxis      
    = 2057;    // Was `GeogSemiMajorAxis`
+    /** In GeogLinearUnits.    */ public static final short SemiMinorAxis      
    = 2058;    // Was `GeogSemiMinorAxis`
+    /** A ratio.               */ public static final short InvFlattening      
    = 2059;    // Was `GeogInvFlattening`
+    /** For some parameters.   */ public static final short GeogAzimuthUnits   
    = 2060;
+    /** In GeogAngularUnits.   */ public static final short 
PrimeMeridianLongitude = 2061;    // Was `GeogPrimeMeridianLong`
 
-    // 6.2.3 Projected CS Parameter Keys
-    /** Section 6.3.3.1 codes. */ public static final short ProjectedCSType    
  = 3072;
-    /** Documentation.         */ public static final short PCSCitation        
  = 3073;
-    /** Section 6.3.3.2 codes. */ public static final short Projection         
  = 3074;
-    /** Section 6.3.3.3 codes. */ public static final short CoordTrans         
  = 3075;
-    /** Section 6.3.1.3 codes. */ public static final short LinearUnits        
  = 3076;
-    /** Relative to meters.    */ public static final short LinearUnitSize     
  = 3077;
-    /** In AngularUnit.        */ public static final short StdParallel1       
  = 3078;    // First projection parameter
-    /** In AngularUnit.        */ public static final short StdParallel2       
  = 3079;
-    /** In AngularUnit.        */ public static final short NatOriginLong      
  = 3080;
-    /** In AngularUnit.        */ public static final short NatOriginLat       
  = 3081;
-    /** In LinearUnits.        */ public static final short FalseEasting       
  = 3082;
-    /** In LinearUnits.        */ public static final short FalseNorthing      
  = 3083;
-    /** In AngularUnit.        */ public static final short FalseOriginLong    
  = 3084;
-    /** In AngularUnit.        */ public static final short FalseOriginLat     
  = 3085;
-    /** In LinearUnits.        */ public static final short FalseOriginEasting 
  = 3086;
-    /** In LinearUnits.        */ public static final short 
FalseOriginNorthing  = 3087;
-    /** In AngularUnit.        */ public static final short CenterLong         
  = 3088;
-    /** In AngularUnit.        */ public static final short CenterLat          
  = 3089;
-    /** In LinearUnits.        */ public static final short CenterEasting      
  = 3090;
-    /** In LinearUnits.        */ public static final short CenterNorthing     
  = 3091;
-    /** A ratio.               */ public static final short ScaleAtNatOrigin   
  = 3092;
-    /** A ratio.               */ public static final short ScaleAtCenter      
  = 3093;
-    /** In AzimuthUnit.        */ public static final short AzimuthAngle       
  = 3094;
-    /** In AngularUnit.        */ public static final short 
StraightVertPoleLong = 3095;
-    /** In AzimuthUnit.        */ public static final short RectifiedGridAngle 
  = 3096;    // Last projection parameter (for now)
-
-    // 6.2.4 Vertical CS Keys
-    /** Section 6.3.4.1 codes. */ public static final short VerticalCSType     
  = 4096;
-    /** Documentation.         */ public static final short VerticalCitation   
  = 4097;
-    /** Section 6.3.4.2 codes. */ public static final short VerticalDatum      
  = 4098;
-    /** Section 6.3.1.3 codes. */ public static final short VerticalUnits      
  = 4099;
-
-    /**
-     * Enumeration of return values for the {@link #unitOf(short)} method.
-     */
-    static final int RATIO = 0, LINEAR = 1, ANGULAR = 2, AZIMUTH = 3;
+    // Projected CRS Parameter Keys, omitting "Proj" prefix in map projection 
parameters
+    /** EPSG code.             */ public static final short ProjectedCRS       
    = 3072;    // Was `ProjectedCSType`
+    /** Documentation.         */ public static final short ProjectedCitation  
    = 3073;    // Was `PCSCitation`
+    /** For user-defined CRS.  */ public static final short Projection         
    = 3074;
+    /** For user-defined CRS.  */ public static final short ProjMethod         
    = 3075;    // Was `ProjCoordTrans`
+    /** For projected axes.    */ public static final short ProjLinearUnits    
    = 3076;
+    /** Relative to meters.    */ public static final short ProjLinearUnitSize 
    = 3077;
+    /** In GeogAngularUnits.   */ public static final short StdParallel1       
    = 3078;    // First projection parameter
+    /** In GeogAngularUnits.   */ public static final short StdParallel2       
    = 3079;
+    /** In GeogAngularUnits.   */ public static final short NatOriginLong      
    = 3080;
+    /** In GeogAngularUnits.   */ public static final short NatOriginLat       
    = 3081;
+    /** In ProjLinearUnits.    */ public static final short FalseEasting       
    = 3082;
+    /** In ProjLinearUnits.    */ public static final short FalseNorthing      
    = 3083;
+    /** In GeogAngularUnits.   */ public static final short FalseOriginLong    
    = 3084;
+    /** In GeogAngularUnits.   */ public static final short FalseOriginLat     
    = 3085;
+    /** In ProjLinearUnits.    */ public static final short FalseOriginEasting 
    = 3086;
+    /** In ProjLinearUnits.    */ public static final short 
FalseOriginNorthing    = 3087;
+    /** In GeogAngularUnits.   */ public static final short CenterLong         
    = 3088;
+    /** In GeogAngularUnits.   */ public static final short CenterLat          
    = 3089;
+    /** In ProjLinearUnits.    */ public static final short CenterEasting      
    = 3090;
+    /** In ProjLinearUnits.    */ public static final short CenterNorthing     
    = 3091;
+    /** A ratio.               */ public static final short ScaleAtNatOrigin   
    = 3092;
+    /** A ratio.               */ public static final short ScaleAtCenter      
    = 3093;
+    /** In GeogAzimuthUnits.   */ public static final short AzimuthAngle       
    = 3094;
+    /** In GeogAngularUnits.   */ public static final short 
StraightVertPoleLong   = 3095;
+    /** In GeogAzimuthUnits.   */ public static final short RectifiedGridAngle 
    = 3096;
+    /** For unit inference.    */ static final short 
LAST_MAP_PROJECTION_PARAMETER = RectifiedGridAngle;
 
-    /**
-     * Returns the unit of measurement for the given map projection parameter.
-     *
-     * @param  key  GeoTIFF key for which to get the unit of associated map 
projection parameter value.
-     * @return one of {@link #RATIO}, {@link #LINEAR}, {@link #ANGULAR}, 
{@link #AZIMUTH} codes,
-     *         or -1 if the given key is not for a map projection parameter.
-     */
-    static int unitOf(final short key) {
-        if (key < StdParallel1 || key > RectifiedGridAngle) {
-            return -1;
-        }
-        switch (key) {
-            case FalseEasting:
-            case FalseNorthing:
-            case FalseOriginEasting:
-            case FalseOriginNorthing:
-            case CenterEasting:
-            case CenterNorthing:     return LINEAR;
-            case ScaleAtNatOrigin:
-            case ScaleAtCenter:      return RATIO;
-            case RectifiedGridAngle: // Note: GDAL seems to use angular unit 
here.
-            case AzimuthAngle:       return AZIMUTH;
-            default:                 return ANGULAR;
-        }
-    }
+    // Vertical CRS Keys
+    /** EPSG code.             */ public static final short Vertical           
    = 4096;    // Was `VerticalCSType`
+    /** Documentation.         */ public static final short VerticalCitation   
    = 4097;
+    /** For user-defined CRS.  */ public static final short VerticalDatum      
    = 4098;
+    /** For vertical axis.     */ public static final short VerticalUnits      
    = 4099;
 
     /**
      * Returns the name of the given key. Implementation of this method is 
inefficient,
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeysLoader.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeysLoader.java
index 76297ecf0b..2a62e2a816 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeysLoader.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoKeysLoader.java
@@ -62,7 +62,7 @@ import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
  * The "|" character is converted to a null delimiter at the end in C/C++ 
libraries.</p>
  *
  * <p>Going further down the list, the key 2051 ({@code GeogLinearUnitSize}) 
is located in {@code GeoDoubleParams(34736)}
- * at offset 0 and has the value 1.5; the value of key 2049 ({@code 
GeogCitation}) is "My Geographic".</p>
+ * at offset 0 and has the value 1.5; the value of key 2049 ({@code 
GeodeticCitation}) is "My Geographic".</p>
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -71,7 +71,7 @@ class GeoKeysLoader {
     /**
      * Number of {@code short} values in each GeoKey entry.
      */
-    private static final int ENTRY_LENGTH = 4;
+    static final int ENTRY_LENGTH = 4;
 
     /**
      * The character used as a separator in {@link String} multi-values.
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/UnitKey.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/UnitKey.java
new file mode 100644
index 0000000000..4aacfb1064
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/UnitKey.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geotiff;
+
+import javax.measure.Unit;
+import org.apache.sis.measure.Units;
+import org.apache.sis.util.Workaround;
+
+
+/**
+ * GeoTIFF keys for storing units of measurement.
+ * The enumeration values are ordered in increasing value of GeoTIFF key codes.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+enum UnitKey {
+    /**
+     * Linear unit in geodetic CRS. Used for
+     * the axes in user-defined geocentric Cartesian CRSs,
+     * the height axis of a user-defined geographic 3D CRS, and
+     * for user-defined ellipsoid axes.
+     *
+     * @see #PROJECTED
+     */
+    LINEAR(GeoKeys.GeogLinearUnits, GeoKeys.GeogLinearUnitSize, true, true, 
false, false),
+
+    /**
+     * Angular unit in geodetic CRS. Used for
+     * the axes in user-defined geographic 2D CRSs,
+     * the horizontal axes in user-defined geographic 3D CRSs,
+     * the longitude from the reference meridian in user-defined prime 
meridians, and
+     * user-defined map projection parameters that are angles.
+     */
+    ANGULAR(GeoKeys.GeogAngularUnits, GeoKeys.GeogAngularUnitSize, true, 
false, true, false),
+
+    /**
+     * Angular unit is <em>some</em> map projection parameters.
+     * Used for angular units for user-defined map projection parameters
+     * when these differ from the angular unit described through {@link 
#ANGULAR}.
+     */
+    AZIMUTH(GeoKeys.GeogAzimuthUnits, (short) 0, false, false, true, false),
+
+    /**
+     * Linear unit in map projections. Used for
+     * the axes of a user-defined projected CRS, and
+     * map projection parameters that are lengths.
+     *
+     * @see #LINEAR
+     */
+    PROJECTED(GeoKeys.ProjLinearUnits, GeoKeys.ProjLinearUnitSize, true, true, 
false, false),
+
+    /**
+     * Unit of axis of a user-defined vertical CRS.
+     */
+    VERTICAL(GeoKeys.VerticalUnits, (short) 0, true, true, false, false),
+
+    /**
+     * Unit of measurement of ratios. There is no GeoTIFF keys associated to 
this unit.
+     */
+    RATIO((short) 0, (short) 0, false, false, false, true),
+
+    /**
+     * Workaround for null return value. To be removed after upgrade to JDK 21.
+     *
+     * @see <a hre="https://openjdk.org/jeps/441";>JEP 441</a>
+     */
+    @Workaround(library="JDK", version="17", fixed="21")
+    NULL((short) 0, (short) 0, false, false, false, false);
+
+    /**
+     * The {@link GeoKeys} for a unit of measurement defined by an EPSG code, 
or 0 if none.
+     */
+    final short codeKey;
+
+    /**
+     * The {@link GeoKeys} for a unit of measurement defined by a scale 
applied on a base unit, or 0 if none.
+     */
+    final short scaleKey;
+
+    /**
+     * Whether the unit may be associated to coordinate system axes.
+     */
+    final boolean isAxis;
+
+    /**
+     * Whether the key accepts linear, angular or scalar units.
+     */
+    private final boolean linear, angular, scalar;
+
+    /**
+     * Creates a enumeration value.
+     *
+     * @param  codeKey   {@link GeoKeys} for a unit defined by an EPSG code, 
or 0 if none.
+     * @param  scaleKey  {@link GeoKeys} for a unit defined by a scale applied 
on a base unit, or 0 if none.
+     * @param  isAxis    whether the unit may be associated to coordinate 
system axes.
+     */
+    private UnitKey(short codeKey, short scaleKey, boolean isAxis, boolean 
linear, boolean angular, boolean scalar) {
+        this.codeKey  = codeKey;
+        this.scaleKey = scaleKey;
+        this.isAxis   = isAxis;
+        this.linear   = linear;
+        this.angular  = angular;
+        this.scalar   = scalar;
+    }
+
+    /**
+     * Returns the unit of measurement for the given map projection parameter.
+     * The returned value should be one of {@link #RATIO}, {@link #PROJECTED},
+     * {@link #ANGULAR} or {@link #AZIMUTH} enumeration values.
+     *
+     * If the given parameter is not a map projection parameter, then this 
method returns
+     * {@link #LINEAR} if the parameter is a semi-axis length, or {@link 
#NULL} otherwise.
+     *
+     * @param  key  GeoTIFF key for which to get the unit of associated map 
projection parameter value.
+     * @return the unit of measurement of the map projection parameter, or 
{@link #LINEAR} or {@link #NULL}
+     *         if the given parameter is not a map projection parameter.
+     */
+    static UnitKey ofProjectionParameter(final short key) {
+        switch (key) {
+            case GeoKeys.SemiMajorAxis:
+            case GeoKeys.SemiMinorAxis:      return LINEAR;
+            case GeoKeys.FalseEasting:
+            case GeoKeys.FalseNorthing:
+            case GeoKeys.FalseOriginEasting:
+            case GeoKeys.FalseOriginNorthing:
+            case GeoKeys.CenterEasting:
+            case GeoKeys.CenterNorthing:     return PROJECTED;
+            case GeoKeys.ScaleAtNatOrigin:
+            case GeoKeys.ScaleAtCenter:      return RATIO;
+            case GeoKeys.RectifiedGridAngle: // Note: GDAL seems to use 
angular unit here.
+            case GeoKeys.AzimuthAngle:       return AZIMUTH;
+            default: {
+                if (key >= GeoKeys.StdParallel1 && key <= 
GeoKeys.LAST_MAP_PROJECTION_PARAMETER) {
+                    return ANGULAR;
+                } else {
+                    return NULL;
+                }
+            }
+        }
+    }
+
+    /**
+     * Verifies that the given unit is compatible with the quantity expected 
by this key.
+     * This method may propose an alternative key for the given unit.
+     *
+     * @param  unit  unit of measurement of an axis of a geodetic CRS.
+     * @return the key to use for the specified unit, or {@code null} if none.
+     */
+    UnitKey validate(final Unit<?> unit) {
+        if ((linear  && Units.isLinear (unit)) ||
+            (angular && Units.isAngular(unit)) ||
+            (scalar  && Units.isScale  (unit)))
+        {
+            return this;
+        }
+        switch (this) {
+            case LINEAR:  if (Units.isAngular(unit)) return ANGULAR; else 
break;
+            case ANGULAR: if (Units.isLinear (unit)) return LINEAR;  else 
break;
+        }
+        return null;
+    }
+
+    /**
+     * {@return the default unit of measurement, or {@code null} if none}.
+     */
+    Unit<?> defaultUnit() {
+        if (linear)  return Units.METRE;
+        if (angular) return Units.DEGREE;
+        if (scalar)  return Units.UNITY;
+        return null;
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoKeysTest.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoKeysTest.java
index f99cd81b2f..e3611298da 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoKeysTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoKeysTest.java
@@ -123,7 +123,7 @@ public final class GeoKeysTest extends TestCase {
         try {
             return GeoKeys.class.getField(name).getShort(null);
         } catch (ReflectiveOperationException e) {
-            throw new IllegalArgumentException(e);
+            throw new AssertionError(e);
         }
     }
 }

Reply via email to