You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2019/04/06 15:57:10 UTC
[sis] 03/04: If the CRS in a netCDF file seems to be a map
projection but the Grid class does not have enough information for building
the right map projection,
creates an "Not specified" projection. This "not specified" projection may
be replaced by a projection parsed from Well Known Text at a later stage
during the netCDF reading process,
but creating an instance in in Grid class allow us to preserve information
like mapping from CRS axes to grid axes.
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 f5fa5b8779ddc94f6747f9356f41133b94d9267e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 6 17:30:09 2019 +0200
If the CRS in a netCDF file seems to be a map projection but the Grid class does not have enough information for building the right map projection, creates an "Not specified" projection.
This "not specified" projection may be replaced by a projection parsed from Well Known Text at a later stage during the netCDF reading process, but creating an instance in in Grid class allow us to preserve information like mapping from CRS axes to grid axes.
---
.../referencing/provider/Equirectangular.java | 11 +-
.../referencing/operation/DefaultConversion.java | 2 +-
.../org/apache/sis/internal/util/Constants.java | 14 -
.../java/org/apache/sis/internal/netcdf/Axis.java | 35 +-
.../org/apache/sis/internal/netcdf/CRSBuilder.java | 377 ++++++++++++++-------
.../org/apache/sis/internal/netcdf/Decoder.java | 15 +-
.../java/org/apache/sis/internal/netcdf/Grid.java | 11 +-
.../apache/sis/internal/netcdf/GridMapping.java | 5 +-
.../apache/sis/internal/netcdf/impl/GridInfo.java | 14 +-
.../internal/netcdf/ucar/CSBuilderFallback.java | 5 +-
.../sis/internal/netcdf/ucar/GridWrapper.java | 6 +-
.../org/apache/sis/internal/netcdf/GridTest.java | 4 +-
12 files changed, 327 insertions(+), 172 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
index ea2d742..033a241 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Equirectangular.java
@@ -61,7 +61,7 @@ import static java.lang.Math.*;
*
* @author John Grange
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
*
* @see PseudoPlateCarre
* @see <a href="http://geotiff.maptools.org/proj_list/equirectangular.html">GeoTIFF parameters for Equirectangular</a>
@@ -76,6 +76,13 @@ public final class Equirectangular extends AbstractProvider {
*/
private static final long serialVersionUID = -278288251842178001L;
+ /**
+ * Name of this projection in EPSG geodetic dataset.
+ *
+ * @todo Remove with JDK9 after we introduce {@code getInstance()} method.
+ */
+ public static final String NAME = "Equidistant Cylindrical (Spherical)";
+
/*
* ACCESS POLICY: Only formal EPSG parameters shall be public.
* Parameters that we add ourselves should be package-privated.
@@ -182,7 +189,7 @@ public final class Equirectangular extends AbstractProvider {
// Do not declare the ESRI "Equidistant_Cylindrical" projection name below,
// for avoiding confusion with EPSG "Equidistant Cylindrical" ellipsoidal projection.
PARAMETERS = addIdentifierAndLegacy(builder, "1029", "9823") // 9823 uses deprecated parameter names
- .addName( "Equidistant Cylindrical (Spherical)")
+ .addName( NAME)
.addName( "Plate Carrée") // Not formally defined by EPSG, but cited in documentation.
.addName(Citations.OGC, "Equirectangular")
.addName(Citations.ESRI, "Plate_Carree")
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
index ca98dd6..4e1ebd0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConversion.java
@@ -190,7 +190,7 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver
* The semi-major and semi-minor parameter values will be set automatically when the
* {@link #specialize specialize(…)} method will be invoked.</p>
*
- * <p>If both the {@code transform} and {@code parameters} arguments are non-null, then the later should describes
+ * <p>If both the {@code transform} and {@code parameters} arguments are non-null, then the later should describe
* the parameters used for creating the transform. Those parameters will be stored for information purpose and can
* be given back by the {@link #getParameterValues()} method.</p>
*
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
index d089889..afd1b0d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
@@ -241,20 +241,6 @@ public final class Constants extends Static {
public static final short EPSG_ANTARCTIC_POLAR_STEREOGRAPHIC = 3031;
/**
- * EPSG code of "Unknown datum based upon the WGS 84 ellipsoid".
- * This is a two-dimensional geographic CRS.
- * Note that the EPSG database defines unknown CRS for many other ellipsoids.
- * For now only the WGS 84 case is used by Apache SIS.
- */
- public static final short EPSG_UNKNOWN_CRS = 4030;
-
- /**
- * EPSG code of "Not specified (based upon the WGS 84 ellipsoid)".
- * This is a geodetic datum.
- */
- public static final short EPSG_UNKNOWN_DATUM = 6030;
-
- /**
* Do not allow instantiation of this class.
*/
private Constants() {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index f034fa4..67e602b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -384,7 +384,7 @@ public final class Axis extends NamedElement {
/**
* Returns {@code true} if the given axis specifies the same direction and unit of measurement than this axis.
- * This is used for testing is a predefined axis can be used instead than invoking {@link #toISO(CSFactory)}.
+ * This is used for testing if a predefined axis can be used instead than invoking {@link #toISO(CSFactory, int)}.
*/
final boolean isSameUnitAndDirection(final CoordinateSystemAxis axis) {
if (!axis.getDirection().equals(direction)) {
@@ -467,9 +467,10 @@ public final class Axis extends NamedElement {
* Creates an ISO 19111 axis from the information stored in this netCDF axis.
*
* @param factory the factory to use for creating the coordinate system axis.
+ * @param order 0 if creating the first axis, 1 if creating the second axis, <i>etc</i>.
* @return the ISO axis.
*/
- final CoordinateSystemAxis toISO(final CSFactory factory) throws FactoryException {
+ final CoordinateSystemAxis toISO(final CSFactory factory, final int order) throws FactoryException {
/*
* The axis name is stored without namespace, because the variable name in a netCDF file can be anything;
* this is not controlled vocabulary. However the standard name, if any, is stored with "NetCDF" namespace
@@ -505,28 +506,40 @@ public final class Axis extends NamedElement {
/*
* Axis abbreviation, direction and unit of measurement are mandatory. If any of them is null,
* creation of CoordinateSystemAxis is likely to fail with an InvalidGeodeticParameterException.
- * We provide default values for the most well-accepted values and leave other values to null.
+ * We provide default values for the most well-identified axes and leave other values to null.
* Those null values can be accepted if users specify their own factory.
+ *
+ * The default values are SI base units except degrees, which is the usually angular units for netCDF files.
+ * Providing default units is a little bit dangerous, but we can not create CRS otherwise. Note that wrong
+ * defaults become harmless if the CRS is overwritten by GridMapping attributes in Variable.getGridGeometry().
*/
Unit<?> unit = getUnit();
if (unit == null) {
switch (abbreviation) {
- /*
- * TODO: consider moving those default values in a separated class,
- * for example a netCDF-specific CSFactory, for allowing users to override.
- */
- case 'λ': case 'φ': unit = Units.DEGREE; break;
+ case 'λ': case 'φ': // Geodetic longitude and latitude.
+ case 'θ': case 'Ω': unit = Units.DEGREE; break; // Spherical longitude and latitude.
+ case 'r': case 'D': // Depth and radius.
+ case 'H': case 'h': // Gravity-related and ellipsoidal height.
+ case 'E': case 'N': unit = Units.METRE; break; // Projected easting and northing.
+ case 't': unit = Units.SECOND; break; // Time.
+ }
+ }
+ AxisDirection dir = direction;
+ if (dir == null) {
+ switch (order) {
+ case 0: dir = AxisDirection.COLUMN_POSITIVE; break;
+ case 1: dir = AxisDirection.ROW_POSITIVE; break;
}
}
final String abbr;
if (abbreviation != 0) {
abbr = Character.toString(abbreviation).intern();
- } else if (direction != null && unit != null) {
- abbr = AxisDirections.suggestAbbreviation(name, direction, unit);
+ } else if (dir != null && unit != null) {
+ abbr = AxisDirections.suggestAbbreviation(name, dir, unit);
} else {
abbr = null;
}
- return factory.createCoordinateSystemAxis(properties, abbr, direction, unit);
+ return factory.createCoordinateSystemAxis(properties, abbr, dir, unit);
}
/**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index f097c65..3a62463 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -33,17 +33,22 @@ import org.opengis.referencing.datum.*;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.GeocentricCRS;
import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.operation.CoordinateOperationFactory;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.Conversion;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.CoordinateSystems;
-import org.apache.sis.referencing.cs.DefaultSphericalCS;
-import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
import org.apache.sis.referencing.crs.AbstractCRS;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.referencing.crs.DefaultGeographicCRS;
+import org.apache.sis.referencing.crs.DefaultGeocentricCRS;
+import org.apache.sis.internal.referencing.provider.Equirectangular;
+import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.util.TemporalUtilities;
-import org.apache.sis.internal.util.Constants;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.measure.Units;
import org.apache.sis.math.Vector;
@@ -75,9 +80,13 @@ import org.apache.sis.math.Vector;
abstract class CRSBuilder<D extends Datum, CS extends CoordinateSystem> {
/**
* The coordinate reference system which is presumed the basis of datum on netCDF files.
- * Note: if this default is changed, search also for "WGS 84" strings in this class.
+ * Note: if this default is changed, search also for "GRS 1980" strings in this class.
+ *
+ * <div class="note"><b>Note:</b> we use GRS 1980 instead than WGS 84 because the CRS name
+ * clearly said "Unknown datum based upon the GRS 1980 ellipsoid" and for consistency with
+ * {@link CommonCRS#SPHERE}, which also use GRS 1980.</div>
*/
- private static final CommonCRS DEFAULT = CommonCRS.WGS84;
+ private static final CommonCRS DEFAULT = CommonCRS.GRS1980;
/**
* The type of datum as a GeoAPI sub-interface of {@link Datum}.
@@ -87,7 +96,7 @@ abstract class CRSBuilder<D extends Datum, CS extends CoordinateSystem> {
/**
* Name of the datum on which the CRS is presumed to be based, or {@code ""}. This is used
- * for building a datum name like <cite>"Unknown datum presumably based on WGS 84"</cite>.
+ * for building a datum name like <cite>"Unknown datum presumably based on GRS 1980"</cite>.
*/
private final String datumBase;
@@ -121,17 +130,17 @@ abstract class CRSBuilder<D extends Datum, CS extends CoordinateSystem> {
/**
* The datum created by {@link #createDatum(DatumFactory, Map)}.
*/
- D datum;
+ protected D datum;
/**
* The coordinate system created by {@link #createCS(CSFactory, Map, CoordinateSystemAxis[])}.
*/
- CS coordinateSystem;
+ protected CS coordinateSystem;
/**
- * The coordinate reference system that may have been create by {@link #candidate(Decoder)}.
+ * The coordinate reference system that may have been create by {@link #setPredefinedComponents(Decoder)}.
*/
- SingleCRS referenceSystem;
+ protected SingleCRS referenceSystem;
/**
* Non-fatal exceptions that may occur while building the coordinate reference system.
@@ -278,12 +287,11 @@ previous: for (int i=components.size(); --i >= 0;) {
* set the datum, CS and CRS field values to those candidate. Those values do not need to be exact; they
* will be overwritten later if they do not match the netCDF file content.
*/
- datum = datumType.cast(decoder.datumCache[datumIndex]); // Should be initialized before 'candidate' call.
- candidate(decoder);
+ datum = datumType.cast(decoder.datumCache[datumIndex]); // Should be before 'setPredefinedComponents' call.
+ setPredefinedComponents(decoder);
/*
- * If 'candidate(decoder)' offers a datum, we will used it as-is. Otherwise create the datum now.
- * Datum are often not defined in netCDF files, so it will usually be EPSG::6030 — "Not specified
- * (based on WGS 84 ellipsoid)".
+ * If 'setPredefinedComponents(decoder)' offers a datum, we will used it as-is. Otherwise create the datum now.
+ * Datum are often not defined in netCDF files, so we use EPSG::6019 — "Not specified (based on GRS 1980 ellipsoid)".
*/
if (datum == null) {
// Not localized because stored as a String, possibly exported in WKT or GML, and 'datumBase' is in English.
@@ -306,8 +314,8 @@ previous: for (int i=components.size(); --i >= 0;) {
}
}
/*
- * If 'candidate(decoder)' did not proposed a coordinate system, or if it proposed a CS but its
- * axes do not match the axes in the netCDF file, then create a new coordinate system here.
+ * If 'setPredefinedComponents(decoder)' did not proposed a coordinate system, or if it proposed a CS
+ * but its axes do not match the axes in the netCDF file, then create a new coordinate system here.
*/
if (referenceSystem == null) {
final Map<String,?> properties;
@@ -319,7 +327,7 @@ previous: for (int i=components.size(); --i >= 0;) {
for (int i=0; i<iso.length; i++) {
final Axis axis = axes[i];
joiner.add(axis.getName());
- iso[i] = axis.toISO(csFactory);
+ iso[i] = axis.toISO(csFactory, i);
}
createCS(csFactory, properties(joiner.toString()), iso);
properties = properties(coordinateSystem.getName());
@@ -356,9 +364,9 @@ previous: for (int i=components.size(); --i >= 0;) {
}
/**
- * Reports a non-fatal exception that may occur when processing the value returned by {@link #epsgCandidate(Unit)}.
- * In order to avoid repeating the same warning many times, this method collects the warnings together and reports
- * them in a single log record after we finished creating the CRS.
+ * Reports a non-fatal exception that may occur during {@link #setPredefinedComponents(Decoder)}.
+ * In order to avoid repeating the same warning many times, this method collects the warnings
+ * together and reports them in a single log record after we finished creating the CRS.
*/
final void recoverableException(final NoSuchAuthorityCodeException e) {
if (warnings == null) warnings = e;
@@ -378,12 +386,14 @@ previous: for (int i=components.size(); --i >= 0;) {
* Returns the EPSG code of a possible coordinate system from EPSG database. This method proceed by brief
* inspection of axis directions and units; there is no guarantees that the coordinate system returned by
* this method match the axes defined in the netCDF file. It is caller's responsibility to verify.
- * This is a helper method for {@link #candidate(Decoder)} implementations.
+ * This is a helper method for {@link #setPredefinedComponents(Decoder)} implementations.
*
* @param defaultUnit the unit to use if unit definition is missing in the netCDF file.
* @return EPSG code of a CS candidate, or {@code null} if none.
+ *
+ * @see Geodetic#isPredefinedCS(Unit)
*/
- final Integer epsgCandidate(final Unit<?> defaultUnit) {
+ final Integer epsgCandidateCS(final Unit<?> defaultUnit) {
Unit<?> unit = getFirstAxis().getUnit();
if (unit == null) unit = defaultUnit;
final AxisDirection[] directions = new AxisDirection[dimension];
@@ -402,11 +412,11 @@ previous: for (int i=components.size(); --i >= 0;) {
* <p>This method may opportunistically set the {@link #datum} and {@link #referenceSystem} fields if it
* can propose a CRS candidate instead than only a CS candidate.</p>
*/
- abstract void candidate(Decoder decoder) throws FactoryException;
+ abstract void setPredefinedComponents(Decoder decoder) throws FactoryException;
/**
* Creates the datum for the coordinate reference system to build. The datum are generally not specified in netCDF files.
- * To make that clearer, this method builds datum with names like <cite>"Unknown datum presumably based on WGS 84"</cite>.
+ * To make that clearer, this method builds datum with names like <cite>"Unknown datum presumably based on GRS 1980"</cite>.
* The newly created datum is assigned to the {@link #datum} field.
*
* @param factory the factory to use for creating the datum.
@@ -435,20 +445,33 @@ previous: for (int i=components.size(); --i >= 0;) {
*/
abstract void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException;
+
+
+
/**
* Base classes of {@link Spherical}, {@link Geographic} and {@link Projected} builders.
* They all have in common to be based on a {@link GeodeticDatum}.
*/
private abstract static class Geodetic<CS extends CoordinateSystem> extends CRSBuilder<GeodeticDatum, CS> {
- /** Whether the coordinate system has longitude before latitude. */
- boolean isLongitudeFirst;
+ /**
+ * Whether the coordinate system has longitude before latitude.
+ * This flag is set as a side-effect of {@link #isPredefinedCS(Unit)} method call.
+ */
+ protected boolean isLongitudeFirst;
- /** For subclasses constructors. */
+ /**
+ * For subclasses constructors.
+ *
+ * @param minDim minimum number of dimensions (2 or 3).
+ */
Geodetic(final byte minDim) {
- super(GeodeticDatum.class, "WGS 84", (byte) 0, minDim, (byte) 3);
+ super(GeodeticDatum.class, "GRS 1980", (byte) 0, minDim, (byte) 3);
}
- /** Creates a {@link GeodeticDatum} for <cite>"Unknown datum based on WGS 84"</cite>. */
+ /**
+ * Creates a {@link GeodeticDatum} for <cite>"Unknown datum based on GRS 1980"</cite>.
+ * This method is invoked only if {@link #setPredefinedComponents(Decoder)} failed to create a datum.
+ */
@Override final void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
final GeodeticDatum template = DEFAULT.datum();
datum = factory.createGeodeticDatum(properties, template.getEllipsoid(), template.getPrimeMeridian());
@@ -461,8 +484,10 @@ previous: for (int i=components.size(); --i >= 0;) {
* If {@code true}, then {@link #isLongitudeFirst} will have been set to an indication of axis order.
*
* @param expected the expected unit of measurement of the first axis.
+ *
+ * @see #epsgCandidateCS(Unit)
*/
- final boolean isPredefined(final Unit<?> expected) {
+ final boolean isPredefinedCS(final Unit<?> expected) {
final Axis axis = getFirstAxis();
final Unit<?> unit = axis.getUnit();
if (unit == null || expected.equals(unit)) {
@@ -473,103 +498,109 @@ previous: for (int i=components.size(); --i >= 0;) {
}
return false;
}
-
- /**
- * If the {@link #datum} field is not already set, initialize it to "Not specified (based upon the WGS 84 ellipsoid)".
- * Subclasses should override this method for setting also the {@link #coordinateSystem} and {@link #referenceSystem}
- * fields if possible.
- */
- @Override void candidate(final Decoder decoder) throws FactoryException {
- if (datum == null) try {
- datum = decoder.getDatumAuthorityFactory().createGeodeticDatum(String.valueOf(Constants.EPSG_UNKNOWN_DATUM));
- } catch (NoSuchAuthorityCodeException e) {
- recoverableException(e);
- }
- }
}
+
+
+
/**
* Builder for geocentric CRS with (θ,Ω,r) axes.
*/
private static final class Spherical extends Geodetic<SphericalCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Spherical() {
super((byte) 3);
}
- /** Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file. */
- @Override void candidate(final Decoder decoder) throws FactoryException {
- super.candidate(decoder);
- final Integer epsg = epsgCandidate(Units.DEGREE);
- if (epsg != null) try {
- coordinateSystem = decoder.getCSAuthorityFactory().createSphericalCS(epsg.toString());
- return;
- } catch (NoSuchAuthorityCodeException e) {
- recoverableException(e);
- }
- if (isPredefined(Units.DEGREE)) {
- coordinateSystem = (SphericalCS) DEFAULT.spherical().getCoordinateSystem();
+ /**
+ * Possibly sets {@link #datum} and {@link #coordinateSystem} to predefined objects
+ * matching the axes defined in the netCDF file.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) throws FactoryException {
+ if (isPredefinedCS(Units.DEGREE)) {
+ GeocentricCRS crs = DEFAULT.spherical();
if (isLongitudeFirst) {
- coordinateSystem = DefaultSphericalCS.castOrCopy(coordinateSystem).forConvention(AxesConvention.RIGHT_HANDED);
+ crs = DefaultGeocentricCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
}
+ referenceSystem = crs;
+ coordinateSystem = (SphericalCS) crs.getCoordinateSystem();
+ datum = crs.getDatum();
+ } else {
+ datum = DEFAULT.datum();
}
}
- /** Creates the three-dimensional {@link SphericalCS} from given axes. */
+ /**
+ * Creates the three-dimensional {@link SphericalCS} from given axes. This method is invoked only
+ * if {@link #setPredefinedComponents(Decoder)} failed to assign a CS or if {@link #build(Decoder)}
+ * found that the {@link #coordinateSystem} does not have compatible axes.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
coordinateSystem = factory.createSphericalCS(properties, axes[0], axes[1], axes[2]);
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ * This method is invoked under conditions similar to the ones of above {@code createCS(…)} method.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
referenceSystem = factory.createGeocentricCRS(properties, datum, coordinateSystem);
}
- };
+ }
+
+
+
/**
* Geographic CRS with (λ,φ,h) axes.
* The height, if present, is ellipsoidal height.
*/
private static final class Geographic extends Geodetic<EllipsoidalCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Geographic() {
super((byte) 2);
}
- /** Tries to creates the coordinate system from EPSG code. */
- private boolean tryEPSG(final Decoder decoder) throws FactoryException {
- super.candidate(decoder); // Initialize the datum.
- final Integer epsg = epsgCandidate(Units.DEGREE);
- if (epsg != null) try {
- coordinateSystem = decoder.getCSAuthorityFactory().createEllipsoidalCS(epsg.toString());
- return true;
- } catch (NoSuchAuthorityCodeException e) {
- recoverableException(e);
- }
- return false;
- }
-
- /** Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file. */
- @Override void candidate(final Decoder decoder) throws FactoryException {
- if (isPredefined(Units.DEGREE)) {
- if (!is3D()) {
- GeographicCRS crs = decoder.getCRSAuthorityFactory().createGeographicCRS(String.valueOf(Constants.EPSG_UNKNOWN_CRS));
- coordinateSystem = crs.getCoordinateSystem();
- datum = crs.getDatum();
- referenceSystem = crs;
- } else if (!tryEPSG(decoder)) {
- coordinateSystem = DEFAULT.geographic3D().getCoordinateSystem();
- }
- if (isLongitudeFirst) {
- coordinateSystem = DefaultEllipsoidalCS.castOrCopy(coordinateSystem).forConvention(AxesConvention.RIGHT_HANDED);
- referenceSystem = null;
+ /**
+ * Possibly sets {@link #datum}, {@link #coordinateSystem} and {@link #referenceSystem}
+ * to predefined objects matching the axes defined in the netCDF file.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) throws FactoryException {
+ if (isPredefinedCS(Units.DEGREE)) {
+ GeographicCRS crs;
+ if (is3D()) {
+ crs = DEFAULT.geographic3D();
+ if (isLongitudeFirst) {
+ crs = DefaultGeographicCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
+ }
+ } else if (isLongitudeFirst) {
+ crs = DEFAULT.normalizedGeographic();
+ } else {
+ crs = DEFAULT.geographic();
}
+ referenceSystem = crs;
+ coordinateSystem = crs.getCoordinateSystem();
+ datum = crs.getDatum();
} else {
- tryEPSG(decoder);
+ datum = DEFAULT.datum();
+ final Integer epsg = epsgCandidateCS(Units.DEGREE);
+ if (epsg != null) try {
+ coordinateSystem = decoder.getCSAuthorityFactory().createEllipsoidalCS(epsg.toString());
+ } catch (NoSuchAuthorityCodeException e) {
+ recoverableException(e);
+ }
}
}
- /** Creates the two- or three-dimensional {@link EllipsoidalCS} from given axes. */
+ /**
+ * Creates the two- or three-dimensional {@link EllipsoidalCS} from given axes. This method is invoked only if
+ * {@link #setPredefinedComponents(Decoder)} failed to assign a coordinate system or if {@link #build(Decoder)}
+ * found that the {@link #coordinateSystem} does not have compatible axes.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
if (axes.length > 2) {
coordinateSystem = factory.createEllipsoidalCS(properties, axes[0], axes[1], axes[2]);
@@ -578,29 +609,71 @@ previous: for (int i=components.size(); --i >= 0;) {
}
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ * This method is invoked under conditions similar to the ones of above {@code createCS(…)} method.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
- referenceSystem = factory.createGeographicCRS(properties, datum, coordinateSystem);
+ referenceSystem = factory.createGeographicCRS(properties, datum, coordinateSystem);
}
- };
+ }
+
+
+
/**
- * Projected CRS with (E,N,h) axes.
+ * Projected CRS with (E,N,h) axes. There is not enough information in a netCDF files for creating the right
+ * map projection, unless {@code "grid_mapping"} attributes are specified. If insufficient information, this
+ * class creates an unknown map projection based on Plate Carrée. Note that this map projection may be replaced
+ * by {@link GridMapping#crs} at a later stage.
*/
private static final class Projected extends Geodetic<CartesianCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * The spherical variant of {@link CRSBuilder#DEFAULT}.
+ * Currently based upon the GRS 1980 Authalic Sphere.
+ */
+ private static final CommonCRS SPHERICAL = CommonCRS.SPHERE;
+
+ /**
+ * Defining conversion for "Not specified (presumed Plate Carrée)". This conversion use spherical formulas.
+ * Consequently it should be used with {@link #SPHERICAL} instead of {@link CommonCRS#DEFAULT}.
+ */
+ private static final Conversion UNKNOWN_PROJECTION;
+ static {
+ final CoordinateOperationFactory factory = DefaultFactories.forBuildin(CoordinateOperationFactory.class);
+ try {
+ final OperationMethod method = factory.getOperationMethod(Equirectangular.NAME);
+ UNKNOWN_PROJECTION = factory.createDefiningConversion(
+ properties("Not specified (presumed Plate Carrée)"),
+ method, method.getParameters().createValue());
+ } catch (FactoryException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Projected() {
super((byte) 2);
}
- /** Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file. */
- @Override void candidate(final Decoder decoder) {
- if (isPredefined(Units.METRE)) {
- coordinateSystem = DEFAULT.universal(0,0).getCoordinateSystem();
+ /**
+ * Possibly sets {@link #datum}, {@link #coordinateSystem} and {@link #referenceSystem}
+ * to predefined objects matching the axes defined in the netCDF file.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) throws FactoryException {
+ datum = SPHERICAL.datum();
+ if (isPredefinedCS(Units.METRE)) {
+ coordinateSystem = CommonCRS.WGS84.universal(0,0).getCoordinateSystem();
}
}
- /** Creates the two- or three-dimensional {@link CartesianCS} from given axes. */
+ /**
+ * Creates the two- or three-dimensional {@link CartesianCS} from given axes. This method is invoked only if
+ * {@link #setPredefinedComponents(Decoder)} failed to assign a coordinate system or if {@link #build(Decoder)}
+ * found that the {@link #coordinateSystem} does not have compatible axes.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
if (axes.length > 2) {
coordinateSystem = factory.createCartesianCS(properties, axes[0], axes[1], axes[2]);
@@ -609,24 +682,38 @@ previous: for (int i=components.size(); --i >= 0;) {
}
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ * The datum for this method is based upon the GRS 1980 Authalic Sphere.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
- throw new UnsupportedOperationException(); // TODO
+ GeographicCRS baseCRS = (coordinateSystem.getDimension() >= 3) ? SPHERICAL.geographic3D() : SPHERICAL.geographic();
+ if (!baseCRS.getDatum().equals(datum)) {
+ baseCRS = factory.createGeographicCRS(properties, datum, baseCRS.getCoordinateSystem());
+ }
+ referenceSystem = factory.createProjectedCRS(properties, baseCRS, UNKNOWN_PROJECTION, coordinateSystem);
}
- };
+ }
+
+
+
/**
* Vertical CRS with (H) or (D) axis.
* Used for mean sea level (not for ellipsoidal height).
*/
private static final class Vertical extends CRSBuilder<VerticalDatum, VerticalCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Vertical() {
super(VerticalDatum.class, "Mean Sea Level", (byte) 1, (byte) 1, (byte) 1);
}
- /** Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file. */
- @Override void candidate(final Decoder decoder) {
+ /**
+ * Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) {
final Axis axis = getFirstAxis();
final Unit<?> unit = axis.getUnit();
final CommonCRS.Vertical predefined;
@@ -644,34 +731,49 @@ previous: for (int i=components.size(); --i >= 0;) {
coordinateSystem = predefined.crs().getCoordinateSystem();
}
- /** Creates a {@link VerticalDatum} for <cite>"Unknown datum based on Mean Sea Level"</cite>. */
+ /**
+ * Creates a {@link VerticalDatum} for <cite>"Unknown datum based on Mean Sea Level"</cite>.
+ */
@Override void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
datum = factory.createVerticalDatum(properties, VerticalDatumType.GEOIDAL);
}
- /** Creates the one-dimensional {@link VerticalCS} from given axes. */
+ /**
+ * Creates the one-dimensional {@link VerticalCS} from given axes. This method is invoked
+ * only if {@link #setPredefinedComponents(Decoder)} failed to assign a coordinate system
+ * or if {@link #build(Decoder)} found that the axis or direction are not compatible.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
coordinateSystem = factory.createVerticalCS(properties, axes[0]);
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
referenceSystem = factory.createVerticalCRS(properties, datum, coordinateSystem);
}
- };
+ }
+
+
+
/**
* Temporal CRS with (t) axis. Its datum need to be built
* in a special way since it contains the time origin.
*/
private static final class Temporal extends CRSBuilder<TemporalDatum, TimeCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Temporal() {
super(TemporalDatum.class, "", (byte) 2, (byte) 1, (byte) 1);
}
- /** Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file. */
- @Override void candidate(final Decoder decoder) {
+ /**
+ * Possibly sets {@link #coordinateSystem} to a predefined CS matching the axes defined in the netCDF file.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) {
final Axis axis = getFirstAxis();
final Unit<?> unit = axis.getUnit();
final CommonCRS.Temporal predefined;
@@ -687,7 +789,9 @@ previous: for (int i=components.size(); --i >= 0;) {
coordinateSystem = predefined.crs().getCoordinateSystem();
}
- /** Creates a {@link VerticalDatum} for <cite>"Unknown datum based on …"</cite>. */
+ /**
+ * Creates a {@link TemporalDatum} for <cite>"Unknown datum based on …"</cite>.
+ */
@Override void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
final Axis axis = getFirstAxis();
axis.getUnit(); // Force epoch parsing if not already done.
@@ -701,37 +805,54 @@ previous: for (int i=components.size(); --i >= 0;) {
}
}
- /** Creates the one-dimensional {@link TimeCS} from given axes. */
+ /**
+ * Creates the one-dimensional {@link TimeCS} from given axes. This method is invoked only
+ * if {@link #setPredefinedComponents(Decoder)} failed to assign a coordinate system or if
+ * {@link #build(Decoder)} found that the axis or direction are not compatible.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
coordinateSystem = factory.createTimeCS(properties, axes[0]);
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
properties = properties(getFirstAxis().coordinates.getUnitsString());
referenceSystem = factory.createTemporalCRS(properties, datum, coordinateSystem);
}
- };
+ }
+
+
+
/**
* Unknown CRS with (x,y,z) axes.
*/
private static final class Engineering extends CRSBuilder<EngineeringDatum, AffineCS> {
- /** Creates a new builder (invoked by lambda function). */
+ /**
+ * Creates a new builder (invoked by lambda function).
+ */
public Engineering() {
super(EngineeringDatum.class, "affine coordinate system", (byte) 3, (byte) 2, (byte) 3);
}
- /** No-op since we have no predefined engineering CRS. */
- @Override void candidate(final Decoder decoder) {
+ /**
+ * No-op since we have no predefined engineering CRS.
+ */
+ @Override void setPredefinedComponents(final Decoder decoder) {
}
- /** Creates a {@link VerticalDatum} for <cite>"Unknown datum based on affine coordinate system"</cite>. */
+ /**
+ * Creates a {@link VerticalDatum} for <cite>"Unknown datum based on affine coordinate system"</cite>.
+ */
@Override void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
datum = factory.createEngineeringDatum(properties);
}
- /** Creates two- or three-dimensional {@link AffineCS} from given axes. */
+ /**
+ * Creates two- or three-dimensional {@link AffineCS} from given axes.
+ */
@Override void createCS(CSFactory factory, Map<String,?> properties, CoordinateSystemAxis[] axes) throws FactoryException {
if (axes.length > 2) {
coordinateSystem = factory.createAffineCS(properties, axes[0], axes[1], axes[2]);
@@ -740,11 +861,13 @@ previous: for (int i=components.size(); --i >= 0;) {
}
}
- /** Creates the coordinate reference system from datum and coordinate system computed in previous steps. */
+ /**
+ * Creates the coordinate reference system from datum and coordinate system computed in previous steps.
+ */
@Override void createCRS(CRSFactory factory, Map<String,?> properties) throws FactoryException {
referenceSystem = factory.createEngineeringCRS(properties, datum, coordinateSystem);
}
- };
+ }
/**
* Maximal {@link #datumIndex} value +1. The maximal value can be seen in the call to {@code super(…)} constructor
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 3084826..1732715 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -96,7 +96,7 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
/**
* The geodetic datum, created when first needed. The datum are generally not specified in netCDF files.
- * To make that clearer, we will build datum with names like "Unknown datum presumably based on WGS 84".
+ * To make that clearer, we will build datum with names like "Unknown datum presumably based on GRS 1980".
*
* @see CRSBuilder#build(Decoder)
*/
@@ -332,8 +332,8 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
/**
* Returns for information purpose only the Coordinate Reference Systems present in this file.
- * The CRS returned by this method may not be exactly the CRS to be used by variables.
- * This method is provided for metadata purposes.
+ * The CRS returned by this method may not be exactly the same than the ones used by variables.
+ * For example, axis order is not guaranteed. This method is provided for metadata purposes.
*
* @return coordinate reference systems present in this file.
* @throws IOException if an I/O operation was necessary but failed.
@@ -347,6 +347,11 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
addIfNotPresent(list, m.crs);
}
}
+ /*
+ * Add the CRS computed by grids only if we did not found any grid mapping information.
+ * This is because grid mapping information override the CRS inferred by Grid from axes.
+ * Consequently if such information is present, grid CRS may be inaccurate.
+ */
if (list.isEmpty()) {
for (final Grid grid : getGrids()) {
addIfNotPresent(list, grid.getCoordinateReferenceSystem(this));
@@ -357,7 +362,9 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
/**
* Adds the given coordinate reference system to the given list, provided that an equivalent CRS
- * (ignoring axes) is not already present.
+ * (ignoring axes) is not already present. We ignore axes because the same CRS may be repeated
+ * with different axis order if values in the localization grid do not vary at the same speed in
+ * the same directions.
*/
private static void addIfNotPresent(final List<CoordinateReferenceSystem> list, final CoordinateReferenceSystem crs) {
if (crs != null) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
index 7cc2401..461cdf8 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
@@ -260,8 +260,15 @@ public abstract class Grid extends NamedElement {
protected abstract boolean containsAllNamedAxes(String[] axisNames);
/**
- * Returns the coordinate reference system, or {@code null} if none.
- * This method creates the CRS the first time it is invoked and cache the result.
+ * Returns the coordinate reference system inferred from axes, or {@code null} if none.
+ * This method creates the CRS the first time it is invoked and caches the result,
+ * for allowing {@link Decoder#getReferenceSystemInfo()} to be cheaper.
+ *
+ * <p>This CRS is inferred only from analysis of grid axes. It does not take in account {@link GridMapping} information.
+ * This CRS may be overwritten by another CRS parsed from Well Known Text or other attributes. This overwriting is done
+ * by {@link Variable#getGridGeometry()}. But even if the CRS is going to be overwritten, we still need to create it in
+ * this method because this CRS will be used for adjusting axis order or for completion if grid mapping does not include
+ * information for all dimensions.</p>
*
* @param decoder the decoder for which CRS are constructed.
* @return the CRS for this grid geometry, or {@code null}.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
index 3a0b235..09de0b2 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@ -58,7 +58,10 @@ import ucar.nc2.constants.CF;
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
- * @since 1.0
+ *
+ * @see <a href="https://www.unidata.ucar.edu/software/thredds/current/netcdf-java/reference/StandardCoordinateTransforms.html">UCAR projections</a>
+ *
+ * @since 1.0
* @module
*/
final class GridMapping {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
index 46bd7ca..5f02906 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridInfo.java
@@ -54,18 +54,24 @@ final class GridInfo extends Grid {
* Mapping from values of the {@code "_CoordinateAxisType"} attribute or axis name to the abbreviation.
* Keys are lower cases and values are controlled vocabulary documented in {@link Axis#abbreviation}.
*
+ * <div class="note">"GeoX" and "GeoY" stands for projected coordinates, not geocentric coordinates
+ * (<a href="https://www.unidata.ucar.edu/software/thredds/current/netcdf-java/reference/CoordinateAttributes.html#AxisTypes">source</a>).
+ * </div>
+ *
* @see #getAxisType(String)
*/
private static final Map<String,Character> AXIS_TYPES = new HashMap<>(26);
static {
addAxisTypes('λ', "longitude", "lon", "long");
addAxisTypes('φ', "latitude", "lat");
- addAxisTypes('H', "pressure", "height", "altitude", "elevation", "elev");
+ addAxisTypes('H', "pressure", "height", "altitude", "elevation", "elev", "geoz");
addAxisTypes('D', "depth");
+ addAxisTypes('E', "geox");
+ addAxisTypes('N', "geoy");
addAxisTypes('t', "t", "time", "runtime");
- addAxisTypes('x', "x", "geox");
- addAxisTypes('y', "y", "geoy");
- addAxisTypes('z', "z", "geoz");
+ addAxisTypes('x', "x");
+ addAxisTypes('y', "y");
+ addAxisTypes('z', "z");
}
/**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
index 581304e..fb88041 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/CSBuilderFallback.java
@@ -45,7 +45,10 @@ import org.apache.sis.util.CharSequences;
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
- * @since 1.0
+ *
+ * @see <a href="https://www.unidata.ucar.edu/software/thredds/current/netcdf-java/tutorial/CoordSysBuilder.html">UCAR tutorial</a>
+ *
+ * @since 1.0
* @module
*/
final class CSBuilderFallback extends CoordSysBuilder {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
index 767d13e..37d56e4 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridWrapper.java
@@ -241,9 +241,9 @@ next: for (final String name : axisNames) {
char abbreviation = 0;
final AxisType type = axis.getAxisType();
if (type != null) switch (type) {
- case GeoX: abbreviation = 'x'; break;
- case GeoY: abbreviation = 'y'; break;
- case GeoZ: abbreviation = 'z'; break;
+ case GeoX: abbreviation = netcdfCS.isGeoXY() ? 'E' : 'x'; break;
+ case GeoY: abbreviation = netcdfCS.isGeoXY() ? 'N' : 'y'; break;
+ case GeoZ: abbreviation = netcdfCS.isGeoXY() ? 'H' : 'z'; break;
case Lon: abbreviation = 'λ'; break;
case Lat: abbreviation = 'φ'; break;
case Pressure: // Fallthrough: consider as Height
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
index 27d7e8a..c754a42 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
@@ -110,8 +110,8 @@ public strictfp class GridTest extends TestCase {
final Axis z = axes[2];
final Axis t = axes[3];
- assertEquals('x', x.abbreviation);
- assertEquals('y', y.abbreviation);
+ assertEquals('E', x.abbreviation);
+ assertEquals('N', y.abbreviation);
assertEquals('H', z.abbreviation);
assertEquals('t', t.abbreviation);