Module org.apache.sis.referencing
Package org.apache.sis.referencing.operation.matrix

# Class Matrices

Object
Static
Matrices

public final class Matrices extends Static
`Matrix` factory methods and utilities. This class provides the following methods:

- Creating new matrices of arbitrary size: `createIdentity`, `createDiagonal`, `createZero`, `create`, `copy`.
- Creating new matrices for coordinate operation steps: `createTransform`, `createDimensionSelect`, `createPassThrough`.
- Information: `isAffine`, `isIdentity`, `equals`, `toString`.
- Miscellaneous: `multiply`, `inverse`, `unmodifiable`,

Since:
  0.4
See Also:
  - `TensorParameters`

- ## Method Summary

  Modifier and Type
  Method
  Description
  `static MatrixSIS`
  `copy(Matrix matrix)`
  Creates a new matrix which is a copy of the given matrix.
  `static MatrixSIS`
  `create(int numRow, int numCol, double[] elements)`
  Creates a matrix of size `numRow` × `numCol` initialized to the given elements.
  `static MatrixSIS`
  `create(int numRow, int numCol, Number[] elements)`
  Creates a matrix of size `numRow` × `numCol` initialized to the given numbers.
  `static MatrixSIS`
  `createAffine(Matrix derivative, DirectPosition translation)`
  Creates an affine transform as the given matrix augmented by the given translation vector and a [0 … 0 1] row.
  `static MatrixSIS`
  `createDiagonal(int numRow, int numCol)`
  Creates a matrix of size `numRow` × `numCol`.
  `static MatrixSIS`
  `createDimensionSelect(int sourceDimensions, int[] selectedDimensions)`
  Creates a matrix for a transform that keep only a subset of source coordinate values.
  `static MatrixSIS`
  `createIdentity(int size)`
  Creates a square identity matrix of size `size` × `size`.
  `static MatrixSIS`
  `createPassThrough(int firstAffectedCoordinate, Matrix subMatrix, int numTrailingCoordinates)`
  Creates a matrix which converts a subset of coordinates using the transform given by another matrix.
  `static MatrixSIS`
  `createTransform(Envelope srcEnvelope, Envelope dstEnvelope)`
  Creates a transform matrix mapping the given source envelope to the given destination envelope.
  `static MatrixSIS`
  `createTransform(Envelope srcEnvelope, AxisDirection[] srcAxes, Envelope dstEnvelope, AxisDirection[] dstAxes)`
  Creates a transform matrix mapping the given source envelope to the given destination envelope, combined with changes in axis order and/or direction.
  `static MatrixSIS`
  `createTransform(AxisDirection[] srcAxes, AxisDirection[] dstAxes)`
  Creates a transform matrix changing axis order and/or direction.
  `static MatrixSIS`
  `createZero(int numRow, int numCol)`
  Creates a matrix of size `numRow` × `numCol` filled with zero values.
  `static boolean`
  `equals(Matrix m1, Matrix m2, double epsilon, boolean relative)`
  Compares the given matrices for equality, using the given relative or absolute tolerance threshold.
  `static boolean`
  `equals(Matrix m1, Matrix m2, ComparisonMode mode)`
  Compares the given matrices for equality, using the given comparison strictness level.
  `static boolean`
  `forceUniformScale(Matrix matrix, double selector, double[] anchor)`
  Forces the matrix coefficients of the given matrix to a uniform scale factor, assuming an affine transform.
  `static MatrixSIS`
  `inverse(Matrix matrix)`
  Returns the inverse of the given matrix.
  `static boolean`
  `isAffine(Matrix matrix)`
  Returns `true` if the given matrix represents an affine transform.
  `static boolean`
  `isIdentity(Matrix matrix, double tolerance)`
  Returns `true` if the given matrix is close to an identity matrix, given a tolerance threshold.
  `static boolean`
  `isTranslation(Matrix matrix)`
  Returns `true` if the given matrix represents a translation.
  `static MatrixSIS`
  `multiply(Matrix m1, Matrix m2)`
  Returns a new matrix which is the result of multiplying the first matrix with the second one.
  `static Matrix`
  `resizeAffine(Matrix matrix, int numRow, int numCol)`
  Returns a matrix with the same content than the given matrix but a different size, assuming an affine transform.
  `static String`
  `toString(Matrix matrix)`
  Returns a unlocalized string representation of the given matrix.
  `static MatrixSIS`
  `unmodifiable(Matrix matrix)`
  Returns an unmodifiable view of the given matrix.

  ### Methods inherited from class Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Method Details

  - ### createIdentity

    public static MatrixSIS createIdentity(int size)
    Creates a square identity matrix of size `size` × `size`. Elements on the diagonal (j == i) are set to 1.

    #### Implementation types

    For sizes between 1 and 4 inclusive, the matrix is guaranteed to be an instance of one of `Matrix1` … `Matrix4` subtypes.
    Parameters:
      `size` - numbers of row and columns. For an affine transform matrix, this is the number of source and target dimensions + 1.
    Returns:
      an identity matrix of the given size.

  - ### createDiagonal

    public static MatrixSIS createDiagonal(int numRow, int numCol)
    Creates a matrix of size `numRow` × `numCol`. Elements on the diagonal (j == i) are set to 1. The result is an identity matrix if `numRow` = `numCol`.

    #### Implementation types

    For `numRow` == `numCol` with a value between 1 and 4 inclusive, the matrix is guaranteed to be an instance of one of `Matrix1` … `Matrix4` subtypes.
    Parameters:
      `numRow` - for a math transform, this is the number of target dimensions + 1.
      `numCol` - for a math transform, this is the number of source dimensions + 1.
    Returns:
      an identity matrix of the given size.

  - ### createZero

    public static MatrixSIS createZero(int numRow, int numCol)
    Creates a matrix of size `numRow` × `numCol` filled with zero values. This constructor is convenient when the caller wants to initialize the matrix elements himself.

    #### Implementation types

    For `numRow` == `numCol` with a value between 1 and 4 inclusive, the matrix is guaranteed to be an instance of one of `Matrix1` … `Matrix4` subtypes.
    Parameters:
      `numRow` - for a math transform, this is the number of target dimensions + 1.
      `numCol` - for a math transform, this is the number of source dimensions + 1.
    Returns:
      a matrix of the given size with only zero values.

  - ### create

    public static MatrixSIS create(int numRow, int numCol, double[] elements)
    Creates a matrix of size `numRow` × `numCol` initialized to the given elements. The elements array size must be equal to `numRow*numCol`. Column indices vary fastest.

    #### Implementation types

    For `numRow` == `numCol` with a value between 1 and 4 inclusive, the matrix is guaranteed to be an instance of one of `Matrix1` … `Matrix4` subtypes.
    Parameters:
      `numRow` - number of rows.
      `numCol` - number of columns.
      `elements` - the matrix elements in a row-major array. Column indices vary fastest.
    Returns:
      a matrix initialized to the given elements.
    See Also:
      - `MatrixSIS​.setElements(double[])`

  - ### create

    public static MatrixSIS create(int numRow, int numCol, Number[] elements)
    Creates a matrix of size `numRow` × `numCol` initialized to the given numbers. The elements array size must be equal to `numRow*numCol`. Column indices vary fastest.

    This method does not guarantee that the same `Number` references will be kept in the matrix, neither that the elements will be of the same `Number` type. However no precision will be lost.

    Parameters:
      `numRow` - number of rows.
      `numCol` - number of columns.
      `elements` - the matrix elements in a row-major array. Column indices vary fastest.
    Returns:
      a matrix initialized to the given elements.

  - ### createTransform

    public static MatrixSIS createTransform(Envelope srcEnvelope, Envelope dstEnvelope)
    Creates a transform matrix mapping the given source envelope to the given destination envelope. The given envelopes can have any dimensions, which are handled as below:
    - If the two envelopes have the same dimension, then the transform is affine.
    - If the destination envelope has less dimensions than the source envelope, then trailing dimensions are silently dropped.
    - If the target envelope has more dimensions than the source envelope, then the transform will append trailing coordinates with the 0 value.
    This method ignores the envelope CRS, which may be null. Actually this method is used more often for grid envelopes (which have no CRS) than geodetic envelopes.

    #### Crossing the anti-meridian of a Geographic CRS

    If the given envelopes cross the date line, then this method requires their `getSpan(int)` method to behave as documented in the `AbstractEnvelope​.getSpan(int)` javadoc. Furthermore, the matrix created by this method will produce expected results only for source or destination points before the date line, since the wrap around operation cannot be represented by an affine transform.

    #### Example

    Given a source envelope of size 100 × 200 (the units do not matter for this method) and a destination envelope of size 300 × 500, and given lower corner translation from (-20, -40) to (-10, -25), then the following method call:

        matrix = Matrices.createTransform(
                new Envelope2D(null, -20, -40, 100, 200),
                new Envelope2D(null, -10, -25, 300, 500));

    will return the following square matrix. The transform of the lower corner is given as an example:

           ┌     ┐   ┌              ┐   ┌     ┐
           │ -10 │   │ 3.0  0    50 │   │ -20 │       // 3.0 is the scale factor from width of 100 to 300
           │ -25 │ = │ 0    2.5  75 │ × │ -40 │       // 2.5 is the scale factor from height of 200 to 500
           │   1 │   │ 0    0     1 │   │   1 │
           └     ┘   └              ┘   └     ┘

    Parameters:
      `srcEnvelope` - the source envelope.
      `dstEnvelope` - the destination envelope.
    Returns:
      the transform from the given source envelope to target envelope.
    See Also:
      - `createTransform(AxisDirection[], AxisDirection[])`
      - `createTransform(Envelope, AxisDirection[], Envelope, AxisDirection[])`
      - `CoordinateSystems​.swapAndScaleAxes(CoordinateSystem, CoordinateSystem)`

  - ### createTransform

    public static MatrixSIS createTransform(AxisDirection[] srcAxes, AxisDirection[] dstAxes)
    Creates a transform matrix changing axis order and/or direction. For example, the transform may convert (northing, westing) coordinates into (easting, northing) coordinates. This method tries to associate each `dstAxes` direction to either an equals `srcAxis` direction, or to an opposite `srcAxis` direction.
    - If some `srcAxes` directions cannot be mapped to `dstAxes` directions, then the transform will silently drops the coordinates associated to those extra source axis directions.
    - If some `dstAxes` directions cannot be mapped to `srcAxes` directions, then an exception will be thrown.
    For example, it is legal to transform from (easting, northing, up) to (easting, northing) — this is the first above case — but illegal to transform (easting, northing) to (easting, up).

    #### Example

    The following method call:

        matrix = Matrices.createTransform(
                new AxisDirection[] {AxisDirection.NORTH, AxisDirection.WEST},
                new AxisDirection[] {AxisDirection.EAST, AxisDirection.NORTH});

    will return the following square matrix, which can be used in coordinate conversions as below:

           ┌    ┐   ┌         ┐   ┌    ┐
           │ +x │   │ 0 -1  0 │   │  y │
           │  y │ = │ 1  0  0 │ × │ -x │
           │  1 │   │ 0  0  1 │   │  1 │
           └    ┘   └         ┘   └    ┘

    Parameters:
      `srcAxes` - the ordered sequence of axis directions for source coordinate system.
      `dstAxes` - the ordered sequence of axis directions for destination coordinate system.
    Returns:
      the transform from the given source axis directions to the given target axis directions.
    Throws:
      `IllegalArgumentException` - if `dstAxes` contains at least one axis not found in `srcAxes`, or if some colinear axes were found.
    See Also:
      - `createTransform(Envelope, Envelope)`
      - `createTransform(Envelope, AxisDirection[], Envelope, AxisDirection[])`
      - `CoordinateSystems​.swapAndScaleAxes(CoordinateSystem, CoordinateSystem)`

  - ### createTransform

    public static MatrixSIS createTransform(Envelope srcEnvelope, AxisDirection[] srcAxes, Envelope dstEnvelope, AxisDirection[] dstAxes)
    Creates a transform matrix mapping the given source envelope to the given destination envelope, combined with changes in axis order and/or direction. Invoking this method is equivalent to concatenating the following matrix transforms:
    - `createTransform(srcEnvelope, dstEnvelope)`
    - `createTransform(srcAxes, dstAxes)`
    This method ignores the envelope CRS, which may be null. Actually this method is used more often for grid envelopes (which have no CRS) than geodetic envelopes.

    #### Crossing the anti-meridian of a Geographic CRS

    If the given envelopes cross the date line, then this method requires their `getSpan(int)` method to behave as documented in the `AbstractEnvelope​.getSpan(int)` javadoc. Furthermore, the matrix created by this method will produce expected results only for source or destination points on one side of the date line (depending on whether axis direction is reversed), since the wrap around operation cannot be represented by an affine transform.

    #### Example

    Combining the examples documented in the above `createTransform(…)` methods, the following method call:

        matrix = Matrices.createTransform(
                new Envelope2D(null, -40, +20, 200, 100), new AxisDirection[] {AxisDirection.NORTH, AxisDirection.WEST},
                new Envelope2D(null, -10, -25, 300, 500), new AxisDirection[] {AxisDirection.EAST, AxisDirection.NORTH});

    will return the following square matrix. The transform of a corner is given as an example. Note that the input coordinate values are swapped because of the (North, West) axis directions, and the lower-left corner of the destination envelope is the lower-right corner of the source envelope because of the opposite axis direction.

           ┌     ┐   ┌               ┐   ┌     ┐
           │ -10 │   │ 0   -3.0  350 │   │ -40 │
           │ -25 │ = │ 2.5  0     75 │ × │ 120 │       // 120 is the westernmost source coordinate: (x=20) + (width=100)
           │   1 │   │ 0    0      1 │   │   1 │
           └     ┘   └               ┘   └     ┘

    Parameters:
      `srcEnvelope` - the source envelope.
      `srcAxes` - the ordered sequence of axis directions for source coordinate system.
      `dstEnvelope` - the destination envelope.
      `dstAxes` - the ordered sequence of axis directions for destination coordinate system.
    Returns:
      the transform from the given source envelope and axis directions to the given envelope and target axis directions.
    Throws:
      `MismatchedDimensionException` - if an envelope dimension does not match the length of the axis directions sequence.
      `IllegalArgumentException` - if `dstAxes` contains at least one axis not found in `srcAxes`, or if some colinear axes were found.
    See Also:
      - `createTransform(Envelope, Envelope)`
      - `createTransform(AxisDirection[], AxisDirection[])`
      - `CoordinateSystems​.swapAndScaleAxes(CoordinateSystem, CoordinateSystem)`

  - ### createDimensionSelect

    public static MatrixSIS createDimensionSelect(int sourceDimensions, int[] selectedDimensions)
    Creates a matrix for a transform that keep only a subset of source coordinate values. The matrix size will be (`selectedDimensions​.length` + 1) × (`sourceDimensions` + 1). The matrix will contain only zero elements, except for the following cells which will contain 1:
    - The last column in the last row.
    - For any row j other than the last row, the column `selectedDimensions[j]`.

    #### Example

    Given (x,y,z,t) coordinate values, if one wants to keep (y,x,t) coordinates (note the x ↔ y swapping) and discard the z values, then the indices of source coordinates to select are 1 for y, 0 for x and 3 for t. One can use the following method call:

        matrix = Matrices.createDimensionSelect(4, new int[] {1, 0, 3});

    The above method call will create the following 4×5 matrix, which can be used for converting coordinates as below:

           ┌   ┐   ┌           ┐   ┌   ┐
           │ y │   │ 0 1 0 0 0 │   │ x │
           │ x │   │ 1 0 0 0 0 │   │ y │
           │ t │ = │ 0 0 0 1 0 │ × │ z │
           │ 1 │   │ 0 0 0 0 1 │   │ t │
           └   ┘   └           ┘   │ 1 │
                                   └   ┘

    The inverse of the matrix created by this method will put `Double​.NaN` values in the extra dimensions. Other dimensions will work as expected.
    Parameters:
      `sourceDimensions` - the number of dimensions in source coordinates.
      `selectedDimensions` - the 0-based indices of source coordinate values to keep. The length of this array will be the number of dimensions in target coordinates.
    Returns:
      an affine transform matrix keeping only the given source dimensions, and discarding all others.
    Throws:
      `IllegalArgumentException` - if a value of `selectedDimensions` is lower than 0 or not smaller than `sourceDimensions`.
    See Also:
      - `TransformSeparator`

  - ### createPassThrough

    public static MatrixSIS createPassThrough(int firstAffectedCoordinate, Matrix subMatrix, int numTrailingCoordinates)
    Creates a matrix which converts a subset of coordinates using the transform given by another matrix. For example, giving (latitude, longitude, height) coordinates, a pass through operation can convert the height values from feet to metres without affecting the (latitude, longitude) values.

    The given sub-matrix shall have the following properties:

    - The last row often (but not necessarily) contains 0 values everywhere except in the last column.
    - Values in the last column are translation terms, except in the last row.
    - All other values are scale or shear terms.
    A square matrix complying with the above conditions is often affine, but this is not mandatory (for example a perspective transform may contain non-zero values in the last row).

    This method builds a new matrix with the following content:

    - An amount of `firstAffectedCoordinate` rows and columns are inserted before the first row and columns of the sub-matrix. The elements for the new rows and columns are set to 1 on the diagonal, and 0 elsewhere.
    - The sub-matrix - except for its last row and column - is copied in the new matrix starting at index (`firstAffectedCoordinate`, `firstAffectedCoordinate`).
    - An amount of `numTrailingCoordinates` rows and columns are appended after the above sub-matrix. Their elements are set to 1 on the pseudo-diagonal ending in the lower-right corner, and 0 elsewhere.
    - The last sub-matrix row is copied in the last row of the new matrix, and the last sub-matrix column is copied in the last column of the sub-matrix.

    #### Example

    given the following sub-matrix which converts height values from feet to metres before to subtracts 25 metres:

           ┌    ┐   ┌             ┐   ┌   ┐
           │ z' │ = │ 0.3048  -25 │ × │ z │
           │ 1  │   │ 0         1 │   │ 1 │
           └    ┘   └             ┘   └   ┘

    Then a call to `Matrices​.createPassThrough(2, subMatrix, 1)` will return the following matrix, which can be used for converting the height (z) without affecting the other coordinate values (x,y,t):

           ┌    ┐   ┌                      ┐   ┌   ┐
           │ x  │   │ 1  0  0       0    0 │   │ x │
           │ y  │   │ 0  1  0       0    0 │   │ y │
           │ z' │ = │ 0  0  0.3048  0  -25 │ × │ z │
           │ t  │   │ 0  0  0       1    0 │   │ t │
           │ 1  │   │ 0  0  0       0    1 │   │ 1 │
           └    ┘   └                      ┘   └   ┘

    Parameters:
      `firstAffectedCoordinate` - the lowest index of the affected coordinates.
      `subMatrix` - the matrix to use for affected coordinates.
      `numTrailingCoordinates` - number of trailing coordinates to pass through.
    Returns:
      a matrix for the same transform than the given matrix, augmented with leading and trailing pass-through coordinates.
    See Also:
      - `DefaultMathTransformFactory​.createPassThroughTransform(int, MathTransform, int)`

  - ### createAffine

    public static MatrixSIS createAffine(Matrix derivative, DirectPosition translation)
    Creates an affine transform as the given matrix augmented by the given translation vector and a [0 … 0 1] row. At least one of `derivative` and `translation` arguments shall be non-null. If `derivative` is non-null, the returned matrix will have one more row and one more column than `derivative` with all `derivative` values copied into the new matrix at the same (row, column) indices. If `translation` is non-null, all its coordinate values are copied in the last column of the returned matrix.

    #### Relationship with `MathTransform`

    When used together with `MathTransforms​.derivativeAndTransform(…)` with source coordinates all set to zero, the `derivative` and `translation` arguments can be respectively the return value and destination coordinates computed by `derivativeAndTransform(…)`. The `createAffine(…)` result is then an approximation of the transform in the vicinity of the origin.
    Parameters:
      `derivative` - the scale, shear and rotation of the affine transform.
      `translation` - the translation vector (the last column) of the affine transform.
    Returns:
      an affine transform as the given matrix augmented by the given column and a a [0 … 0 1] row.
    Throws:
      `NullPointerException` - if `derivative` and `translation` are both null.
      `MismatchedMatrixSizeException` - if `derivative` and `translation` are both non-null and the number of `derivative` rows is not equal to the number of `translation` dimensions.
    Since:
      1.1
    See Also:
      - `MathTransforms​.derivativeAndTransform(MathTransform, double[], int, double[], int)`
      - `MathTransforms​.tangent(MathTransform, DirectPosition)`

  - ### resizeAffine

    public static Matrix resizeAffine(Matrix matrix, int numRow, int numCol)
    Returns a matrix with the same content than the given matrix but a different size, assuming an affine transform. This method can be invoked for adding or removing the last dimensions of an affine transform. More specifically:
    - If the given `numCol` is n less than the number of columns in the given matrix, then the n columns before the last column are removed. The last column is left unchanged because it is assumed to contain the translation terms.
    - If the given `numCol` is n more than the number of columns in the given matrix, then n columns are inserted before the last column. All values in the new columns will be zero.
    - If the given `numRow` is n less than the number of rows in the given matrix, then the n rows before the last row are removed. The last row is left unchanged because it is assumed to contain the usual [0 0 0 … 1] terms.
    - If the given `numRow` is n more than the number of rows in the given matrix, then n rows are inserted before the last row. The corresponding offset and scale factors will be 0 and 1 respectively. In other words, new dimensions are propagated unchanged.
    Parameters:
      `matrix` - the matrix to resize. This matrix will never be changed.
      `numRow` - the new number of rows. This is equal to the desired number of target dimensions plus 1.
      `numCol` - the new number of columns. This is equal to the desired number of source dimensions plus 1.
    Returns:
      a new matrix of the given size, or the given `matrix` if no resizing was needed.

  - ### forceUniformScale

    public static boolean forceUniformScale(Matrix matrix, double selector, double[] anchor)
    Forces the matrix coefficients of the given matrix to a uniform scale factor, assuming an affine transform. The uniformization is applied on a row-by-row basis (ignoring the last row and last column), i.e.:
    - All coefficients (excluding translation term) in the same row are multiplied by the same factor.
    - After rescaling, each row (excluding translation column) have the same magnitude.
    The coefficients are multiplied by factors which result in the smallest magnitude if `selector` is 0, the largest magnitude if `selector` is 1, or an intermediate value if `selector` is any value between 0 and 1. In the common case where the matrix has no rotation and no shear terms, the magnitude is directly the scale factors on the matrix diagonal and `selector=0` sets all those scales to the smallest value while `selector=1` sets all those scales to the largest value (ignoring sign).

    Translation terms can be compensated for scale changes if the `anchor` argument is non-null. The anchor gives coordinates of the point to keep at fixed position in target coordinates. For example if the matrix is for transforming coordinates to a screen device and `target` is an `Envelope` with device position and size in pixels, then:

    - `anchor[i] = target​.getMinimum(i)` keeps the image on the left border (i = 0) or upper border (i = 1).
    - `anchor[i] = target​.getMaximum(i)` translates the image to the right border (i = 0) or to the bottom border (i = 1).
    - `anchor[i] = target​.getMedian(i)` translates the image to the device center.
    - Any intermediate values are allowed.
    Parameters:
      `matrix` - the matrix in which to uniformize scale factors. Will be modified in-place.
      `selector` - a value between 0 for smallest scale magnitude and 1 for largest scale magnitude (inclusive). Values outside [0 … 1] range are authorized, but will result in scale factors outside the range of current scale factors in the given matrix.
      `anchor` - point to keep at fixed position in target coordinates, or `null` if none.
    Returns:
      `true` if the given matrix changed as a result of this method call.
    Since:
      1.1

  - ### copy

    public static MatrixSIS copy(Matrix matrix)
    Creates a new matrix which is a copy of the given matrix.
    Parameters:
      `matrix` - the matrix to copy, or `null`.
    Returns:
      a copy of the given matrix, or `null` if the given matrix was null.
    See Also:
      - `MatrixSIS​.clone()`
      - `MatrixSIS​.castOrCopy(Matrix)`

  - ### unmodifiable

    public static MatrixSIS unmodifiable(Matrix matrix)
    Returns an unmodifiable view of the given matrix. The returned matrix is immutable only if the given `matrix` is not modified anymore after this method call.
    Parameters:
      `matrix` - the matrix for which to get an unmodifiable view, or `null`.
    Returns:
      a unmodifiable view of the given matrix, or `null` if the given matrix was null.
    Since:
      0.6

  - ### multiply

    public static MatrixSIS multiply(Matrix m1, Matrix m2) throws MismatchedMatrixSizeException
    Returns a new matrix which is the result of multiplying the first matrix with the second one. In other words, returns `m1` × `m2`.
    Parameters:
      `m1` - the first matrix to multiply.
      `m2` - the second matrix to multiply.
    Returns:
      the result of `m1` × `m2`.
    Throws:
      `MismatchedMatrixSizeException` - if the number of columns in `m1` is not equals to the number of rows in `m2`.
    Since:
      0.6
    See Also:
      - `MatrixSIS​.multiply(Matrix)`

  - ### inverse

    public static MatrixSIS inverse(Matrix matrix) throws NoninvertibleMatrixException
    Returns the inverse of the given matrix.
    Parameters:
      `matrix` - the matrix to inverse, or `null`.
    Returns:
      the inverse of this matrix, or `null` if the given matrix was null.
    Throws:
      `NoninvertibleMatrixException` - if the given matrix is not invertible.
    Since:
      0.6
    See Also:
      - `MatrixSIS​.inverse()`

  - ### isAffine

    public static boolean isAffine(Matrix matrix)
    Returns `true` if the given matrix represents an affine transform. A transform is affine if the matrix is square and its last row contains only zeros, except in the last column which contains 1.
    Parameters:
      `matrix` - the matrix to test.
    Returns:
      `true` if the matrix represents an affine transform.
    See Also:
      - `MatrixSIS​.isAffine()`

  - ### isTranslation

    public static boolean isTranslation(Matrix matrix)
    Returns `true` if the given matrix represents a translation. This method returns `true` if the given matrix is affine and differs from the identity matrix only in the last column.
    Parameters:
      `matrix` - the matrix to test.
    Returns:
      `true` if the matrix represents a translation.
    Since:
      0.7

  - ### isIdentity

    public static boolean isIdentity(Matrix matrix, double tolerance)
    Returns `true` if the given matrix is close to an identity matrix, given a tolerance threshold. This method is equivalent to computing the difference between the given matrix and an identity matrix of identical size, and returning `true` if and only if all differences are smaller than or equal to `tolerance`.

    Caution: Bursa-Wolf parameters, when represented as a matrix, are close to an identity transform and could easily be confused with rounding errors. In case of doubt, it is often safer to use the strict `MatrixSIS​.isIdentity()` method instead than this one.

    Parameters:
      `matrix` - the matrix to test for identity.
      `tolerance` - the tolerance value, or 0 for a strict comparison.
    Returns:
      `true` if this matrix is close to the identity matrix given the tolerance threshold.
    See Also:
      - `MatrixSIS​.isIdentity()`

  - ### equals

    public static boolean equals(Matrix m1, Matrix m2, double epsilon, boolean relative)
    Compares the given matrices for equality, using the given relative or absolute tolerance threshold. The matrix elements are compared as below:
    - `Double​.NaN` values are considered equals to all other NaN values
    - Infinite values are considered equal to other infinite values of the same sign
    - All other values are considered equal if the absolute value of their difference is smaller than or equals to the threshold described below.
    If `relative` is `true`, then for any pair of values v1j,i and v2j,i to compare, the tolerance threshold is scaled by `max(abs(v1), abs(v2))`. Otherwise the threshold is used as-is.
    Parameters:
      `m1` - the first matrix to compare, or `null`.
      `m2` - the second matrix to compare, or `null`.
      `epsilon` - the tolerance value.
      `relative` - if `true`, then the tolerance value is relative to the magnitude of the matrix elements being compared.
    Returns:
      `true` if the values of the two matrix do not differ by a quantity greater than the given tolerance threshold.
    See Also:
      - `MatrixSIS​.equals(Matrix, double)`

  - ### equals

    public static boolean equals(Matrix m1, Matrix m2, ComparisonMode mode)
    Compares the given matrices for equality, using the given comparison strictness level. To be considered equal, the two matrices must met the following conditions, which depend on the `mode` argument:
    - `STRICT`: the two matrices must be of the same class, have the same size and the same element values.
    - `BY_CONTRACT`: the two matrices must have the same size and the same element values, but are not required to be the same implementation class (any `Matrix` is okay).
    - `IGNORE_METADATA`: same as `BY_CONTRACT`, since matrices have no metadata.
    - `APPROXIMATE`: the two matrices must have the same size, but the element values can differ up to some threshold. The threshold value is determined empirically and may change in any future SIS versions. For more control, use `equals(Matrix, Matrix, double, boolean)` instead.
    Parameters:
      `m1` - the first matrix to compare, or `null`.
      `m2` - the second matrix to compare, or `null`.
      `mode` - the strictness level of the comparison.
    Returns:
      `true` if both matrices are equal.
    See Also:
      - `MatrixSIS​.equals(Object, ComparisonMode)`

  - ### toString

    public static String toString(Matrix matrix)
    Returns a unlocalized string representation of the given matrix. For each column, the numbers are aligned on the decimal separator.

    The current implementation formats ±0 and ±1 without trailing `".0"`, and all other values with a per-column uniform number of fraction digits. The ±0 and ±1 values are treated especially because they usually imply a "no scale", "no translation" or "orthogonal axes" meaning. A matrix in SIS is often populated mostly by ±0 and ±1 values, with a few "interesting" values. The simpler ±0 and ±1 formatting makes easier to spot the "interesting" values.

    The following example shows the string representation of an affine transform which swap (latitude, longitude) axes, converts degrees to radians and converts height values from feet to metres:

           ┌                                                       ┐
           │ 0                     0.017453292519943295  0       0 │
           │ 0.017453292519943295  0                     0       0 │
           │ 0                     0                     0.3048  0 │
           │ 0                     0                     0       1 │
           └                                                       ┘

    #### Usage note

    Formatting on a per-column basis is convenient for the kind of matrices used in referencing by coordinates, because each column is typically a displacement vector in a different dimension of the source coordinate reference system. In addition, the last column is often a translation vector having a magnitude very different than the other columns.
    Parameters:
      `matrix` - the matrix for which to get a string representation.
    Returns:
      a string representation of the given matrix.