/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.image.io.plugin;
import java.io.File;
import java.io.IOException;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.DataBuffer;
import javax.measure.Unit;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.geometry.Envelopes;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.io.ImageCoverageReader;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.image.io.DimensionSlice;
import org.geotoolkit.image.io.SpatialImageReadParam;
import org.geotoolkit.image.io.metadata.SpatialMetadata;
import org.apache.sis.referencing.CommonCRS;
import org.junit.*;
import static org.geotoolkit.test.Assert.*;
import static org.geotoolkit.test.Commons.*;
import static org.geotoolkit.image.io.metadata.SpatialMetadataFormat.GEOTK_FORMAT_NAME;
import static org.apache.sis.measure.Units.METRE;
import static org.apache.sis.measure.Units.DAY;
import static org.apache.sis.measure.Units.DEGREE;
/**
* Tests using the {@code "World/Coriolis/OA_RTQCGL01_20070606_FLD_TEMP.nc"} file.
* This test class queries many different aspects of the same file. The data are
* loaded using:
* <p>
* <ul>
* <li>{@link NetcdfImageReader}</li>
* <li>{@link ImageCoverageReader} wrapping an {@code NetcdfImageReader}</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.08
*/
public final strictfp class CoriolisFormatTest extends NetcdfImageReaderTestBase {
/**
* The directory which contains the data used by the tests.
*
* @see #getTestFile()
* @see CoriolisFormatTest#getTestFile()
*/
static final String DIRECTORY = "World/Coriolis/";
/**
* The file to be used for the tests.
*
* @see #getTestFile()
*/
private static final String FILENAME = "OA_RTQCGL01_20070606_FLD_TEMP.nc";
/**
* The name of variables in the {@value #FILENAME} file.
*
*/
private static final String[] VARIABLE_NAMES = new String[] {"temperature", "pct_variance"};
/**
* The size of the grid in each dimension. This field is public for usage by
* {@link org.geotoolkit.referencing.adapters.NetcdfCRSTest}. Do not modify.
*/
public static final int[] GRID_SIZE = new int[] {720, 499, 59, 1};
/**
* Name of axes in the {@value #FILENAME} file.
*
* @see #assertExpectedAxes(CoordinateSystem, boolean)
*/
private static final String[] AXIS_NAMES = new String[] {"longitude", "latitude", "depth", "time"},
PROJECTED_AXIS_NAMES = new String[] {"Easting", "Northing", "depth", "time"};
/**
* Abbreviations of axes in the {@value #FILENAME} file.
*
* @see #assertExpectedAxes(CoordinateSystem, boolean)
*/
private static final String[] AXIS_ABBREVIATIONS = new String[] {"λ", "φ", "d", "t"},
PROJECTED_AXIS_ABBREVIATIONS = new String[] {"E", "N", "d", "t"};
/**
* Directions of axes in the {@value #FILENAME} file.
*
* @see #assertExpectedAxes(CoordinateSystem, boolean)
*/
private static final AxisDirection[] AXIS_DIRECTIONS = new AxisDirection[] {
AxisDirection.EAST, AxisDirection.NORTH, AxisDirection.DOWN, AxisDirection.FUTURE
};
/**
* Units of axes in the {@value #FILENAME} file.
*
* @see #assertExpectedAxes(CoordinateSystem, boolean)
*/
private static final Unit<?>[] AXIS_UNITS = new Unit<?>[] {DEGREE, DEGREE, METRE, DAY},
PROJECTED_AXIS_UNITS = new Unit<?>[] {METRE, METRE, METRE, DAY};
/**
* The first part of expected metadata (without the sample dimensions).
*/
private static final String EXPECTED_METADATA =
GEOTK_FORMAT_NAME + '\n' +
"├───RectifiedGridDomain\n" +
"│ ├───origin=“-1.9959489E7 -1.3843768E7 5.0 20975.0”\n" +
"│ ├───Limits\n" +
"│ │ ├───low=“0 0 0 0”\n" +
"│ │ └───high=“719 498 58 0”\n" +
"│ ├───OffsetVectors\n" +
"│ │ ├───OffsetVector\n" +
"│ │ │ └───values=“55597.46 0.0 0.0 0.0”\n" +
"│ │ ├───OffsetVector\n" +
"│ │ │ └───values=“0.0 55597.46 0.0 0.0”\n" +
"│ │ ├───OffsetVector\n" +
"│ │ │ └───values=“0.0 0.0 NaN 0.0”\n" +
"│ │ └───OffsetVector\n" +
"│ │ └───values=“0.0 0.0 0.0 NaN”\n" +
"│ └───CoordinateReferenceSystem\n" +
"│ ├───name=“NetCDF:time depth latitude longitude”\n" +
"│ └───CoordinateSystem\n" +
"│ ├───name=“NetCDF:time depth latitude longitude”\n" +
"│ ├───dimension=“4”\n" +
"│ └───Axes\n" +
"│ ├───CoordinateSystemAxis\n" +
"│ │ ├───name=“Easting”\n" +
"│ │ ├───axisAbbrev=“E”\n" +
"│ │ ├───direction=“east”\n" +
"│ │ └───unit=“m”\n" +
"│ ├───CoordinateSystemAxis\n" +
"│ │ ├───name=“Northing”\n" +
"│ │ ├───axisAbbrev=“N”\n" +
"│ │ ├───direction=“north”\n" +
"│ │ └───unit=“m”\n" +
"│ ├───CoordinateSystemAxis\n" +
"│ │ ├───name=“NetCDF:depth”\n" +
"│ │ ├───axisAbbrev=“d”\n" +
"│ │ ├───direction=“down”\n" +
"│ │ ├───minimumValue=“5.0”\n" +
"│ │ ├───maximumValue=“1950.0”\n" +
"│ │ └───unit=“m”\n" +
"│ └───CoordinateSystemAxis\n" +
"│ ├───name=“NetCDF:time”\n" +
"│ ├───axisAbbrev=“t”\n" +
"│ ├───direction=“future”\n" +
"│ ├───minimumValue=“20975.0”\n" +
"│ ├───maximumValue=“20975.0”\n" +
"│ └───unit=“d”\n" +
"├───SpatialRepresentation\n" +
"│ ├───numberOfDimensions=“4”\n" +
"│ └───centerPoint=“27798.73166114092 1.862645149230957E-9 NaN NaN”\n";
/**
* Numbers which were simplified in the above metadata. This simplification
* is performed in order to protect the test suite from slight variations in
* floating point computations.
*/
private static final String[] SIMPLIFIED = {"-1.9959489", "1.3843768", "55597.46"};
/**
* Returns the {@value #FILENAME} test file, which is optional.
* If the test file is not present, the test will be interrupted
* by the JUnit {@link org.junit.Assume} class.
*
* @return The test file (never null).
*/
public static File getTestFile() {
return getLocallyInstalledFile(DIRECTORY + FILENAME);
}
/**
* Creates a reader and initializes its input to the test file defined in
* {@link #getTestFile()}. This method is invoked by each tests inherited
* from the parent class, and by the tests defined in this class.
*/
@Override
protected void prepareImageReader(final boolean setInput) throws IOException {
if (reader == null) {
NetcdfImageReader.Spi spi = new NetcdfImageReader.Spi();
reader = new NetcdfImageReader(spi);
}
if (setInput) {
reader.setInput(getTestFile());
}
}
/**
* Ensures that axes in the given coordinate system have the expected name, abbreviation,
* direction and unit for a geographic or projected CRS.
*
* @param cs The coordinate system to test.
* @param isProjected {@code true} if the CRS is expected to be projected,
* or {@code false} if it is expected to be geographic.
*/
public static void assertExpectedAxes(final CoordinateSystem cs, final boolean isProjected) {
assertNotNull("The coordinate system can't be null.", cs);
final int dimension = cs.getDimension();
final String [] axisNames;
final String [] axisAbbreviations;
final Unit<?>[] axisUnits;
if (isProjected) {
axisNames = PROJECTED_AXIS_NAMES;
axisAbbreviations = PROJECTED_AXIS_ABBREVIATIONS;
axisUnits = PROJECTED_AXIS_UNITS;
} else {
axisNames = AXIS_NAMES;
axisAbbreviations = AXIS_ABBREVIATIONS;
axisUnits = AXIS_UNITS;
}
for (int i=0; i<dimension; i++) {
final CoordinateSystemAxis axis = cs.getAxis(i);
assertEquals("Unexpected axis name.", axisNames [i], axis.getName().getCode());
assertEquals("Unexpected abbreviation.", axisAbbreviations[i], axis.getAbbreviation());
assertEquals("Unexpected axis direction.", AXIS_DIRECTIONS [i], axis.getDirection());
assertEquals("Unexpected axis unit.", axisUnits [i], axis.getUnit());
if (!isProjected) {
assertEquals("Unexpected toString().", "NetCDF:" + AXIS_NAMES[i], axis.toString());
}
}
}
/**
* Removes a few digits to some numbers, in order to protect the test suite from
* slight variation in floating point computation.
*/
private static String simplify(final String tree) {
final StringBuilder buffer = new StringBuilder(tree);
for (final String search : SIMPLIFIED) {
final int length = search.length();
for (int i=buffer.indexOf(search); i>=0; i=buffer.indexOf(search, i)) {
int j = (i += length);
char c;
do c = buffer.charAt(++j);
while (c >= '0' && c <= '9');
buffer.delete(i, j);
}
}
return buffer.toString();
}
/**
* Tests the metadata.
*
* @throws IOException if an error occurred while reading the file.
*/
@Test
@Ignore("Failure because of change in Ellipsoidal.SPHERE radius.")
public void testMetadata() throws IOException {
prepareImageReader(true);
final NetcdfImageReader reader = (NetcdfImageReader) this.reader;
assertArrayEquals(new String[] {"temperature", "pct_variance"}, reader.getImageNames().toArray());
assertEquals( 2, reader.getNumImages(false));
assertEquals( 1, reader.getNumBands (0));
assertEquals( 4, reader.getDimension(0));
assertEquals(720, reader.getWidth (0));
assertEquals(499, reader.getHeight (0));
assertEquals(DataBuffer.TYPE_SHORT, reader.getRawDataType(0));
final SpatialMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
assertMultilinesEquals(decodeQuotes(EXPECTED_METADATA +
"└───ImageDescription\n" +
" └───Dimensions\n" +
" └───Dimension\n" +
" ├───descriptor=“temperature”\n" +
" ├───units=“degree_Celsius”\n" +
" ├───minValue=“-3.0”\n" +
" ├───maxValue=“40.0”\n" +
" ├───validSampleValues=“[-23000 … 20000]”\n" +
" ├───fillSampleValues=“32767.0”\n" +
" ├───scaleFactor=“0.001”\n" +
" ├───offset=“20.0”\n" +
" └───transferFunctionType=“linear”\n"), simplify(metadata.toString()));
}
/**
* Tests the metadata with two named bands.
*
* @throws IOException if an error occurred while reading the file.
*/
@Test
@Ignore("Failure because of change in Ellipsoidal.SPHERE radius.")
public void testMetadataTwoBands() throws IOException {
prepareImageReader(true);
final NetcdfImageReader reader = (NetcdfImageReader) this.reader;
reader.setBandNames(0, "temperature", "pct_variance");
assertEquals( 2, reader.getNumBands (0));
assertEquals( 4, reader.getDimension(0));
assertEquals(720, reader.getWidth (0));
assertEquals(499, reader.getHeight (0));
assertEquals(DataBuffer.TYPE_SHORT, reader.getRawDataType(0));
final SpatialMetadata metadata = reader.getImageMetadata(0);
assertNotNull(metadata);
assertMultilinesEquals(decodeQuotes(EXPECTED_METADATA +
"└───ImageDescription\n" +
" └───Dimensions\n" +
" ├───Dimension\n" +
" │ ├───descriptor=“temperature”\n" +
" │ ├───units=“degree_Celsius”\n" +
" │ ├───minValue=“-3.0”\n" +
" │ ├───maxValue=“40.0”\n" +
" │ ├───validSampleValues=“[-23000 … 20000]”\n" +
" │ ├───fillSampleValues=“32767.0”\n" +
" │ ├───scaleFactor=“0.001”\n" +
" │ ├───offset=“20.0”\n" +
" │ └───transferFunctionType=“linear”\n" +
" └───Dimension\n" +
" ├───descriptor=“pct_variance”\n" +
" ├───units=“percent”\n" +
" ├───minValue=“0.0”\n" +
" ├───maxValue=“1.0”\n" +
" ├───validSampleValues=“[0 … 100]”\n" +
" ├───fillSampleValues=“32767.0”\n" +
" ├───scaleFactor=“0.01”\n" +
" └───transferFunctionType=“linear”\n"), simplify(metadata.toString()));
}
/**
* Tests reading a few sample values at different slices, selected as band index.
*
* @throws IOException if an error occurred while reading the file.
*
* @since 3.15
*/
@Test
public void testReadSliceThroughBandAPI() throws IOException {
prepareImageReader(true);
final NetcdfImageReader reader = (NetcdfImageReader) this.reader;
assertEquals("Unexpected number of variables.", 2, reader.getNumImages(true));
assertEquals("Expected only 1 band by default.", 1, reader.getNumBands(0));
reader.getDimensionForAPI(DimensionSlice.API.BANDS).addDimensionId("depth");
assertEquals("Expected the number of z values.", 59, reader.getNumBands(0));
assertEquals("Number of images shall be unchanged.", 2, reader.getNumImages(true));
assertNull ("Should not be an aggregation.", reader.getAggregatedFiles(0));
assertArrayEquals("Expected the names of the variables found in the NetCDF file.",
new String[] {"temperature", "pct_variance"}, reader.getImageNames().toArray());
/*
* Set the subregion to load.
*/
final SpatialImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(360, 236, 2, 3));
param.setSourceBands(new int[] {0});
Raster data;
/*
* Read data at the first band index, which is 0.
*/
assertArrayEquals(new int[] {0}, param.getSourceBands());
data = reader.readRaster(0, param);
assertEquals(2, data.getWidth());
assertEquals(3, data.getHeight());
assertEquals(1, data.getNumBands());
assertArrayEquals(new int[] {6138, 6120, 6046, 6029, 5941, 5933},
data.getSamples(0, 0, 2, 3, 0, (int[]) null));
/*
* Select a band and read data again.
*/
param.setSourceBands(new int[] {1});
data = reader.readRaster(0, param);
assertEquals(2, data.getWidth());
assertEquals(3, data.getHeight());
assertEquals(1, data.getNumBands());
assertArrayEquals(new int[] {6125, 6124, 6007, 6007, 5880, 5888},
data.getSamples(0, 0, 2, 3, 0, (int[]) null));
}
/**
* Tests reading a few sample values at different slices, selected as image index.
*
* @throws IOException if an error occurred while reading the file.
*
* @since 3.15
*/
@Test
public void testReadSliceThroughImageAPI() throws IOException {
prepareImageReader(true);
final NetcdfImageReader reader = (NetcdfImageReader) this.reader;
assertEquals("Unexpected number of variables.", 2, reader.getNumImages(true));
assertEquals("Expected only 1 band by default.", 1, reader.getNumBands(0));
reader.getDimensionForAPI(DimensionSlice.API.IMAGES).addDimensionId("depth");
assertEquals("Number of bands shall be unchanged.", 1, reader.getNumBands(0));
assertEquals("Expected the number of z values.", 59, reader.getNumImages(true));
assertNull ("Should not be an aggregation.", reader.getAggregatedFiles(0));
assertArrayEquals("Expected the names of the variables found in the NetCDF file.",
new String[] {"temperature", "pct_variance"}, reader.getImageNames().toArray());
/*
* Set the subregion to load.
*/
final SpatialImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(360, 236, 2, 3));
assertNull(param.getSourceBands());
Raster data;
/*
* Read data in the slice z0.
*/
data = reader.readRaster(0, param);
assertEquals(2, data.getWidth());
assertEquals(3, data.getHeight());
assertEquals(1, data.getNumBands());
assertArrayEquals(new int[] {6138, 6120, 6046, 6029, 5941, 5933},
data.getSamples(0, 0, 2, 3, 0, (int[]) null));
/*
* Read data in the slice z1.
*/
data = reader.readRaster(1, param);
assertEquals(2, data.getWidth());
assertEquals(3, data.getHeight());
assertEquals(1, data.getNumBands());
assertArrayEquals(new int[] {6125, 6124, 6007, 6007, 5880, 5888},
data.getSamples(0, 0, 2, 3, 0, (int[]) null));
}
/**
* Tests a {@link ImageCoverageReader#read} operation.
*
* @throws CoverageStoreException If an error occurred while reading the NetCDF file.
* @throws TransformException Should not occur.
*/
@Test
@Ignore("Failure because of change in Ellipsoidal.SPHERE radius.")
public void testCoverageReader() throws CoverageStoreException, TransformException {
final ImageCoverageReader reader = new ImageCoverageReader();
reader.setInput(getTestFile());
assertArrayEquals(VARIABLE_NAMES, toStringArray(reader.getCoverageNames()));
final GridCoverage2D coverage = reader.read(0, null);
assertNotNull(coverage);
reader.dispose();
/*
* Verify the grid coverage.
*/
CoordinateReferenceSystem crs = coverage.getCoordinateReferenceSystem();
assertEquals(4, crs.getCoordinateSystem().getDimension());
assertTrue(crs instanceof CompoundCRS);
crs = ((CompoundCRS) crs).getComponents().get(0);
assertTrue(crs instanceof ProjectedCRS);
/*
* Verify the envelope.
*/
Envelope envelope = coverage.getEnvelope();
assertEquals(-19987288, envelope.getMinimum(0), 1);
assertEquals(-13871567, envelope.getMinimum(1), 1);
envelope = Envelopes.transform(envelope, CommonCRS.SPHERE.normalizedGeographic());
/*
* Note: Coriolis data have a 0.25° offset in longitude. This is a known
* problem of the tested data, not a problem of the Geotk library.
*/
assertEquals(-179.750, envelope.getMinimum(0), 1E-10);
assertEquals( 180.250, envelope.getMaximum(0), 1E-10);
assertEquals( -77.067, envelope.getMinimum(1), 1E-3);
assertEquals( 77.067, envelope.getMaximum(1), 1E-3);
}
}