| /* |
| * 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; |
| |
| import java.util.Collection; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.geom.RectangularShape; |
| import java.awt.geom.AffineTransform; |
| import static java.lang.StrictMath.*; |
| import javax.measure.Unit; |
| import org.opengis.geometry.Envelope; |
| import org.opengis.geometry.DirectPosition; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.parameter.GeneralParameterValue; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.opengis.parameter.ParameterValue; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.operation.Matrix; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.cs.AxisDirection; |
| import org.opengis.referencing.cs.CoordinateSystemAxis; |
| import org.opengis.referencing.cs.RangeMeaning; |
| import org.opengis.util.GenericName; |
| import org.apache.sis.util.Static; |
| import org.apache.sis.io.wkt.Symbols; |
| import org.apache.sis.io.wkt.WKTFormat; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.geometry.AbstractEnvelope; |
| import org.apache.sis.geometry.Envelopes; |
| import org.apache.sis.geometry.GeneralDirectPosition; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.referencing.operation.transform.LinearTransform; |
| import org.apache.sis.util.privy.Constants; |
| |
| // Test dependencies |
| import static org.junit.jupiter.api.Assertions.*; |
| import org.apache.sis.test.TestUtilities; |
| import static org.apache.sis.test.Assertions.assertMultilinesEquals; |
| |
| |
| /** |
| * Assertion methods used by the {@code org.apache.sis.referencing} module in addition of the ones inherited |
| * from other modules and libraries. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| */ |
| public final class Assertions extends Static { |
| /** |
| * The formatter to be used by {@link #assertWktEquals(String, Object)}. |
| * This formatter uses the {@code “…”} quotation marks instead of {@code "…"} |
| * for easier readability of {@link String} constants in Java code. |
| */ |
| private static final WKTFormat WKT_FORMAT = new WKTFormat(); |
| static { |
| final Symbols s = new Symbols(Symbols.SQUARE_BRACKETS); |
| s.setPairedQuotes("“”", "\"\""); |
| WKT_FORMAT.setSymbols(s); |
| } |
| |
| /** |
| * Do not allow instantiation of this class. |
| */ |
| private Assertions() { |
| } |
| |
| /** |
| * Asserts that the given identifier has the expected code and the {@code "OGC"} code space. |
| * The authority is expected to be {@link Citations#OGC}. We expect the exact same authority |
| * instance because identifiers in OGC namespace are often hard-coded in SIS. |
| * |
| * @param expected the expected identifier code. |
| * @param actual the identifier to verify. |
| */ |
| public static void assertOgcIdentifierEquals(final String expected, final Identifier actual) { |
| assertNotNull(actual); |
| assertEquals(expected, actual.getCode(), "code"); |
| assertEquals(Constants.OGC, actual.getCodeSpace(), "codeSpace"); |
| assertSame (Citations.OGC, actual.getAuthority(), "authority"); |
| assertEquals(Constants.OGC + Constants.DEFAULT_SEPARATOR + expected, IdentifiedObjects.toString(actual), "identifier"); |
| } |
| |
| /** |
| * Asserts that the given identifier has the expected code and the {@code "EPSG"} code space. |
| * The authority is expected to have the {@code "EPSG"} title, alternate title or identifier. |
| * |
| * @param expected the expected identifier code. |
| * @param actual the identifier to verify. |
| */ |
| public static void assertEpsgIdentifierEquals(final String expected, final Identifier actual) { |
| assertNotNull(actual); |
| assertEquals(expected, actual.getCode(), "code"); |
| assertEquals(Constants.EPSG, actual.getCodeSpace(), "codeSpace"); |
| assertEquals(Constants.EPSG, Citations.toCodeSpace(actual.getAuthority()), "authority"); |
| assertEquals(Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected, IdentifiedObjects.toString(actual), "identifier"); |
| } |
| |
| /** |
| * Asserts that the given object has the expected name and singleton identifier in the {@code "EPSG"} code space. |
| * No other identifier than the given one is expected. The authority is expected to have the {@code "EPSG"} title, |
| * alternate title or identifier. |
| * |
| * @param name the expected EPSG name. |
| * @param identifier the expected EPSG identifier. |
| * @param object the object to verify. |
| */ |
| public static void assertEpsgNameAndIdentifierEqual(final String name, final int identifier, final IdentifiedObject object) { |
| assertNotNull(object, name); |
| assertEpsgIdentifierEquals(name, object.getName()); |
| assertEpsgIdentifierEquals(String.valueOf(identifier), TestUtilities.getSingleton(object.getIdentifiers())); |
| } |
| |
| /** |
| * Asserts that the tip of the unique alias of the given object is equal to the expected value. |
| * As a special case if the expected value is null, then this method verifies that the given object has no alias. |
| * |
| * @param expected the expected alias, or {@code null} if we expect no alias. |
| * @param object the object for which to test the alias. |
| */ |
| public static void assertAliasTipEquals(final String expected, final IdentifiedObject object) { |
| final Collection<GenericName> aliases = object.getAlias(); |
| if (expected == null) { |
| assertTrue(aliases.isEmpty(), "aliases.isEmpty()"); |
| } else { |
| assertEquals(expected, TestUtilities.getSingleton(aliases).tip().toString(), "alias"); |
| } |
| } |
| |
| /** |
| * Compares the given coordinate system axis against the expected values. |
| * |
| * @param name the expected axis name code. |
| * @param abbreviation the expected axis abbreviation. |
| * @param direction the expected axis direction. |
| * @param minimumValue the expected axis minimal value. |
| * @param maximumValue the expected axis maximal value. |
| * @param unit the expected axis unit of measurement. |
| * @param rangeMeaning the expected axis range meaning. |
| * @param axis the axis to verify. |
| */ |
| public static void assertAxisEquals(final String name, final String abbreviation, final AxisDirection direction, |
| final double minimumValue, final double maximumValue, final Unit<?> unit, final RangeMeaning rangeMeaning, |
| final CoordinateSystemAxis axis) |
| { |
| assertEquals(name, axis.getName().getCode(), "name"); |
| assertEquals(abbreviation, axis.getAbbreviation(), "abbreviation"); |
| assertEquals(direction, axis.getDirection(), "direction"); |
| assertEquals(minimumValue, axis.getMinimumValue(), "minimumValue"); |
| assertEquals(maximumValue, axis.getMaximumValue(), "maximumValue"); |
| assertEquals(unit, axis.getUnit(), "unit"); |
| assertEquals(rangeMeaning, axis.getRangeMeaning(), "rangeMeaning"); |
| } |
| |
| /** |
| * Asserts that the given parameter values are equal to the expected ones within |
| * a positive delta. Only the elements in the given descriptor are compared, and |
| * the comparisons are done in the units declared in the descriptor. |
| * |
| * @param expected the expected parameter values. |
| * @param actual the actual parameter values. |
| * @param tolerance the tolerance threshold for comparison of numerical values. |
| */ |
| public static void assertParameterEquals(final ParameterValueGroup expected, |
| final ParameterValueGroup actual, final double tolerance) |
| { |
| for (final GeneralParameterValue candidate : expected.values()) { |
| if (!(candidate instanceof ParameterValue<?>)) { |
| throw new UnsupportedOperationException("Not yet implemented."); |
| } |
| final ParameterValue<?> value = (ParameterValue<?>) candidate; |
| final ParameterDescriptor<?> descriptor = value.getDescriptor(); |
| final String name = descriptor.getName().getCode(); |
| final Unit<?> unit = descriptor.getUnit(); |
| final Class<?> valueClass = descriptor.getValueClass(); |
| final ParameterValue<?> e = expected.parameter(name); |
| final ParameterValue<?> a = actual .parameter(name); |
| if (unit != null) { |
| final double f = e.doubleValue(unit); |
| assertEquals(f, a.doubleValue(unit), tolerance, name); |
| } else if (valueClass == Float.class || valueClass == Double.class) { |
| final double f = e.doubleValue(); |
| assertEquals(f, a.doubleValue(), tolerance, name); |
| } else { |
| assertEquals(e.getValue(), a.getValue(), name); |
| } |
| } |
| } |
| |
| /** |
| * Asserts that the given matrix is diagonal, and that all elements on the diagonal are equal |
| * to the given values. The matrix doesn't need to be square. The last row is handled especially |
| * if the {@code affine} argument is {@code true}. |
| * |
| * @param expected the values which are expected on the diagonal. If the length of this array |
| * is less than the matrix size, then the last element in the array is repeated |
| * for all remaining diagonal elements. |
| * @param affine if {@code true}, then the last row is expected to contains the value 1 |
| * in the last column, and all other columns set to 0. |
| * @param matrix the matrix to test. |
| * @param tolerance the tolerance threshold while comparing floating point values. |
| */ |
| public static void assertDiagonalEquals(final double[] expected, final boolean affine, |
| final Matrix matrix, final double tolerance) |
| { |
| final int numRows = matrix.getNumRow(); |
| final int numCols = matrix.getNumCol(); |
| for (int j=0; j<numRows; j++) { |
| for (int i=0; i<numCols; i++) { |
| final double e; |
| if (affine && j == numRows-1) { |
| e = (i == numCols-1) ? 1 : 0; |
| } else if (i == j) { |
| e = expected[min(expected.length-1, i)]; |
| } else { |
| e = 0; |
| } |
| final int ti=i, tj=j; // Because lambda requires final values. |
| assertEquals(e, matrix.getElement(j, i), tolerance, () -> "matrix(" + tj + ", " + ti + ")"); |
| } |
| } |
| } |
| |
| /** |
| * Compares two affine transforms for equality. |
| * |
| * @param expected the expected affine transform. |
| * @param actual the actual affine transform. |
| * @param tolerance the tolerance threshold. |
| */ |
| public static void assertTransformEquals(final AffineTransform expected, final AffineTransform actual, final double tolerance) { |
| assertEquals(expected.getScaleX(), actual.getScaleX(), tolerance, "scaleX"); |
| assertEquals(expected.getScaleY(), actual.getScaleY(), tolerance, "scaleY"); |
| assertEquals(expected.getShearX(), actual.getShearX(), tolerance, "shearX"); |
| assertEquals(expected.getShearY(), actual.getShearY(), tolerance, "shearY"); |
| assertEquals(expected.getTranslateX(), actual.getTranslateX(), tolerance, "translateX"); |
| assertEquals(expected.getTranslateY(), actual.getTranslateY(), tolerance, "translateY"); |
| } |
| |
| /** |
| * Asserts that two rectangles have the same location and the same size. |
| * |
| * @param expected the expected rectangle. |
| * @param actual the rectangle to compare with the expected one. |
| * @param tolx the tolerance threshold on location along the <var>x</var> axis. |
| * @param toly the tolerance threshold on location along the <var>y</var> axis. |
| */ |
| public static void assertRectangleEquals(final RectangularShape expected, |
| final RectangularShape actual, final double tolx, final double toly) |
| { |
| assertEquals(expected.getMinX(), actual.getMinX(), tolx, "Min X"); |
| assertEquals(expected.getMinY(), actual.getMinY(), toly, "Min Y"); |
| assertEquals(expected.getMaxX(), actual.getMaxX(), tolx, "Max X"); |
| assertEquals(expected.getMaxY(), actual.getMaxY(), toly, "Max Y"); |
| assertEquals(expected.getCenterX(), actual.getCenterX(), tolx, "Center X"); |
| assertEquals(expected.getCenterY(), actual.getCenterY(), toly, "Center Y"); |
| assertEquals(expected.getWidth(), actual.getWidth(), tolx*2, "Width"); |
| assertEquals(expected.getHeight(), actual.getHeight(), toly*2, "Height"); |
| } |
| |
| /** |
| * Asserts that two envelopes have the same minimum and maximum coordinates. |
| * This method ignores the envelope type (i.e. the implementation class) and the CRS. |
| * |
| * @param expected the expected envelope. |
| * @param actual the envelope to compare with the expected one. |
| * @param tolerances the tolerance threshold on location along each axis. If this array length is shorter |
| * than the number of dimensions, then the last tolerance is reused for all remaining axes. |
| * If this array is empty, then the tolerance threshold is zero. |
| */ |
| public static void assertEnvelopeEquals(final Envelope expected, final Envelope actual, final double... tolerances) { |
| final int dimension = expected.getDimension(); |
| assertEquals(dimension, actual.getDimension(), "dimension"); |
| final DirectPosition expectedLower = expected.getLowerCorner(); |
| final DirectPosition expectedUpper = expected.getUpperCorner(); |
| final DirectPosition actualLower = actual .getLowerCorner(); |
| final DirectPosition actualUpper = actual .getUpperCorner(); |
| double tolerance = 0; |
| for (int i=0; i<dimension; i++) { |
| if (i < tolerances.length) { |
| tolerance = tolerances[i]; |
| } |
| if (abs(expectedLower.getCoordinate(i) - actualLower.getCoordinate(i)) > tolerance || |
| abs(expectedUpper.getCoordinate(i) - actualUpper.getCoordinate(i)) > tolerance) |
| { |
| fail("Envelopes are not equal in dimension " + i + ":\n" |
| + "expected " + Envelopes.toString(expected) + "\n" |
| + " but got " + Envelopes.toString(actual)); |
| } |
| } |
| } |
| |
| /** |
| * Tests if the given {@code outer} shape contains the given {@code inner} rectangle. |
| * This method will also verify class consistency by invoking the {@code intersects} |
| * method, and by interchanging the arguments. |
| * |
| * <p>This method can be used for testing the {@code outer} implementation - |
| * it should not be needed for standard JDK implementations.</p> |
| * |
| * @param outer the shape which is expected to contains the given rectangle. |
| * @param inner the rectangle which should be contained by the shape. |
| */ |
| public static void assertContains(final RectangularShape outer, final Rectangle2D inner) { |
| assertTrue(outer.contains (inner), "outer.contains(inner)"); |
| assertTrue(outer.intersects(inner), "outer.intersects(inner)"); |
| if (outer instanceof Rectangle2D r) { |
| assertTrue (inner.intersects(r), "inner.intersects(outer)"); |
| assertFalse(inner.contains (r), "inner.contains(outer)"); |
| } |
| assertTrue(outer.contains(inner.getCenterX(), inner.getCenterY()), "outer.contains(centerX, centerY)"); |
| } |
| |
| /** |
| * Tests if the given {@code outer} envelope contains the given {@code inner} envelope. |
| * This method will also verify class consistency by invoking the {@code intersects} |
| * method, and by interchanging the arguments. |
| * |
| * @param outer the envelope which is expected to contains the given inner envelope. |
| * @param inner the envelope which should be contained by the outer envelope. |
| */ |
| public static void assertContains(final AbstractEnvelope outer, final Envelope inner) { |
| assertTrue(outer.contains (inner, true), "outer.contains(inner)"); |
| assertTrue(outer.contains (inner, false), "outer.contains(inner)"); |
| assertTrue(outer.intersects(inner, true), "outer.intersects(inner)"); |
| assertTrue(outer.intersects(inner, false), "outer.intersects(inner)"); |
| if (inner instanceof AbstractEnvelope ai) { |
| assertTrue (ai.intersects(outer, true), "inner.intersects(outer)"); |
| assertTrue (ai.intersects(outer, false), "inner.intersects(outer)"); |
| assertFalse(ai.contains (outer, true), "inner.contains(outer)"); |
| assertFalse(ai.contains (outer, false), "inner.contains(outer)"); |
| } |
| final GeneralDirectPosition median = new GeneralDirectPosition(inner.getDimension()); |
| for (int i=median.getDimension(); --i>=0;) { |
| median.setCoordinate(i, inner.getMedian(i)); |
| } |
| assertTrue(outer.contains(median), "outer.contains(median)"); |
| } |
| |
| /** |
| * Tests if the given {@code r1} shape is disjoint with the given {@code r2} rectangle. |
| * This method will also verify class consistency by invoking the {@code contains} |
| * method, and by interchanging the arguments. |
| * |
| * <p>This method can be used for testing the {@code r1} implementation - it should not |
| * be needed for standard implementations.</p> |
| * |
| * @param r1 the first shape to test. |
| * @param r2 the second rectangle to test. |
| */ |
| public static void assertDisjoint(final RectangularShape r1, final Rectangle2D r2) { |
| assertFalse(r1.intersects(r2), "r1.intersects(r2)"); |
| assertFalse(r1.contains(r2), "r1.contains(r2)"); |
| if (r1 instanceof Rectangle2D r) { |
| assertFalse(r2.intersects(r), "r2.intersects(r1)"); |
| assertFalse(r2.contains (r), "r2.contains(r1)"); |
| } |
| for (int i=0; i<9; i++) { |
| final double x, y; |
| switch (i % 3) { |
| case 0: x = r2.getMinX(); break; |
| case 1: x = r2.getCenterX(); break; |
| case 2: x = r2.getMaxX(); break; |
| default: throw new AssertionError(i); |
| } |
| switch (i / 3) { |
| case 0: y = r2.getMinY(); break; |
| case 1: y = r2.getCenterY(); break; |
| case 2: y = r2.getMaxY(); break; |
| default: throw new AssertionError(i); |
| } |
| assertFalse(r1.contains(x, y), () -> "r1.contains(" + x + ", " + y + ')'); |
| } |
| } |
| |
| /** |
| * Tests if the given {@code e1} envelope is disjoint with the given {@code e2} envelope. |
| * This method will also verify class consistency by invoking the {@code contains} method, |
| * and by interchanging the arguments. |
| * |
| * @param e1 the first envelope to test. |
| * @param e2 the second envelope to test. |
| */ |
| public static void assertDisjoint(final AbstractEnvelope e1, final Envelope e2) { |
| assertFalse(e1.intersects(e2, false), "e1.intersects(e2)"); |
| assertFalse(e1.intersects(e2, true), "e1.intersects(e2)"); |
| assertFalse(e1.contains (e2, false), "e1.contains(e2)"); |
| assertFalse(e1.contains (e2, true), "e1.contains(e2)"); |
| if (e2 instanceof AbstractEnvelope ae) { |
| assertFalse(ae.intersects(e1, false), "e2.intersects(e1)"); |
| assertFalse(ae.intersects(e1, true), "e2.intersects(e1)"); |
| assertFalse(ae.contains (e1, false), "e2.contains(e1)"); |
| assertFalse(ae.contains (e1, true), "e2.contains(e1)"); |
| } |
| final int dimension = e1.getDimension(); |
| final int numCases = toIntExact(round(pow(3, dimension))); |
| final GeneralDirectPosition pos = new GeneralDirectPosition(dimension); |
| for (int index=0; index<numCases; index++) { |
| int n = index; |
| for (int i=0; i<dimension; i++) { |
| final double coordinate; |
| switch (n % 3) { |
| case 0: coordinate = e2.getMinimum(i); break; |
| case 1: coordinate = e2.getMedian (i); break; |
| case 2: coordinate = e2.getMaximum(i); break; |
| default: throw new AssertionError(i); |
| } |
| pos.setCoordinate(i, coordinate); |
| n /= 3; |
| } |
| assertEquals(0, n); // Opportunist check of this assert method. |
| assertFalse(e1.contains(pos), () -> "e1.contains(" + pos + ')'); |
| } |
| } |
| |
| /** |
| * Tests if the given transform is the identity transform. |
| * If the current transform is linear, then this method will also verifies {@link Matrix#isIdentity()}. |
| * |
| * @param transform the transform to test. |
| */ |
| public static void assertIsIdentity(final MathTransform transform) { |
| assertTrue(transform.isIdentity(), "isIdentity()"); |
| if (transform instanceof LinearTransform linear) { |
| assertTrue(linear.getMatrix().isIdentity(), "getMatrix().isIdentity()"); |
| } |
| } |
| |
| /** |
| * Tests if the given transform is <strong>not</strong> the identity transform. |
| * If the current transform is linear, then this method will also verifies {@link Matrix#isIdentity()}. |
| * |
| * @param transform the transform to test. |
| */ |
| public static void assertIsNotIdentity(final MathTransform transform) { |
| assertFalse(transform.isIdentity(), "isIdentity()"); |
| if (transform instanceof LinearTransform linear) { |
| assertFalse(linear.getMatrix().isIdentity(), "getMatrix().isIdentity()"); |
| } |
| } |
| |
| /** |
| * Asserts that the WKT 2 of the given object is equal to the expected one. |
| * This method expected the {@code “…”} quotation marks instead of {@code "…"} |
| * for easier readability of {@link String} constants in Java code. |
| * |
| * @param expected the expected text, or {@code null} if {@code object} is expected to be null. |
| * @param object the object to format in <i>Well Known Text</i> format, or {@code null}. |
| */ |
| public static void assertWktEquals(final String expected, final Object object) { |
| assertWktEquals(Convention.WKT2, expected, object); |
| } |
| |
| /** |
| * Asserts that the WKT of the given object according the given convention is equal to the expected one. |
| * This method expected the {@code “…”} quotation marks instead of {@code "…"} for easier readability of |
| * {@link String} constants in Java code. |
| * |
| * @param convention the WKT convention to use. |
| * @param expected the expected text, or {@code null} if {@code object} is expected to be null. |
| * @param object the object to format in <i>Well Known Text</i> format, or {@code null}. |
| */ |
| public static void assertWktEquals(final Convention convention, final String expected, final Object object) { |
| if (expected == null) { |
| assertNull(object); |
| } else { |
| assertNotNull(object); |
| final String wkt; |
| synchronized (WKT_FORMAT) { |
| WKT_FORMAT.setConvention(convention); |
| wkt = WKT_FORMAT.format(object); |
| } |
| assertMultilinesEquals(expected, wkt, (object instanceof IdentifiedObject) ? |
| ((IdentifiedObject) object).getName().getCode() : object.getClass().getSimpleName()); |
| } |
| } |
| |
| /** |
| * Asserts that the WKT of the given object according the given convention is equal to the given regular expression. |
| * This method is like {@link #assertWktEquals(String, Object)}, but the use of regular expression allows some |
| * tolerance for example on numerical parameter values that may be subject to a limited form of rounding errors. |
| * |
| * @param convention the WKT convention to use. |
| * @param expected the expected regular expression, or {@code null} if {@code object} is expected to be null. |
| * @param object the object to format in <i>Well Known Text</i> format, or {@code null}. |
| */ |
| public static void assertWktEqualsRegex(final Convention convention, final String expected, final Object object) { |
| if (expected == null) { |
| assertNull(object); |
| } else { |
| assertNotNull(object); |
| final String wkt; |
| synchronized (WKT_FORMAT) { |
| WKT_FORMAT.setConvention(convention); |
| wkt = WKT_FORMAT.format(object); |
| } |
| if (!wkt.matches(expected.replace("\n", System.lineSeparator()))) { |
| fail("WKT does not match the expected regular expression. The WKT that we got is:\n" + wkt); |
| } |
| } |
| } |
| } |