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); } } }