blob: 3db9ad2e9473784ae9fe89bf8da8bf3e819eec88 [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.referencing.operation.provider;
import java.util.Locale;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Files;
import javax.measure.quantity.Angle;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.gridded.GridFile;
import org.apache.sis.referencing.operation.gridded.GridLoader;
import org.apache.sis.referencing.operation.gridded.LoadedGrid;
import org.apache.sis.referencing.operation.matrix.Matrix3;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.measure.Units;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import static org.opengis.test.Assertions.assertMatrixEquals;
/**
* Tests the {@link NADCON} grid loader.
*
* @author Martin Desruisseaux (Geomatys)
* @author Simon Reynard (Geomatys)
*/
public final class NADCONTest extends DatumShiftTestCase {
/**
* Creates a new test case.
*/
public NADCONTest() {
}
/**
* Returns the sample point for a step in the transformation from NAD27 to NAD83.
* The sample point is the coordinate of Meades Ranch station, which was the point
* relative to which all land measurements in USA were made for NAD27.
* Its position was officially set as 39°13′26.686″N, 98°32′30.506″W.
* Precision is given by {@link #ANGULAR_TOLERANCE}.
*
* <p>The coordinate in NAD83 has been computed with the
* <a href="http://www.ngs.noaa.gov/cgi-bin/nadcon.prl">NADCON tools on NOAA website.</a></p>
*
* @param step The step as a value from 1 to 3 inclusive.
* @return the sample point at the given step.
*/
public static double[] samplePoint(final int step) {
switch (step) {
case 1: return new double[] { // NAD27 coordinate
-(98 + (32 + 30.506/60)/60), // 98°32′30.506″
39 + (13 + 26.686/60)/60 // 39°13′26.686″
};
case 2: return new double[] { // Shift in seconds of angle
-1.24121, // 1.24121″ (positive west in NADCON)
0.03498 // 0.03498″
};
case 3: return new double[] { // NAD83 coordinate
-(98 + (32 + 31.74721/60)/60), // 98°32′31.74721″
39 + (13 + 26.72098/60)/60 // 39°13′26.72098″
};
default: throw new AssertionError(step);
}
}
/**
* The tolerance when comparing coordinates in degrees.
* This tolerance is determined by the precision of the tools used for computing NAD83 coordinates.
*/
public static final double ANGULAR_TOLERANCE = 0.5E-5 / GridLoader.DEGREES_TO_SECONDS;
/**
* Name of the file without extension containing a small extract of the NADCON grid.
* The {@code ".laa"} and {@code ".loa"} extension must be added to that name.
*/
public static final String TEST_FILE = "conus-extract";
/**
* Tests loading a grid file and interpolating a sample point.
* The point used for this test is given by {@link #samplePoint(int)}.
*
* @throws Exception if an error occurred while loading or computing the grid, or while testing transformations.
*/
@Test
public void testLoader() throws Exception {
testNADCON(getResource(TEST_FILE + ".laa"), // Latitude shifts
getResource(TEST_FILE + ".loa"), // Longitude shifts
-99.75, -98.0, 37.5, 39.75);
}
/**
* Tests loading an official {@code "conus.las"} and {@code "conus.los"} datum shift grid files and interpolating
* the sample point given by {@link #samplePoint(int)}. This test is normally not executed because Apache SIS does
* not redistribute the datum shift grid files. But developers can invoke this method explicitly if they can
* provide a path to those files.
*
* @param latitudeShifts path to the official {@code "conus.las"} file.
* @param longitudeShifts path to the official {@code "conus.los"} file.
* @throws Exception if an error occurred while loading or computing the grid, or while testing transformations.
*/
public static void testNADCON(final GridFile latitudeShifts, final GridFile longitudeShifts) throws Exception {
testNADCON(latitudeShifts, longitudeShifts, -131, -63, 20, 50);
}
/**
* Implementation of {@link #testLoader()} and {@link #testNADCON(URI, URI)}.
*
* @param xmin westmost longitude.
* @param xmax eastmost longitude.
* @param ymin southmost latitude.
* @param ymax northmost latitude.
*/
private static void testNADCON(final GridFile latitudeShifts, final GridFile longitudeShifts,
final double xmin, final double xmax, final double ymin, final double ymax)
throws Exception
{
final LoadedGrid<Angle,Angle> grid = NADCON.getOrLoad(latitudeShifts, longitudeShifts);
assertInstanceOf(LoadedGrid.Float.class, grid, "Should not be compressed.");
assertEquals(Units.DEGREE, grid.getCoordinateUnit());
assertEquals(Units.DEGREE, grid.getTranslationUnit());
assertEquals(2, grid.getTranslationDimensions());
assertTrue (grid.isCellValueRatio());
assertTrue (grid.getCellPrecision() > 0);
/*
* Verify the envelope and the conversion between geographic coordinates and grid indices.
* The cells are expected to have the same size (0.25°) in longitudes and latitudes.
*/
final double cellSize = 0.25;
final Envelope envelope = grid.getDomainOfValidity();
assertEquals(xmin, envelope.getMinimum(0), "xmin");
assertEquals(xmax, envelope.getMaximum(0), "xmax");
assertEquals(ymin, envelope.getMinimum(1), "ymin");
assertEquals(ymax, envelope.getMaximum(1), "ymax");
assertMatrixEquals(new Matrix3(cellSize, 0, xmin,
0, cellSize, ymin,
0, 0, 1),
grid.getCoordinateToGrid().inverse().getMatrix(), STRICT, "coordinateToGrid");
/*
* Test the Meades Ranch station. If we were using the complete Conus files, we would obtain
* after conversion the grid indices listed on the left side. But since we are using a sub-set
* of the grid, we rather obtain the grid indices on the right side.
*
* gridX ≈ 129.83277 in official file, ≈ 4.83277 in the test file.
* gridY ≈ 76.89632 in official file, ≈ 6.89632 in the test file.
*/
final double[] position = samplePoint(1);
final double[] expected = samplePoint(2);
final double[] indices = new double[position.length];
final double[] vector = new double[2];
grid.getCoordinateToGrid().transform(position, 0, indices, 0, 1);
grid.interpolateInCell(indices[0], indices[1], vector);
vector[0] *= cellSize * GridLoader.DEGREES_TO_SECONDS;
vector[1] *= cellSize * GridLoader.DEGREES_TO_SECONDS;
assertArrayEquals(expected, vector, 0.5E-5);
// Same test as above, but let DatumShiftGrid do the conversions for us.
expected[0] /= GridLoader.DEGREES_TO_SECONDS;
expected[1] /= GridLoader.DEGREES_TO_SECONDS;
assertArrayEquals(expected, grid.interpolateAt(position), ANGULAR_TOLERANCE);
assertSame(grid, NADCON.getOrLoad(latitudeShifts, longitudeShifts), "Grid should be cached.");
}
//////////////////////////////////////////////////
//////// ////////
//////// TEST FILE CREATION ////////
//////// ////////
//////////////////////////////////////////////////
/**
* Writes a sub-grid of the given grid in pseudo-NADCON ASCII format.
* This method is used only for creating the test file, and the output is not fully NADCON compliant.
* We take this opportunity for testing the parser capability to be lenient.
*
* <p>This method has been executed once for creating the {@code "conus-extract.laa"} and
* {@code "conus-extract.loa"} test files and should not be needed anymore, but we keep it
* around in case we have new test files to generate. The parameter used for creating the
* test file are:</p>
*
* <ul>
* <li>{@code gridX} = 125</li>
* <li>{@code gridY} = 70</li>
* <li>{@code nx} = 8</li>
* <li>{@code ny} = 10</li>
* </ul>
*
* This ensure that the grid indices (129.83277, 76.89632) is included in the test file.
* Those grid indices is the location of the (39°13′26.71″N, 98°32′31.75″W) test point to interpolate.
*
* @param grid the full grid from which to extract a few values.
* @param file where to write the test file.
* @param dim 0 for writing longitudes, or 1 for writing latitudes.
* @param gridX index along the longitude axis of the first cell to write.
* @param gridY index along the latitude axis of the first cell to write.
* @param nx number of cells to write along the longitude axis.
* @param ny number of cells to write along the latitude axis.
* @throws TransformException if an error occurred while computing the envelope.
* @throws IOException if an error occurred while writing the test file.
*/
public static void writeSubGrid(final LoadedGrid<Angle,Angle> grid, final Path file, final int dim,
final int gridX, final int gridY, final int nx, final int ny) throws IOException, TransformException
{
Envelope envelope = new Envelope2D(null, gridX, gridY, nx - 1, ny - 1);
envelope = Envelopes.transform(grid.getCoordinateToGrid().inverse(), envelope);
try (BufferedWriter out = Files.newBufferedWriter(file)) {
out.write("NADCON EXTRACTED REGION\n");
out.write(String.format(Locale.US, "%4d %3d %3d %11.5f %11.5f %11.5f %11.5f %11.5f\n", nx, ny, 1,
envelope.getMinimum(0), envelope.getSpan(0) / (nx - 1),
envelope.getMinimum(1), envelope.getSpan(1) / (ny - 1),
0.0));
for (int y=0; y<ny; y++) {
for (int x=0; x<nx; x++) {
out.write(String.format(Locale.US, " %11.6f", grid.getCellValue(dim, gridX + x, gridY + y)));
}
out.write('\n');
}
}
}
}