| /* |
| * 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.internal.netcdf; |
| |
| import java.util.List; |
| import java.util.Arrays; |
| import java.io.IOException; |
| import java.time.Instant; |
| import org.apache.sis.math.Vector; |
| import org.apache.sis.util.Workaround; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.internal.netcdf.ucar.DecoderWrapper; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.test.DependsOn; |
| import org.opengis.test.dataset.TestData; |
| import org.junit.Test; |
| |
| import static org.opengis.test.Assert.*; |
| |
| |
| /** |
| * Tests the {@link Variable} implementation. The default implementation tests |
| * {@code org.apache.sis.internal.netcdf.ucar.VariableWrapper} since the UCAR |
| * library is our reference implementation. However subclasses can override the |
| * {@link #createDecoder(TestData)} method in order to test a different implementation. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * @since 0.3 |
| * @module |
| */ |
| @DependsOn(DecoderTest.class) |
| public strictfp class VariableTest extends TestCase { |
| /** |
| * Expected number of columns per variables for the {@code expected} argument |
| * given to the {@link #assertBasicPropertiesEqual(Object[], Variable[])} method. |
| */ |
| private static final int NUM_BASIC_PROPERTY_COLUMNS = 5; |
| |
| /** |
| * Whether the {@code "runtime"} variable in {@link TestData#NETCDF_4D_PROJECTED} is considered an axis or not. |
| * The UCAR library considers it as an axis because it has an {@code "_CoordinateAxisType"} attribute. |
| * Apache SIS does not consider it as an axis because that variable does not match any dimension and is not used |
| * in any other variable. |
| */ |
| protected boolean isRuntimeAnAxis; |
| |
| /** |
| * Creates a new test. |
| */ |
| public VariableTest() { |
| isRuntimeAnAxis = true; |
| } |
| |
| /** |
| * Gets the variable from the given decoder, reordering them if the decoder is a wrapper for UCAR library. |
| * We perform this reordering because UCAR library does not always return the variables in the order they |
| * are declared. In the case of the {@link TestData#NETCDF_4D_PROJECTED} file, the CIP variable is expected |
| * last but UCAR library puts it second. |
| */ |
| @Workaround(library = "UCAR", version = "4.6.11") |
| private Variable[] getVariablesCIP(final Decoder decoder) { |
| Variable[] variables = decoder.getVariables(); |
| if (decoder instanceof DecoderWrapper) { |
| variables = variables.clone(); |
| final Variable cip = variables[1]; |
| final int last = variables.length - 1; |
| System.arraycopy(variables, 2, variables, 1, last - 1); |
| variables[last] = cip; |
| } |
| return variables; |
| } |
| |
| /** |
| * Tests the basic properties of all variables found in the {@link TestData#NETCDF_4D_PROJECTED} file. |
| * The tested methods are: |
| * |
| * <ul> |
| * <li>{@link Variable#getName()}</li> |
| * <li>{@link Variable#getDescription()}</li> |
| * <li>{@link Variable#getDataType()}</li> |
| * <li>{@link Variable#getGridDimensions()} length</li> |
| * <li>{@link Variable#getRole()}</li> |
| * </ul> |
| * |
| * @throws IOException if an I/O error occurred while opening the file. |
| * @throws DataStoreException if a logical error occurred. |
| */ |
| @Test |
| public void testBasicProperties() throws IOException, DataStoreException { |
| assertBasicPropertiesEqual(new Object[] { |
| // __name______________description_____________________datatype_______dim__role |
| "grid_mapping_0", null, DataType.INT, 0, VariableRole.OTHER, |
| "x0", "projection_x_coordinate", DataType.FLOAT, 1, VariableRole.AXIS, |
| "y0", "projection_y_coordinate", DataType.FLOAT, 1, VariableRole.AXIS, |
| "z0", "Flight levels in 100s of feet", DataType.FLOAT, 1, VariableRole.AXIS, |
| "time", "Data time", DataType.DOUBLE, 1, VariableRole.AXIS, |
| "runtime", "Data generation time", DataType.DOUBLE, 1, isRuntimeAnAxis ? VariableRole.AXIS : VariableRole.OTHER, |
| "CIP", "Current Icing Product", DataType.FLOAT, 4, VariableRole.COVERAGE |
| }, getVariablesCIP(selectDataset(TestData.NETCDF_4D_PROJECTED))); |
| } |
| |
| /** |
| * Compares the basic properties of the given variables. |
| * |
| * @param expected the expected property values. |
| * @param variables the variable for which to test properties. |
| */ |
| private static void assertBasicPropertiesEqual(final Object[] expected, final Variable[] variables) { |
| int propertyIndex = 0; |
| for (final Variable variable : variables) { |
| final String name = variable.getName(); |
| final DataType dataType = variable.getDataType(); |
| assertFalse("Too many variables.", propertyIndex == expected.length); |
| assertEquals(name, expected[propertyIndex++], name); |
| assertEquals(name, expected[propertyIndex++], variable.getDescription()); |
| assertEquals(name, expected[propertyIndex++], dataType); |
| assertEquals(name, expected[propertyIndex++], variable.getGridDimensions().size()); |
| assertEquals(name, expected[propertyIndex++], variable.getRole()); |
| assertEquals(0, propertyIndex % NUM_BASIC_PROPERTY_COLUMNS); // Sanity check for VariableTest itself. |
| } |
| assertEquals("Expected more variables.", |
| expected.length / NUM_BASIC_PROPERTY_COLUMNS, |
| propertyIndex / NUM_BASIC_PROPERTY_COLUMNS); |
| } |
| |
| /** |
| * Tests {@link Variable#parseUnit(String)} method. |
| * |
| * @throws Exception if an I/O or logical error occurred while opening the file. |
| */ |
| @Test |
| public void testParseUnit() throws Exception { |
| final Variable variable = selectDataset(TestData.NETCDF_2D_GEOGRAPHIC).getVariables()[0]; |
| /* |
| * From CF-Conventions: |
| * The recommended unit of latitude is degrees_north. Also acceptable are degree_north, degree_N, degrees_N, degreeN, and degreesN. |
| * The recommended unit of longitude is degrees_east. Also acceptable are degree_east, degree_E, degrees_E, degreeE, and degreesE. |
| */ |
| assertSame(Units.DEGREE, variable.parseUnit("degrees_north")); |
| assertSame(Units.DEGREE, variable.parseUnit("degrees_east")); |
| assertSame(Units.DEGREE, variable.parseUnit("degree_north")); |
| assertSame(Units.DEGREE, variable.parseUnit("degree_east")); |
| assertSame(Units.DEGREE, variable.parseUnit("degrees_N")); |
| assertSame(Units.DEGREE, variable.parseUnit("degrees_E")); |
| assertSame(Units.DEGREE, variable.parseUnit("degree_N")); |
| assertSame(Units.DEGREE, variable.parseUnit("degree_E")); |
| assertSame(Units.DEGREE, variable.parseUnit("degreesN")); |
| assertSame(Units.DEGREE, variable.parseUnit("degreesE")); |
| assertSame(Units.DEGREE, variable.parseUnit("degreeN")); |
| assertSame(Units.DEGREE, variable.parseUnit("degreeE")); |
| |
| assertSame(Units.SECOND, variable.parseUnit("s")); |
| assertSame(Units.SECOND, variable.parseUnit("second")); |
| assertSame(Units.SECOND, variable.parseUnit("seconds")); |
| assertSame(Units.MINUTE, variable.parseUnit("min")); |
| assertSame(Units.MINUTE, variable.parseUnit("minute")); |
| assertSame(Units.MINUTE, variable.parseUnit("minutes")); |
| assertSame(Units.HOUR, variable.parseUnit("h")); |
| assertSame(Units.HOUR, variable.parseUnit("hr")); |
| assertSame(Units.HOUR, variable.parseUnit("hour")); |
| assertSame(Units.HOUR, variable.parseUnit("hours")); |
| assertSame(Units.DAY, variable.parseUnit("d")); |
| assertSame(Units.DAY, variable.parseUnit("day")); |
| assertSame(Units.DAY, variable.parseUnit("days")); |
| /* |
| * Parsing date set the epoch as a side effect. |
| */ |
| final Instant save = variable.epoch; |
| try { |
| assertSame(Units.DAY, variable.parseUnit("days since 1992-10-8 15:15:42.5 -06:00")); |
| assertEquals("epoch", variable.epoch, Instant.parse("1992-10-08T21:15:42.500Z")); |
| } finally { |
| variable.epoch = save; |
| } |
| } |
| |
| /** |
| * Returns the dimension names. |
| */ |
| private static String[] names(final List<Dimension> dimensions) { |
| return dimensions.stream().map(Dimension::getName).toArray(String[]::new); |
| } |
| |
| /** |
| * Returns the dimension lengths. |
| */ |
| private static long[] lengths(final List<Dimension> dimensions) { |
| return dimensions.stream().mapToLong(Dimension::length).toArray(); |
| } |
| |
| /** |
| * Tests {@link Variable#getGridDimensions()} on a simple two-dimensional dataset. |
| * |
| * @throws IOException if an I/O error occurred while opening the file. |
| * @throws DataStoreException if a logical error occurred. |
| */ |
| @Test |
| public void testGridRange2D() throws IOException, DataStoreException { |
| final Variable variable = selectDataset(TestData.NETCDF_2D_GEOGRAPHIC).getVariables()[0]; |
| assertEquals("SST", variable.getName()); |
| |
| final List<Dimension> dimensions = variable.getGridDimensions(); |
| assertArrayEquals("getGridDimensionNames()", new String[] { |
| "lat", "lon" |
| }, names(dimensions)); |
| |
| assertArrayEquals("getGridEnvelope()", new long[] { |
| 73, 73 |
| }, lengths(dimensions)); |
| } |
| |
| /** |
| * Tests {@link Variable#getGridDimensions()} on a compound four-dimensional dataset. |
| * |
| * @throws IOException if an I/O error occurred while opening the file. |
| * @throws DataStoreException if a logical error occurred. |
| */ |
| @Test |
| public void testGridRange4D() throws IOException, DataStoreException { |
| final Variable variable = getVariablesCIP(selectDataset(TestData.NETCDF_4D_PROJECTED))[6]; |
| assertEquals("CIP", variable.getName()); |
| |
| final List<Dimension> dimensions = variable.getGridDimensions(); |
| assertArrayEquals("getGridDimensionNames()", new String[] { |
| "time", "z0", "y0", "x0" |
| }, names(dimensions)); |
| |
| assertArrayEquals("getGridEnvelope()", new long[] { |
| 1, 4, 19, 38 |
| }, lengths(dimensions)); |
| } |
| |
| /** |
| * Tests {@link Variable#getAttributeValue(String)} and related methods. |
| * |
| * @throws IOException if an I/O error occurred while opening the file. |
| * @throws DataStoreException if a logical error occurred. |
| */ |
| @Test |
| public void testGetAttributes() throws IOException, DataStoreException { |
| final Variable[] variables = getVariablesCIP(selectDataset(TestData.NETCDF_4D_PROJECTED)); |
| Variable variable = variables[6]; |
| assertEquals("CIP", variable.getName()); |
| assertSingletonEquals(variable, "_FillValue", -1f); |
| assertSingletonEquals(variable, "units", "%"); |
| |
| variable = variables[0]; |
| assertEquals("grid_mapping_0", variable.getName()); |
| assertVectorEquals(variable, "standard_parallel", 25.f, 25.05f); |
| } |
| |
| /** |
| * Asserts that the attribute of given name contains a value equals to the expected value. |
| * This method is used for attributes that are expected to contain singleton. |
| */ |
| private static void assertSingletonEquals(final Node variable, final String name, final Object expected) { |
| final String t = expected.toString(); |
| assertEquals ("getAttributeValue", expected, variable.getAttributeValue (name)); |
| assertEquals ("getAttributeAsString", t, variable.getAttributeAsString (name)); |
| assertArrayEquals("getAttributeAsStrings", new String[] {t}, variable.getAttributeAsStrings(name, ' ')); |
| if (expected instanceof Number) { |
| final double en = ((Number) expected).doubleValue(); |
| assertEquals("getAttributeAsNumber", en, variable.getAttributeAsNumber(name), STRICT); |
| final Vector vector = variable.getAttributeAsVector(name); |
| assertNotNull("getAttributeAsVector", vector); |
| assertEquals(1, vector.size()); |
| assertEquals(en, vector.get(0).doubleValue(), STRICT); |
| } else { |
| assertNull("getAttributeAsVector", variable.getAttributeAsVector(name)); |
| } |
| } |
| |
| /** |
| * Asserts that the attribute of given name contains a value equals to the expected value. |
| * This method is used for attributes that are expected to contain vector. |
| */ |
| private static void assertVectorEquals(final Node variable, final String name, final Number... expected) { |
| final Vector values = variable.getAttributeAsVector(name); |
| assertNotNull(name, values); |
| assertEquals ("size", expected.length, values.size()); |
| assertTrue ("getAttributeAsNumber", Double.isNaN(variable.getAttributeAsNumber(name))); |
| assertEquals ("getAttributeValue", values, variable.getAttributeValue(name)); |
| final Object[] texts = Arrays.stream(expected).map(Object::toString).toArray(); |
| assertArrayEquals("getAttributeAsStrings", texts, variable.getAttributeAsStrings(name, ' ')); |
| } |
| |
| /** |
| * Tests {@link Variable#read()} on a one-dimensional variable. |
| * |
| * @throws IOException if an error occurred while reading the netCDF file. |
| * @throws DataStoreException if a logical error occurred. |
| */ |
| @Test |
| public void testRead1D() throws IOException, DataStoreException { |
| final Variable variable = selectDataset(TestData.NETCDF_2D_GEOGRAPHIC).getVariables()[2]; |
| assertEquals("lon", variable.getName()); |
| final Vector data = variable.read(); |
| assertEquals("lon", Float.class, data.getElementType()); |
| final int length = data.size(); |
| assertEquals("length", 73, length); |
| for (int i=0; i<length; i++) { |
| assertEquals("Longitude value", -180 + 5*i, data.floatValue(i), 0f); |
| } |
| } |
| } |