/** * Casts or copies the given matrix to a {@code Matrix4} implementation. If the given {@code matrix} * is already an instance of {@code Matrix4}, then it is returned unchanged. Otherwise this method * verifies the matrix size, then copies all elements in a new {@code Matrix4} object. * * @param matrix the matrix to cast or copy, or {@code null}. * @return the matrix argument if it can be safely casted (including {@code null} argument), * or a copy of the given matrix otherwise. * @throws MismatchedMatrixSizeException if the size of the given matrix is not {@value #SIZE}×{@value #SIZE}. */ public static Matrix4 castOrCopy(final Matrix matrix) throws MismatchedMatrixSizeException { if (matrix == null || matrix instanceof Matrix4) { return (Matrix4) matrix; } ensureSizeMatch(SIZE, SIZE, matrix); return new Matrix4(matrix); }
/** * Casts or copies the given matrix to a {@code Matrix4} implementation. If the given {@code matrix} * is already an instance of {@code Matrix4}, then it is returned unchanged. Otherwise this method * verifies the matrix size, then copies all elements in a new {@code Matrix4} object. * * @param matrix the matrix to cast or copy, or {@code null}. * @return the matrix argument if it can be safely casted (including {@code null} argument), * or a copy of the given matrix otherwise. * @throws MismatchedMatrixSizeException if the size of the given matrix is not {@value #SIZE}×{@value #SIZE}. */ public static Matrix4 castOrCopy(final Matrix matrix) throws MismatchedMatrixSizeException { if (matrix == null || matrix instanceof Matrix4) { return (Matrix4) matrix; } ensureSizeMatch(SIZE, SIZE, matrix); return new Matrix4(matrix); }
/** * Creates a dummy transform for testing purpose. * The transform has the following properties: * * <ul> * <li>The source and target dimensions are 3.</li> * <li>The transform contains 3 step.</li> * <li>The second step is a {@link PassThroughTransform}.</li> * <li>The transform in the middle (at dimension 1) is non-linear.</li> * </ul> * * @return the dummy math transform. */ public static MathTransform createConcatenateAndPassThrough() { return createConcatenateAndPassThrough(new Matrix4(), new Matrix4()); }
/** * Creates a square identity matrix of size {@code size} × {@code size}. * Elements on the diagonal (<var>j</var> == <var>i</var>) are set to 1. * * <div class="note"><b>Implementation note:</b> * For sizes between {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.</div> * * @param size numbers of row and columns. For an affine transform matrix, this is the number of * {@linkplain MathTransform#getSourceDimensions() source} and * {@linkplain MathTransform#getTargetDimensions() target} dimensions + 1. * @return an identity matrix of the given size. */ public static MatrixSIS createIdentity(final int size) { switch (size) { case 1: return new Matrix1(); case 2: return new Matrix2(); case 3: return new Matrix3(); case 4: return new Matrix4(); default: return new GeneralMatrix(size, size, true, 1); } }
/** * Creates a square identity matrix of size {@code size} × {@code size}. * Elements on the diagonal (<var>j</var> == <var>i</var>) are set to 1. * * <div class="note"><b>Implementation note:</b> * For sizes between {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.</div> * * @param size numbers of row and columns. For an affine transform matrix, this is the number of * {@linkplain MathTransform#getSourceDimensions() source} and * {@linkplain MathTransform#getTargetDimensions() target} dimensions + 1. * @return an identity matrix of the given size. */ public static MatrixSIS createIdentity(final int size) { switch (size) { case 1: return new Matrix1(); case 2: return new Matrix2(); case 3: return new Matrix3(); case 4: return new Matrix4(); default: return new GeneralMatrix(size, size, true, 1); } }
/** * Creates a matrix of size {@code numRow} × {@code numCol} filled with zero values. * This constructor is convenient when the caller wants to initialize the matrix elements himself. * * <div class="note"><b>Implementation note:</b> * For {@code numRow} == {@code numCol} with a value between * {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.</div> * * @param numRow for a math transform, this is the number of {@linkplain MathTransform#getTargetDimensions() target dimensions} + 1. * @param numCol for a math transform, this is the number of {@linkplain MathTransform#getSourceDimensions() source dimensions} + 1. * @return a matrix of the given size with only zero values. */ public static MatrixSIS createZero(final int numRow, final int numCol) { if (numRow == numCol) switch (numRow) { case 1: return new Matrix1(false); case 2: return new Matrix2(false); case 3: return new Matrix3(false); case 4: return new Matrix4(false); default: return new GeneralMatrix(numRow, numCol, false, 1); } return new NonSquareMatrix(numRow, numCol, false, 1); }
/** * Creates a matrix of size {@code numRow} × {@code numCol} filled with zero values. * This constructor is convenient when the caller wants to initialize the matrix elements himself. * * <div class="note"><b>Implementation note:</b> * For {@code numRow} == {@code numCol} with a value between * {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.</div> * * @param numRow for a math transform, this is the number of {@linkplain MathTransform#getTargetDimensions() target dimensions} + 1. * @param numCol for a math transform, this is the number of {@linkplain MathTransform#getSourceDimensions() source dimensions} + 1. * @return a matrix of the given size with only zero values. */ public static MatrixSIS createZero(final int numRow, final int numCol) { if (numRow == numCol) switch (numRow) { case 1: return new Matrix1(false); case 2: return new Matrix2(false); case 3: return new Matrix3(false); case 4: return new Matrix4(false); default: return new GeneralMatrix(numRow, numCol, false, 1); } return new NonSquareMatrix(numRow, numCol, false, 1); }
/** * Tests {@link MathTransforms#getSteps(MathTransform)}. */ @Test public void testGetSteps() { final Matrix4 scale = new Matrix4(); // Scales a value. final Matrix4 swap = new Matrix4(); // Swaps two dimensions. final List<MathTransform> steps = MathTransforms.getSteps(createConcatenateAndPassThrough(scale, swap)); assertEquals(3, steps.size()); assertMatrixEquals("Step 1", scale, MathTransforms.getMatrix(steps.get(0)), STRICT); assertMatrixEquals("Step 3", swap, MathTransforms.getMatrix(steps.get(2)), STRICT); assertInstanceOf ("Step 2", PassThroughTransform.class, steps.get(1)); }
/** * Creates a matrix of size {@code numRow} × {@code numCol} initialized to the given elements. * The elements array size must be equals to {@code numRow*numCol}. Column indices vary fastest. * * <div class="note"><b>Implementation note:</b> * For {@code numRow} == {@code numCol} with a value between * {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.</div> * * @param numRow number of rows. * @param numCol number of columns. * @param elements the matrix elements in a row-major array. Column indices vary fastest. * @return a matrix initialized to the given elements. * * @see MatrixSIS#setElements(double[]) */ public static MatrixSIS create(final int numRow, final int numCol, final double[] elements) { if (numRow == numCol) switch (numRow) { case 1: return new Matrix1(elements); case 2: return new Matrix2(elements); case 3: return new Matrix3(elements); case 4: return new Matrix4(elements); default: return new GeneralMatrix(numRow, numCol, elements); } return new NonSquareMatrix(numRow, numCol, elements); }
/** * Creates a transform from the specified group of parameter values. * The parameter values are unconditionally converted to degrees and metres. * * @param factory ignored (can be null). * @param values the group of parameter values. * @return the created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ @Override public MathTransform createMathTransform(MathTransformFactory factory, ParameterValueGroup values) throws ParameterNotFoundException { final Parameters pv = Parameters.castOrWrap(values); final Matrix4 t = new Matrix4(); t.m03 = pv.doubleValue(TX); t.m13 = pv.doubleValue(TY); t.m23 = pv.doubleValue(vertical()); return MathTransforms.linear(t); } }
/** * Tests a transform created from a square matrix with no error terms. * * @throws TransformException should never happen. */ @Test public void testConstantDimension() throws TransformException { create(3, new Matrix4( 1, 0, 0, 2, 0, 1, 0, 3, 0, 0, 1, 8, 0, 0, 0, 1)); verifyTransform(new double[] {1,1,1, 6, 0, 2, 2, Double.NaN, 6}, new double[] {3,4,9, 8, 3, 10, 4, Double.NaN, 14}); }
/** * Tests a transform created from a square matrix with no error terms. * In this test, no dimension are dropped. * * @throws TransformException should never happen. */ @Test public void testConstantDimension() throws TransformException { create(3, 3, new Matrix4( 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 8, 0, 0, 0, 0, 1)); verifyTransform(new double[] {1,1,1, 6, 0, 2, 2, Double.NaN, 6}, new double[] {2,3,8, 12, 0, 16, 4, Double.NaN, 48}); }
/** * Invokes {@link BursaWolfParameters#getPositionVectorTransformation(Date)} * and compares with our own matrix calculated using double arithmetic. */ private static MatrixSIS getPositionVectorTransformation(final BursaWolfParameters p) { final double S = 1 + p.dS / BursaWolfParameters.PPM; final double RS = TO_RADIANS * S; final Matrix4 expected = new Matrix4( S, -p.rZ*RS, +p.rY*RS, p.tX, +p.rZ*RS, S, -p.rX*RS, p.tY, -p.rY*RS, +p.rX*RS, S, p.tZ, 0, 0, 0, 1); final MatrixSIS matrix = MatrixSIS.castOrCopy(p.getPositionVectorTransformation(null)); assertMatrixEquals("getPositionVectorTransformation", expected, matrix, p.isTranslation() ? 0 : 1E-14); return matrix; }
/** * Tests {@link PixelTranslation#translate(MathTransform, PixelInCell, PixelInCell)} with an identity transform. * If grid coordinates (0,0) in "pixel center" convention map to (0,0) in "real world" coordinates, * then grid coordinates (0,0) in "pixel corner" convention shall map to (-½, -½) in real world. * That way, grid coordinates (½,½) in "pixel corner" convention still map to (0,0) in real world. * * @throws TransformException if an error occurred while transforming a test point. */ @Test public void testTranslatePixelInCell() throws TransformException { final MathTransform mt = centerToCorner(3); assertMatrixEquals("center → corner", new Matrix4( 1, 0, 0, -0.5, 0, 1, 0, -0.5, 0, 0, 1, -0.5, 0, 0, 0, 1), MathTransforms.getMatrix(mt), STRICT); /* * Just for making clear what we explained in javadoc comment: the real world (0,0,0) coordinates was in the center * of cell (0,0,0). After we switched to "cell corner" convention, that center is (½,½,½) in grid coordinates but * should still map (0,0,0) in "real world" coordinates. */ final double[] coordinates = new double[] {0.5, 0.5, 0.5}; mt.transform(coordinates, 0, coordinates, 0, 1); assertArrayEquals(new double[3], coordinates, STRICT); }
/** * Tests the {@link Matrix4#Matrix4(double, double, double, double, double, double, double, * double, double, double, double, double, double, double, double, double)} constructor. * This constructor is specific to the implementation class. */ @Test public void testConstructor() { initialize(-7053945420932915425L); final double[] elements = createRandomPositiveValues(SIZE * SIZE); final Matrix4 matrix = new Matrix4( elements[ 0], elements[ 1], elements[ 2], elements[ 3], elements[ 4], elements[ 5], elements[ 6], elements[ 7], elements[ 8], elements[ 9], elements[10], elements[11], elements[12], elements[13], elements[14], elements[15]); validate(matrix); assertArrayEquals(elements, matrix.getElements(), STRICT); }
/** * Tests {@link PixelTranslation#translate(MathTransform, PixelOrientation, PixelOrientation, int, int)}. * See {@link #testTranslatePixelInCell()} for discussion on expected values. */ @Test public void testTranslatePixelOrientation() { MathTransform mt = centerToCorner2D(); assertMatrixEquals("center → corner", new Matrix3( 1, 0, -0.5, 0, 1, -0.5, 0, 0, 1), MathTransforms.getMatrix(mt), STRICT); mt = PixelTranslation.translate(MathTransforms.identity(3), PixelOrientation.LOWER_LEFT, PixelOrientation.CENTER, 1, 2); assertMatrixEquals("corner → center", new Matrix4( 1, 0, 0, 0.0, 0, 1, 0, +0.5, 0, 0, 1, -0.5, 0, 0, 0, 1), MathTransforms.getMatrix(mt), STRICT); }
/** * Tests separation of a linear transform containing {@link Double#NaN} values. * * @throws FactoryException if an error occurred while creating a new transform. */ @Test public void testIncompleteTransform() throws FactoryException { Matrix matrix = new Matrix4( 1, 0, 0, 7, 0, 0, 1, 8, 0, NaN, 0, 6, 0, 0, 0, 1 ); TransformSeparator s = new TransformSeparator(MathTransforms.linear(matrix)); s.addSourceDimensions(1); assertMatrixEquals("transform", new Matrix2( NaN, 6, 0, 1 ), ((LinearTransform) s.separate()).getMatrix(), STRICT); assertArrayEquals(new int[] {2}, s.getTargetDimensions()); }
/** * Tests the {@link GridGeometry#GridGeometry(GridGeometry, GridExtent, MathTransform)} constructor. * * @throws TransformException if an error occurred while using the "grid to CRS" transform. */ @Test public void testFromOther() throws TransformException { long[] low = new long[] { 1, 3, 2}; long[] high = new long[] {101, 203, 4}; GridExtent extent = new GridExtent(null, low, high, false); MathTransform gridToCRS = MathTransforms.translation(5, 7, 8); GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, gridToCRS, null); low = new long[] { 11, 35, 20}; high = new long[] {120, 250, 39}; extent = new GridExtent(null, low, high, false); grid = new GridGeometry(grid, extent, MathTransforms.scale(2, 1, 3)); assertSame(extent, grid.getExtent()); assertMatrixEquals("gridToCRS", new Matrix4( 2, 0, 0, 5, 0, 1, 0, 7, // Combination of above scales (diagonal) and translation (last column). 0, 0, 3, 8, 0, 0, 0, 1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER)), STRICT); }
/** * Tests {@link BursaWolfParameters#getPositionVectorTransformation(Date)}. * This test transform a point from WGS72 to WGS84, and conversely, * as documented in the example section of EPSG operation method 9606. * * @throws NoninvertibleMatrixException Should never happen. */ @Test public void testGetPositionVectorTransformation() throws NoninvertibleMatrixException { final BursaWolfParameters bursaWolf = createWGS72_to_WGS84(); final MatrixSIS toWGS84 = getPositionVectorTransformation(bursaWolf); final MatrixSIS toWGS72 = toWGS84.inverse(); final MatrixSIS source = Matrices.create(4, 1, new double[] {3657660.66, 255768.55, 5201382.11, 1}); final MatrixSIS target = Matrices.create(4, 1, new double[] {3657660.78, 255778.43, 5201387.75, 1}); assertMatrixEquals("toWGS84", target, toWGS84.multiply(source), 0.01); assertMatrixEquals("toWGS72", source, toWGS72.multiply(target), 0.01); /* * Tests the optimized path for translation-only parameters. * Matrices having only translation terms are much easier to predict. */ assertMatrixEquals("Translation only", new Matrix4( 1, 0, 0, -168, 0, 1, 0, -60, 0, 0, 1, 320, 0, 0, 0, 1), getPositionVectorTransformation(createNTF_to_WGS84()), 0); }
/** * Tests {@link MathTransforms#linear(MathTransform, DirectPosition)}. * * @throws TransformException if an error occurred while computing the derivative. */ @Test @DependsOnMethod("testGetMatrix") public void testLinearUsingPosition() throws TransformException { final DirectPosition pos = new GeneralDirectPosition(3, 1.5, 6); MathTransform tr = MathTransforms.linear(new Matrix4( 0, 5, 0, 9, 1, 0, 0, 0, // Non-linear transform will be concatenated at this dimension. 0, 0, 12, -3, 0, 0, 0, 1)); LinearTransform linear = MathTransforms.linear(tr, pos); assertSame("Linear transform shall be returned unchanged.", tr, linear); tr = MathTransforms.concatenate(nonLinear3D(), tr); linear = MathTransforms.linear(tr, pos); assertNotSame(tr, linear); /* * Transformation using above approximation shall produce the same result than the original * transform if we do the comparison at the position where the approximation has been computed. */ DirectPosition expected = tr.transform(pos, null); DirectPosition actual = linear.transform(pos, null); assertEquals(expected, actual); }