/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2010-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.referencing.adapters; import java.util.Date; import java.util.List; import java.io.IOException; import ucar.nc2.dataset.NetcdfDataset; import ucar.nc2.dataset.CoordinateSystem; import ucar.nc2.dataset.CoordinateAxis1DTime; import org.opengis.coverage.grid.GridGeometry; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.opengis.referencing.crs.CompoundCRS; import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.crs.VerticalCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.datum.PixelInCell; import org.apache.sis.measure.Range; import org.apache.sis.test.DependsOn; import org.apache.sis.referencing.crs.DefaultTemporalCRS; import org.geotoolkit.referencing.cs.DiscreteReferencingFactory; import org.geotoolkit.referencing.cs.DiscreteCoordinateSystemAxis; import org.geotoolkit.referencing.operation.matrix.GeneralMatrix; import org.apache.sis.referencing.operation.transform.LinearTransform; import org.geotoolkit.internal.image.io.IrregularAxesConverterTest; import org.apache.sis.referencing.operation.matrix.Matrices; import org.junit.*; import static org.opengis.test.Assert.*; import static java.lang.Double.NaN; import static org.geotoolkit.image.io.plugin.CoriolisFormatTest.GRID_SIZE; import static org.geotoolkit.image.io.plugin.CoriolisFormatTest.getTestFile; import static org.geotoolkit.image.io.plugin.CoriolisFormatTest.assertExpectedAxes; /** * Tests the {@link NetcdfCRS} class using the same test file than {@link CoriolisFormatTest}. * In addition, this class inherits all tests defined in the {@code geoapi-netcdf} module. * * @author Martin Desruisseaux (Geomatys) * @version 3.20 * * @since 3.08 */ @DependsOn({NetcdfAxisTest.class, IrregularAxesConverterTest.class}) public final strictfp class NetcdfCRSTest extends org.opengis.wrapper.netcdf.NetcdfCRSTest { /** * Small tolerance factor for floating point comparison. */ private static final double EPS = 1E-10; /** * Wraps the given NetCDF file into the CRS object to test. * This method is invoked by the tests inherited from the {@code geoapi-test} module. * * @param cs The NetCDF coordinate system to wrap. * @param file The originating dataset file, or {@code null} if none. * @return A CRS implementation created from the given NetCDF coordinate system. * @throws IOException If an error occurred while wrapping the given NetCDF coordinate system. */ @Override protected CoordinateReferenceSystem wrap(final CoordinateSystem cs, final NetcdfDataset file) throws IOException { return wrapStatic(cs, file); } /** * Implementation of {@link #wrap(CoordinateSystem, NetcdfDataset)} as a static method. */ static NetcdfCRS wrapStatic(final CoordinateSystem cs, final NetcdfDataset file) throws IOException { final NetcdfCRSBuilder builder = new NetcdfCRSBuilder(file, null); builder.setCoordinateSystem(cs); return builder.getNetcdfCRS(); } /** * Tests the creation of a geographic CRS from the Coriolis format. * This is a "geographic" CRS with non-regular axes. * * @throws IOException If an error occurred while reading the test file. */ @Test public void testCoriolisCRS() throws IOException { final NetcdfDataset data = NetcdfDataset.openDataset(getTestFile().getPath()); assertNotNull("NetcdfDataset shall not be null.", data); try { final List<CoordinateSystem> cs = data.getCoordinateSystems(); assertNotNull("List of CoordinateSystem shall not be null.", cs); assertEquals("Expected exactly one CoordinateSystem.", 1, cs.size()); assertValid(wrapStatic(cs.get(0), null), false, false); assertValid(wrapStatic(cs.get(0), data), false, true); } finally { data.close(); } } /** * Tests the creation of a geographic CRS, which is then made regular. * * @throws IOException If an error occurred while reading the test file. * * @since 3.15 */ @Test @Ignore public void testRegularCRS() throws IOException { final NetcdfDataset data = NetcdfDataset.openDataset(getTestFile().getPath()); try { final List<CoordinateSystem> cs = data.getCoordinateSystems(); final NetcdfCRS geographic = wrapStatic(cs.get(0), data); final CoordinateReferenceSystem projected = geographic.regularize(); assertValid(geographic, false, true); assertValid(projected , true, true); } finally { data.close(); } } /** * Run the test on the following NetCDF wrapper. * * @param crs The NetCDF wrapper to test. * @param isProjected {@code true} if the CRS axes are expected to be projected. * @param hasTimeAxis {@code true} if the 4th dimension is expected to wraps an * instance of {@link CoordinateAxis1DTime}. */ private static void assertValid(final CoordinateReferenceSystem crs, final boolean isProjected, final boolean hasTimeAxis) { final CoordinateReferenceSystem NULL = null; // Only for avoiding a NetBeans warning. assertEquals("The CRS shall be equals to itself.", crs, crs); assertFalse ("The CRS shall not be equals to null.", crs.equals(NULL)); assertValidAxes(crs.getCoordinateSystem(), isProjected, hasTimeAxis); assertValidGridGeometry(crs, isProjected); } /** * Checks that the given coordinate system has the expected axes. * * @param cs The coordinate system to test. * @param isProjected {@code true} if the CRS axes are expected to be projected. * @param hasTimeAxis {@code true} if the 4th dimension is expected to wraps an * instance of {@link CoordinateAxis1DTime}. */ private static void assertValidAxes(final org.opengis.referencing.cs.CoordinateSystem cs, final boolean isProjected, final boolean hasTimeAxis) { assertEquals("Expected a 4-dimensional CRS.", GRID_SIZE.length, cs.getDimension()); assertExpectedAxes(cs, isProjected); for (int i=0; i<GRID_SIZE.length; i++) { /* * For each axis, check the consistency of ordinate values. */ final CoordinateSystemAxis axis = cs.getAxis(i); assertInstanceOf("Expected a discrete axis.", DiscreteCoordinateSystemAxis.class, axis); final DiscreteCoordinateSystemAxis<?> discreteAxis = (DiscreteCoordinateSystemAxis<?>) axis; final int n = discreteAxis.length(); assertEquals("Unexpected number of indices.", GRID_SIZE[i], n); final boolean isTimeAxis = (hasTimeAxis && i == 3); if (isTimeAxis) { final Date first = ((Date) discreteAxis.getOrdinateAt(0)); final Date last = (Date) discreteAxis.getOrdinateAt(n-1); assertFalse("Inconsistent dates.", first.after(last)); } else { final double minimum = axis.getMinimumValue(); final double maximum = axis.getMaximumValue(); final double first = ((Number) discreteAxis.getOrdinateAt(0)).doubleValue(); final double last = ((Number) discreteAxis.getOrdinateAt(n-1)).doubleValue(); assertTrue ("Inconsistent first ordinate.", minimum <= first); assertTrue ("Inconsistent last ordinate.", maximum >= last); if (!isProjected) { assertEquals("Inconsistent first ordinate.", minimum, first, EPS); assertEquals("Inconsistent last ordinate.", maximum, last, EPS); } } final Range<?> r1 = discreteAxis.getOrdinateRangeAt(0); final Range<?> r2 = discreteAxis.getOrdinateRangeAt(n-1); if (n > 1) { assertFalse(r1.intersects((Range) r2)); } final Class<?> elementClass = isTimeAxis ? Date.class : Double.class; assertEquals(elementClass, r1.getElementType()); assertEquals(elementClass, r2.getElementType()); } } /** * Checks that the given CRS has the expected grid geometry. * * @param crs The NetCDF wrapper to test. * @param isProjected {@code true} if the CRS axes are expected to be projected. */ private static void assertValidGridGeometry(final CoordinateReferenceSystem crs, final boolean isProjected) { /* * Check the CRS types. It should be a CompoundCRS. The first component shall be either * geographic and projected, and the last components shall be vertical and temporal. */ assertInstanceOf("Expected a Compound CRS.", CompoundCRS.class, crs); final List<CoordinateReferenceSystem> components = ((CompoundCRS) crs).getComponents(); assertEquals(3, components.size()); if (isProjected) { assertInstanceOf("Expected a Projected CRS.", ProjectedCRS.class, components.get(0)); } else { assertInstanceOf("Expected a Geographic CRS.", GeographicCRS.class, components.get(0)); } assertInstanceOf("Expected a Vertical CRS.", VerticalCRS.class, components.get(1)); assertInstanceOf("Expected a Temporal CRS.", TemporalCRS.class, components.get(2)); /* * Check the epoch of the temporal CRS. */ final DefaultTemporalCRS timeCS = DefaultTemporalCRS.castOrCopy((TemporalCRS) components.get(2)); assertEquals("Expected the 1950-01-01 origin", -20L * 365250 * 24 * 60 * 60, timeCS.getDatum().getOrigin().getTime()); /* * Check the grid geometry. */ assertInstanceOf("Expected a grid geometry.", GridGeometry.class, crs); final GridGeometry gg = (GridGeometry) crs; final GridEnvelope ge = gg.getExtent(); final int[] high = GRID_SIZE.clone(); for (int i=0; i<high.length; i++) { high[i]--; } assertArrayEquals(new int[high.length], ge.getLow() .getCoordinateValues()); assertArrayEquals(high, ge.getHigh().getCoordinateValues()); final MathTransform gridToCRS = gg.getGridToCRS(); final Matrix matrix = DiscreteReferencingFactory.getAffineTransform(crs); if (isProjected) { assertInstanceOf("Expected regular axes.", LinearTransform.class, gridToCRS); /* * The first two lines of the above matrix contain the same offset and scale factors * than the ones in IrregularAxesConverterTest, except for a slight southing offset. * The error (3 metres in the translation term of the y axis) is assumed to be caused * by slightly different input values. */ assertTrue("GridToCRS of a ProjectedCRS", new GeneralMatrix( new double[] {55597, 0, 0, 0, -19959489}, new double[] { 0, 55597, 0, 0, -13843768}, new double[] { 0, 0, NaN, 0, 5}, new double[] { 0, 0, 0, NaN, 20975}, new double[] { 0, 0, 0, 0, 1}).equals(matrix, 1)); } else { assertInstanceOf("Expected an irregular transform.", NetcdfGridToCRS.class, gridToCRS); assertTrue("GridToCRS of a GeographicCRS", new GeneralMatrix( new double[] {NaN, 0, 0, 0, -179.5}, // Actually, the scale should be 0.5 new double[] { 0, NaN, 0, 0, -77.0105}, new double[] { 0, 0, NaN, 0, 5.0}, new double[] { 0, 0, 0, NaN, 20975.0}, new double[] { 0, 0, 0, 0, 1.0}).equals(matrix, 1)); } /* * Ask again the affine transform, this time from the grid geometry. */ assertTrue("getAffineTransform(GridGeometry, CELL_CENTER) should give the same result.", Matrices.equals(matrix, DiscreteReferencingFactory.getAffineTransform( (GridGeometry) crs, PixelInCell.CELL_CENTER), 0, false)); // ((GeneralMatrix) matrix).sub((GeneralMatrix) DiscreteReferencingFactory.getAffineTransform( // (GridGeometry) crs, PixelInCell.CELL_CORNER)); // if (isProjected) { // assertTrue("CELL_CENTER - CELL_CORNER should be half of a pixel size.", new GeneralMatrix( // new double[] {0, 0, 0, 0, 27799}, // new double[] {0, 0, 0, 0, 27799}, // new double[] {0, 0, NaN, 0, NaN}, // new double[] {0, 0, 0, NaN, NaN}, // new double[] {0, 0, 0, 0, 0}).equals(matrix, 1)); // } } }