blob: d13313afa36da0c1d86166f6215141bc0b681704 [file] [log] [blame]
/*
* 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);
}
}
}