Search in sources :

Example 26 with Matrix

use of org.opengis.referencing.operation.Matrix in project sis by apache.

the class ConcatenatedTransformDirect2D method derivative.

/**
 * Gets the derivative of this transform at a point.
 *
 * @param  point  the coordinate point where to evaluate the derivative.
 * @return the derivative at the specified point (never {@code null}).
 * @throws TransformException if the derivative can't be evaluated at the specified point.
 */
@Override
public Matrix derivative(final Point2D point) throws TransformException {
    final MathTransform2D transform1 = (MathTransform2D) this.transform1;
    final MathTransform2D transform2 = (MathTransform2D) this.transform2;
    final Matrix matrix1 = transform1.derivative(point);
    final Matrix matrix2 = transform2.derivative(transform1.transform(point, null));
    return Matrices.multiply(matrix2, matrix1);
}
Also used : Matrix(org.opengis.referencing.operation.Matrix) MathTransform2D(org.opengis.referencing.operation.MathTransform2D)

Example 27 with Matrix

use of org.opengis.referencing.operation.Matrix in project sis by apache.

the class ContextualParameters method beforeFormat.

/**
 * Given a transformation chain, replaces the elements around {@code transforms.get(index)} transform by
 * alternative objects to use when formatting WKT. The replacement is performed in-place in the given list.
 *
 * <p>This method shall replace only the previous element and the few next elements that need
 * to be changed as a result of the previous change. This method is not expected to continue
 * the iteration after the changes that are of direct concern to this object.</p>
 *
 * <p>This method is invoked (indirectly) only by {@link ConcatenatedTransform#getPseudoSteps()} in order
 * to get the {@link ParameterValueGroup} of a map projection, or to format a {@code ProjectedCRS} WKT.</p>
 *
 * @param  transforms  the full chain of concatenated transforms.
 * @param  index       the index of this transform in the {@code transforms} chain.
 * @param  inverse     always {@code false}, except if we are formatting the inverse transform.
 * @return index of this transform in the {@code transforms} chain after processing.
 *
 * @see ConcatenatedTransform#getPseudoSteps()
 * @see AbstractMathTransform#beforeFormat(List, int, boolean)
 */
final int beforeFormat(final List<Object> transforms, int index, final boolean inverse) {
    /*
         * We expect affine transforms before and after the normalized projection. Extracts those
         * affine transforms now. If one or both are missing, we will treat null as an identity
         * transform. We will not replace the elements in the list before new values for those
         * affine transforms have been fully calculated.
         */
    Matrix before = null;
    Matrix after = null;
    if (index != 0) {
        final Object candidate = transforms.get(index - 1);
        if (candidate instanceof MathTransform) {
            before = MathTransforms.getMatrix((MathTransform) candidate);
        }
    }
    if (index + 1 < transforms.size()) {
        final Object candidate = transforms.get(index + 1);
        if (candidate instanceof MathTransform) {
            after = MathTransforms.getMatrix((MathTransform) candidate);
        }
    }
    final boolean hasBefore = (before != null);
    final boolean hasAfter = (after != null);
    /*
         * We assume that the "before" affine contains the normalize operation to be applied
         * before the projection. However it may contains more than just this normalization,
         * because it may have been concatenated with any user-defined transform (for example
         * in order to apply a change of axis order). We need to separate the "user-defined"
         * step from the "normalize" step.
         */
    MatrixSIS userDefined;
    try {
        userDefined = getMatrix(inverse ? MatrixRole.DENORMALIZATION : MatrixRole.INVERSE_NORMALIZATION);
    } catch (IllegalStateException e) {
        /*
             * Should never happen. But if it does, we abandon the attempt to change
             * the list elements and will format the objects in their "raw" format.
             */
        unexpectedException(e);
        return index;
    }
    if (hasBefore) {
        userDefined = userDefined.multiply(before);
    }
    /*
         * At this point "userDefined" is the affine transform to show to user instead of the
         * "before" affine transform. Replaces "before" by "userDefined" locally (but not yet
         * in the list), or set it to null (meaning that it will be removed from the list) if
         * it is identity, which happen quite often.
         *
         * Note on rounding error: the coefficients are often either 0 or 1 since the transform
         * is often for changing axis order. Thanks to double-double arithmetic in SIS matrices,
         * the non-zero values are usually accurate. But the values that should be zero are much
         * harder to get right. Sometime we see small values (around 1E-12) in the last column of
         * the 'before' matrix below. Since this column contains translation terms, those numbers
         * are in the unit of measurement of input values of the MathTransform after the matrix.
         *
         *   - For forward map projections, those values are conceptually in decimal degrees
         *     (in fact the values are converted to radians but not by this 'before' matrix).
         *
         *   - For inverse map projections, those values are conceptually in metres (in fact
         *     converted to distances on a unitary ellipsoid but not by this 'before' matrix).
         *
         *   - Geographic/Geocentric transformations behave like map projections in regard to units.
         *     Molodensky transformations conceptually use always decimal degrees. There is not much
         *     other cases since this mechanism is internal to SIS (not in public API).
         *
         * Consequently we set the tolerance threshold to ANGULAR_TOLERANCE. We do not bother (at least
         * for now) to identify the cases where we could use LINEAR_TOLERANCE because just checking the
         * 'inverse' flag is not sufficient (e.g. the Molodensky case). Since the angular tolerance is
         * smaller than the linear one, unconditional usage of ANGULAR_TOLERANCE is more conservative.
         */
    before = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined;
    /*
         * Compute the "after" affine transform in a way similar than the "before" affine.
         * Note that if this operation fails, we will cancel everything we would have done
         * in this method (i.e. we do not touch the transforms list at all).
         */
    try {
        userDefined = getMatrix(inverse ? MatrixRole.NORMALIZATION : MatrixRole.INVERSE_DENORMALIZATION);
    } catch (IllegalStateException e) {
        unexpectedException(e);
        return index;
    }
    if (hasAfter) {
        userDefined = Matrices.multiply(after, userDefined);
    }
    /*
         * Note on rounding error: same discussion than the "note on rounding error" of the 'before' matrix,
         * with the following differences:
         *
         *   - For forward map projections, unit of measurements of translation terms are conceptually
         *     metres (instead than degrees) multiplied by the scale factors in the 'after' matrix.
         *
         *   - For inverse map projections, unit of measurements of translation terms are conceptually
         *     degrees (instead than metres) multiplied by the scale factors in the 'after' matrix.
         *
         *   - And so on for all cases: swap the units of the forward and inverse cases, then multiply
         *     by the scale factor. Note that the multiplication step does not exist in the 'before' case.
         *
         * Since we are seeking for the identity matrix, the scale factor is 1. We do not bother to distinguish
         * the ANGULAR_TOLERANCE and LINEAR_TOLERANCE cases for the same reasons than for the 'before' matrix.
         */
    after = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined;
    /*
         * At this point we have computed all the affine transforms to show to the user.
         * We can replace the elements in the list. The transform referenced by transforms.get(index)
         * is usually a NormalizedProjection, to be replaced by a ContextualParameters instance in order
         * to format real parameter values (semi-major axis, scale factor, etc.)
         * instead than a semi-major axis length of 1.
         */
    if (before == null) {
        if (hasBefore) {
            final Object old = transforms.remove(--index);
            assert (old instanceof LinearTransform);
        }
    } else {
        if (hasBefore) {
            final Object old = transforms.set(index - 1, before);
            assert (old instanceof LinearTransform);
        } else {
            transforms.add(index++, before);
        }
    }
    transforms.set(index, new WKT(inverse));
    if (after == null) {
        if (hasAfter) {
            final Object old = transforms.remove(index + 1);
            assert (old instanceof LinearTransform);
        }
    } else {
        if (hasAfter) {
            final Object old = transforms.set(index + 1, after);
            assert (old instanceof LinearTransform);
        } else {
            transforms.add(index + 1, after);
        }
    }
    return index;
}
Also used : Matrix(org.opengis.referencing.operation.Matrix) ExtendedPrecisionMatrix(org.apache.sis.internal.referencing.ExtendedPrecisionMatrix) MathTransform(org.opengis.referencing.operation.MathTransform) FormattableObject(org.apache.sis.io.wkt.FormattableObject) MatrixSIS(org.apache.sis.referencing.operation.matrix.MatrixSIS)

Example 28 with Matrix

use of org.opengis.referencing.operation.Matrix in project sis by apache.

the class ContextualParameters method getMatrix.

/**
 * Returns the affine transforms to be applied before or after the non-linear kernel operation.
 * Immediately after {@linkplain #ContextualParameters(OperationMethod) construction}, those matrices
 * are modifiable identity matrices. Callers can modify the matrix element values, typically by calls to
 * the {@link MatrixSIS#convertBefore(int, Number, Number) MatrixSIS.convertBefore(…)} method.
 * Alternatively, the following methods can be invoked for applying some frequently used configurations:
 *
 * <ul>
 *   <li>{@link #normalizeGeographicInputs(double)}</li>
 *   <li>{@link #denormalizeGeographicOutputs(double)}</li>
 * </ul>
 *
 * After the {@link #completeTransform(MathTransformFactory, MathTransform) completeTransform(…)} method has been
 * invoked, the matrices returned by this method are {@linkplain Matrices#unmodifiable(Matrix) unmodifiable}.
 *
 * <div class="note"><b>Application to map projections:</b>
 * after {@link org.apache.sis.referencing.operation.projection.NormalizedProjection} construction, the matrices
 * returned by {@code projection.getContextualParameters().getMatrix(…)} are initialized to the values shown below.
 * Note that some {@code NormalizedProjection} subclasses apply further modifications to those matrices.
 *
 * <table class="sis">
 *   <caption>Initial matrix coefficients after {@code NormalizedProjection} construction</caption>
 *   <tr>
 *     <th>{@code getMatrix(NORMALIZATION)}</th>
 *     <th class="sep">{@code getMatrix(DENORMALIZATION)}</th>
 *   </tr><tr>
 *     <td>{@include formulas.html#NormalizeGeographic}</td>
 *     <td class="sep">{@include formulas.html#DenormalizeCartesian}</td>
 *   </tr>
 * </table>
 * </div>
 *
 * @param  role  {@code NORMALIZATION} for fetching the <cite>normalization</cite> transform to apply before the kernel,
 *               {@code DENORMALIZATION} for the <cite>denormalization</cite> transform to apply after the kernel, or
 *               {@code INVERSE_*} for the inverse of the above-cited matrices.
 * @return the matrix for the requested normalization or denormalization affine transform.
 *
 * @since 0.7
 */
public final MatrixSIS getMatrix(MatrixRole role) {
    final Matrix fallback;
    final ContextualParameters inverse;
    synchronized (this) {
        switch(role) {
            default:
                throw new AssertionError(role);
            case NORMALIZATION:
                return toMatrixSIS(normalize);
            case DENORMALIZATION:
                return toMatrixSIS(denormalize);
            case INVERSE_NORMALIZATION:
                role = MatrixRole.DENORMALIZATION;
                fallback = normalize;
                break;
            case INVERSE_DENORMALIZATION:
                role = MatrixRole.NORMALIZATION;
                fallback = denormalize;
                break;
        }
        // Copy the reference while we are inside the synchronized block.
        inverse = this.inverse;
    }
    /*
         * Following must be outside the synchronized block in order to avoid potential deadlock while invoking
         * inverse.getMatrix(role). We do not cache the matrix here, but 'inverse' is likely to have cached it.
         */
    final Matrix m;
    if (inverse != null) {
        m = inverse.getMatrix(role);
    } else
        try {
            m = Matrices.inverse(fallback);
        } catch (NoninvertibleMatrixException e) {
            throw new IllegalStateException(Errors.format(Errors.Keys.CanNotCompute_1, role), e);
        }
    return Matrices.unmodifiable(m);
}
Also used : Matrix(org.opengis.referencing.operation.Matrix) ExtendedPrecisionMatrix(org.apache.sis.internal.referencing.ExtendedPrecisionMatrix) NoninvertibleMatrixException(org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException)

Example 29 with Matrix

use of org.opengis.referencing.operation.Matrix in project sis by apache.

the class DefaultMathTransformFactory method swapAndScaleAxes.

/**
 * Given a transform between normalized spaces,
 * creates a transform taking in account axis directions, units of measurement and longitude rotation.
 * This method {@linkplain #createConcatenatedTransform concatenates} the given parameterized transform
 * with any other transform required for performing units changes and ordinates swapping.
 *
 * <p>The given {@code parameterized} transform shall expect
 * {@linkplain org.apache.sis.referencing.cs.AxesConvention#NORMALIZED normalized} input coordinates and
 * produce normalized output coordinates. See {@link org.apache.sis.referencing.cs.AxesConvention} for more
 * information about what Apache SIS means by "normalized".</p>
 *
 * <div class="note"><b>Example:</b>
 * The most typical examples of transforms with normalized inputs/outputs are normalized
 * map projections expecting (<cite>longitude</cite>, <cite>latitude</cite>) inputs in degrees
 * and calculating (<cite>x</cite>, <cite>y</cite>) coordinates in metres,
 * both of them with ({@linkplain org.opengis.referencing.cs.AxisDirection#EAST East},
 * {@linkplain org.opengis.referencing.cs.AxisDirection#NORTH North}) axis orientations.</div>
 *
 * <div class="section">Controlling the normalization process</div>
 * Users who need a different normalized space than the default one way find more convenient to
 * override the {@link Context#getMatrix Context.getMatrix(ContextualParameters.MatrixRole)} method.
 *
 * @param  parameterized  a transform for normalized input and output coordinates.
 * @param  context        source and target coordinate systems in which the transform is going to be used.
 * @return a transform taking in account unit conversions and axis swapping.
 * @throws FactoryException if the object creation failed.
 *
 * @see org.apache.sis.referencing.cs.AxesConvention#NORMALIZED
 * @see org.apache.sis.referencing.operation.DefaultConversion#DefaultConversion(Map, OperationMethod, MathTransform, ParameterValueGroup)
 *
 * @since 0.7
 */
public MathTransform swapAndScaleAxes(final MathTransform parameterized, final Context context) throws FactoryException {
    ArgumentChecks.ensureNonNull("parameterized", parameterized);
    ArgumentChecks.ensureNonNull("context", context);
    /*
         * Computes matrix for swapping axis and performing units conversion.
         * There is one matrix to apply before projection on (longitude,latitude)
         * coordinates, and one matrix to apply after projection on (easting,northing)
         * coordinates.
         */
    final Matrix swap1 = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
    final Matrix swap3 = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
    /*
         * Prepares the concatenation of the matrices computed above and the projection.
         * Note that at this stage, the dimensions between each step may not be compatible.
         * For example the projection (step2) is usually two-dimensional while the source
         * coordinate system (step1) may be three-dimensional if it has a height.
         */
    MathTransform step1 = (swap1 != null) ? createAffineTransform(swap1) : MathTransforms.identity(parameterized.getSourceDimensions());
    MathTransform step3 = (swap3 != null) ? createAffineTransform(swap3) : MathTransforms.identity(parameterized.getTargetDimensions());
    MathTransform step2 = parameterized;
    /*
         * Special case for the way EPSG handles reversal of axis direction. For now the "Vertical Offset" (EPSG:9616)
         * method is the only one for which we found a need for special case. But if more special cases are added in a
         * future SIS version, then we should replace the static method by a non-static one defined in AbstractProvider.
         */
    if (context.provider instanceof VerticalOffset) {
        step2 = VerticalOffset.postCreate(step2, swap3);
    }
    /*
         * If the target coordinate system has a height, instructs the projection to pass
         * the height unchanged from the base CRS to the target CRS. After this block, the
         * dimensions of 'step2' and 'step3' should match.
         */
    final int numTrailingOrdinates = step3.getSourceDimensions() - step2.getTargetDimensions();
    if (numTrailingOrdinates > 0) {
        step2 = createPassThroughTransform(0, step2, numTrailingOrdinates);
    }
    /*
         * If the source CS has a height but the target CS doesn't, drops the extra coordinates.
         * After this block, the dimensions of 'step1' and 'step2' should match.
         */
    final int sourceDim = step1.getTargetDimensions();
    final int targetDim = step2.getSourceDimensions();
    if (sourceDim > targetDim) {
        final Matrix drop = Matrices.createDiagonal(targetDim + 1, sourceDim + 1);
        // Element in the lower-right corner.
        drop.setElement(targetDim, sourceDim, 1);
        step1 = createConcatenatedTransform(createAffineTransform(drop), step1);
    }
    MathTransform mt = createConcatenatedTransform(createConcatenatedTransform(step1, step2), step3);
    /*
         * At this point we finished to create the transform.  But before to return it, verify if the
         * parameterized transform given in argument had some custom parameters. This happen with the
         * Equirectangular projection, which can be simplified as an AffineTransform while we want to
         * continue to describe it with the "semi_major", "semi_minor", etc. parameters  instead than
         * "elt_0_0", "elt_0_1", etc.  The following code just forwards those parameters to the newly
         * created transform; it does not change the operation.
         */
    if (parameterized instanceof ParameterizedAffine && !(mt instanceof ParameterizedAffine)) {
        mt = ((ParameterizedAffine) parameterized).newTransform(mt);
    }
    return mt;
}
Also used : Matrix(org.opengis.referencing.operation.Matrix) MathTransform(org.opengis.referencing.operation.MathTransform) ParameterizedAffine(org.apache.sis.internal.referencing.j2d.ParameterizedAffine) VerticalOffset(org.apache.sis.internal.referencing.provider.VerticalOffset)

Example 30 with Matrix

use of org.opengis.referencing.operation.Matrix in project sis by apache.

the class TransferFunction method setLinearTerms.

/**
 * Sets the {@link #scale} and {@link #offset} terms from the given function.
 *
 * @param  function  the transform to set.
 * @throws IllegalArgumentException if this method does not recognize the given transform.
 */
private void setLinearTerms(final LinearTransform function) throws IllegalArgumentException {
    final Matrix m = function.getMatrix();
    final int numRow = m.getNumRow();
    final int numCol = m.getNumCol();
    if (numRow != 2 || numCol != 2) {
        final Integer two = 2;
        throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedMatrixSize_4, two, two, numRow, numCol));
    }
    scale = m.getElement(0, 0);
    offset = m.getElement(0, 1);
}
Also used : Matrix(org.opengis.referencing.operation.Matrix)

Aggregations

Matrix (org.opengis.referencing.operation.Matrix)63 Test (org.junit.Test)20 DependsOnMethod (org.apache.sis.test.DependsOnMethod)11 MathTransform (org.opengis.referencing.operation.MathTransform)8 HashMap (java.util.HashMap)6 ParameterValueGroup (org.opengis.parameter.ParameterValueGroup)6 NoninvertibleTransformException (org.opengis.referencing.operation.NoninvertibleTransformException)5 TransformException (org.opengis.referencing.operation.TransformException)5 FormattableObject (org.apache.sis.io.wkt.FormattableObject)4 DirectPosition1D (org.apache.sis.geometry.DirectPosition1D)3 ExtendedPrecisionMatrix (org.apache.sis.internal.referencing.ExtendedPrecisionMatrix)3 Matrix3 (org.apache.sis.referencing.operation.matrix.Matrix3)3 CoordinateSystem (org.opengis.referencing.cs.CoordinateSystem)3 FactoryException (org.opengis.util.FactoryException)3 DirectPosition2D (org.apache.sis.geometry.DirectPosition2D)2 DirectPositionView (org.apache.sis.internal.referencing.DirectPositionView)2 Parameterized (org.apache.sis.parameter.Parameterized)2 MatrixSIS (org.apache.sis.referencing.operation.matrix.MatrixSIS)2 GeneralParameterValue (org.opengis.parameter.GeneralParameterValue)2 ParameterValue (org.opengis.parameter.ParameterValue)2