Java Code Examples for org.opengis.referencing.operation.MathTransform#getTargetDimensions()

The following examples show how to use org.opengis.referencing.operation.MathTransform#getTargetDimensions() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: EllipsoidToCentricTransform.java    From sis with Apache License 2.0 6 votes vote down vote up
/**
 * If this transform returns three-dimensional outputs, and if the transform just after this one
 * just drops the height values, then replaces this transform by a two-dimensional one.
 * The intent is to handle the following sequence of operations defined in the EPSG database:
 *
 * <ol>
 *   <li>Inverse of <cite>Geographic/geocentric conversions</cite> (EPSG:9602)</li>
 *   <li><cite>Geographic 3D to 2D conversion</cite> (EPSG:9659)</li>
 * </ol>
 *
 * Replacing the above sequence by a two-dimensional {@code EllipsoidToCentricTransform} instance
 * allow the following optimizations:
 *
 * <ul>
 *   <li>Avoid computation of <var>h</var> value.</li>
 *   <li>Allow use of the more efficient {@link java.awt.geom.AffineTransform} after this transform
 *       instead than a transform based on a matrix of size 3×4.</li>
 * </ul>
 */
@Override
protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other,
        final MathTransformFactory factory) throws FactoryException
{
    if (!applyOtherFirst && forward.withHeight && other instanceof LinearTransform && other.getTargetDimensions() == 2) {
        /*
         * Found a 3×4 matrix after this transform. We can reduce to a 3×3 matrix only if no dimension
         * use the column that we are about to drop (i.e. all coefficients in that column are zero).
         */
        Matrix matrix = ((LinearTransform) other).getMatrix();
        if (matrix.getElement(0,2) == 0 &&
            matrix.getElement(1,2) == 0 &&
            matrix.getElement(2,2) == 0)
        {
            matrix = MatrixSIS.castOrCopy(matrix).removeColumns(2, 3);
            final MathTransform tr2D = forward.create2D().inverse();
            if (factory != null) {
                return factory.createConcatenatedTransform(tr2D, factory.createAffineTransform(matrix));
            } else {
                return ConcatenatedTransform.create(tr2D, MathTransforms.linear(matrix), factory);
            }
        }
    }
    return super.tryConcatenate(applyOtherFirst, other, factory);
}
 
Example 2
Source File: ProjectedTransformTry.java    From sis with Apache License 2.0 6 votes vote down vote up
/**
 * Prepares a new attempt to project a localization grid.
 * All arguments are stored as-is (arrays are not cloned).
 *
 * @param name               a name by witch this projection attempt is identified, or {@code null}.
 * @param projection         conversion from non-linear grid to something that may be more linear.
 * @param projToGrid         maps {@code projection} dimensions to {@link LinearTransformBuilder} target dimensions.
 * @param expectedDimension  number of {@link LinearTransformBuilder} target dimensions.
 */
ProjectedTransformTry(final String name, final MathTransform projection, final int[] projToGrid, int expectedDimension) {
    ArgumentChecks.ensureNonNull("name", name);
    ArgumentChecks.ensureNonNull("projection", projection);
    this.name       = name;
    this.projection = projection;
    this.projToGrid = projToGrid;
    int side = 0;                           // 0 = problem with source dimensions, 1 = problem with target dimensions.
    int actual = projection.getSourceDimensions();
    if (actual <= expectedDimension) {
        expectedDimension = projToGrid.length;
        if (actual == expectedDimension) {
            actual = projection.getTargetDimensions();
            if (actual == expectedDimension) {
                return;
            }
            side = 1;
        }
    }
    throw new MismatchedDimensionException(Resources.format(
            Resources.Keys.MismatchedTransformDimension_3, side, expectedDimension, actual));
}
 
Example 3
Source File: AbstractMathTransform.java    From sis with Apache License 2.0 6 votes vote down vote up
/**
 * Returns {@code true} if {@code tr1} is the inverse of {@code tr2}.
 * If this method is unsure, it conservatively returns {@code false}.
 * The transform that may be inverted is {@code tr1}.
 *
 * @param  tr1  the transform to inverse.
 * @param  tr2  the transform that may be the inverse of {@code tr1}.
 * @return whether this transform is the inverse of the given transform. If unsure, {@code false}.
 */
static boolean isInverseEquals(MathTransform tr1, final MathTransform tr2) {
    if (tr1.getSourceDimensions() != tr2.getTargetDimensions() ||
        tr1.getTargetDimensions() != tr2.getSourceDimensions())
    {
        return false;
    }
    try {
        tr1 = tr1.inverse();
    } catch (NoninvertibleTransformException e) {
        return false;
    }
    if (tr1 instanceof LenientComparable) {
        return ((LenientComparable) tr1).equals(tr2, ComparisonMode.APPROXIMATE);
    }
    if (tr2 instanceof LenientComparable) {
        return ((LenientComparable) tr2).equals(tr1, ComparisonMode.APPROXIMATE);
    }
    return tr1.equals(tr2);
}
 
Example 4
Source File: Transformer.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Transforms the given points.
 */
final double[][] transform(final double[][] points) {
    final MathTransform         mt       = operation.getMathTransform();
    final GeneralDirectPosition sourcePt = new GeneralDirectPosition(mt.getSourceDimensions());
    final GeneralDirectPosition targetPt = new GeneralDirectPosition(mt.getTargetDimensions());
    final double[][] result = new double[points.length][];
    for (int j=0; j<points.length; j++) {
        final double[] coords = points[j];
        if (coords != null) {                                               // Paranoiac check.
            for (int i=sourcePt.coordinates.length; --i>=0;) {
                sourcePt.coordinates[i] = (i < coords.length) ? coords[i] : 0;
            }
            try {
                result[j] = mt.transform(sourcePt, targetPt).getCoordinate();
            } catch (TransformException exception) {
                /*
                 * The coordinate operation failed for this particular point. But maybe it will
                 * succeed for an other point. Set the values to NaN and continue the loop. Note:
                 * we will report the failure for logging purpose, but only the first one since
                 * all subsequent failures are likely to be the same one.
                 */
                final double[] pad = new double[mt.getTargetDimensions()];
                Arrays.fill(pad, Double.NaN);
                result[j] = pad;
                if (warning == null) {
                    warning = exception;
                }
            }
        }
    }
    return result;
}
 
Example 5
Source File: DefaultOperationMethod.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Convenience constructor that creates an operation method from a math transform.
 * The information provided in the newly created object are approximations, and
 * usually acceptable only as a fallback when no other information are available.
 *
 * @param  transform  the math transform to describe.
 */
public DefaultOperationMethod(final MathTransform transform) {
    super(getProperties(transform));
    sourceDimensions = transform.getSourceDimensions();
    targetDimensions = transform.getTargetDimensions();
    if (transform instanceof Parameterized) {
        parameters = ((Parameterized) transform).getParameterDescriptors();
    } else {
        parameters = null;
    }
    formula = null;
}
 
Example 6
Source File: SpecializableTransform.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Creates a new transform with the given global transform and some amount of specializations.
 *
 * @param  global  the transform to use globally where there is no suitable specialization.
 * @param  specializations  more accurate transforms available in sub-areas.
 */
SpecializableTransform(final MathTransform global, final Map<Envelope,MathTransform> specializations) {
    this.global = global;
    SubArea root = null;
    final int sourceDim = global.getSourceDimensions();
    final int targetDim = global.getTargetDimensions();
    for (final Map.Entry<Envelope,MathTransform> entry : specializations.entrySet()) {
        MathTransform tr = entry.getValue();
        ensureDimensionMatches(0, sourceDim, tr.getSourceDimensions());
        ensureDimensionMatches(1, targetDim, tr.getTargetDimensions());
        /*
         * If the given MathTransform is another SpecializableTransform, then instead of storing nested
         * SpecializableTransforms we will store directly the specializations that it contains. It will
         * reduce the amountof steps when transforming coordinates.
         */
        List<SubArea> inherited = Collections.emptyList();
        if (tr instanceof SpecializableTransform) {
            inherited = ((SpecializableTransform) tr).roots();
            tr        = ((SpecializableTransform) tr).global;
        }
        final SubArea area = new SubArea(entry.getKey(), tr);
        ArgumentChecks.ensureDimensionMatches("envelope", sourceDim, area);
        if (!area.isEmpty()) {
            if (root == null) root = area;
            else root.addNode(area);
            /*
             * If the transform was another SpecializableTransform, copies the nested RTreeNode.
             * A copy is necessary: we shall not modify the nodes of the given transform.
             */
            for (final SubArea other : inherited) {
                final SubArea e = new SubArea(other, other.transform);
                e.intersect(area);
                if (!e.isEmpty()) {
                    root.addNode(e);
                }
            }
        }
    }
    domains = (root != null) ? root.finish() : null;
}
 
Example 7
Source File: PassThroughTransform.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Constructs the general {@code PassThroughTransform} object. An optimization is done right in
 * the constructor for the case where the sub-transform is already a {@code PassThroughTransform}.
 * It is caller's responsibility to ensure that the argument values are valid.
 */
private static PassThroughTransform newInstance(final int firstAffectedCoordinate,
                                                final MathTransform subTransform,
                                                final int numTrailingCoordinates)
{
    int dim = subTransform.getSourceDimensions();
    if (subTransform.getTargetDimensions() == dim) {
        dim += firstAffectedCoordinate + numTrailingCoordinates;
        if (dim == 2) {
            return new PassThroughTransform2D(firstAffectedCoordinate, subTransform, numTrailingCoordinates);
        }
    }
    return new PassThroughTransform(firstAffectedCoordinate, subTransform, numTrailingCoordinates);
}
 
Example 8
Source File: AbstractCoordinateOperation.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
     * Ensures that {@link #sourceCRS}, {@link #targetCRS} and {@link #interpolationCRS} dimensions
     * are consistent with {@link #transform} input and output dimensions.
     */
    final void checkDimensions(final Map<String,?> properties) {
        final MathTransform transform = this.transform;                     // Protect from changes.
        if (transform != null) {
            final int interpDim = ReferencingUtilities.getDimension(interpolationCRS);
check:      for (int isTarget=0; ; isTarget++) {        // 0 == source check; 1 == target check.
                final CoordinateReferenceSystem crs;    // Will determine the expected dimensions.
                int actual;                             // The MathTransform number of dimensions.
                switch (isTarget) {
                    case 0: crs = sourceCRS; actual = transform.getSourceDimensions(); break;
                    case 1: crs = targetCRS; actual = transform.getTargetDimensions(); break;
                    default: break check;
                }
                int expected = ReferencingUtilities.getDimension(crs);
                if (interpDim != 0) {
                    if (actual == expected || actual < interpDim) {
                        // This check is not strictly necessary as the next check below would catch the error,
                        // but we provide here a hopefully more helpful error message for a common mistake.
                        throw new IllegalArgumentException(Resources.forProperties(properties)
                                .getString(Resources.Keys.MissingInterpolationOrdinates));
                    }
                    expected += interpDim;
                }
                if (crs != null && actual != expected) {
                    throw new IllegalArgumentException(Resources.forProperties(properties).getString(
                            Resources.Keys.MismatchedTransformDimension_3, isTarget, expected, actual));
                }
            }
        }
        computeTransientFields();
    }
 
Example 9
Source File: MathTransforms.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Returns the coefficients of an affine transform in the vicinity of the given position.
 * If the given transform is linear, then this method produces a result identical to {@link #getMatrix(MathTransform)}.
 * Otherwise the returned matrix can be used for {@linkplain #linear(Matrix) building a linear transform} which can be
 * used as an approximation of the given transform for short distances around the given position.
 *
 * @param  transform  the transform to approximate by an affine transform.
 * @param  position   position in source CRS around which to get the coefficients of an affine transform approximation.
 * @return the matrix of the given transform around the given position.
 * @throws TransformException if an error occurred while transforming the given position or computing the derivative at
 *         that position.
 *
 * @since 1.0
 *
 * @see #linear(MathTransform, DirectPosition)
 */
public static Matrix getMatrix(final MathTransform transform, final DirectPosition position) throws TransformException {
    ArgumentChecks.ensureNonNull("transform", transform);
    final int srcDim = transform.getSourceDimensions();
    ArgumentChecks.ensureDimensionMatches("position", srcDim, position);            // Null position is okay for now.
    final Matrix affine = getMatrix(transform);
    if (affine != null) {
        return affine;
        // We accept null position here for consistency with MathTransform.derivative(DirectPosition).
    }
    ArgumentChecks.ensureNonNull("position", position);
    final int tgtDim = transform.getTargetDimensions();
    double[] pts = new double[Math.max(srcDim + 1, tgtDim)];
    for (int i=0; i<srcDim; i++) {
        pts[i] = position.getOrdinate(i);
    }
    final Matrix d = derivativeAndTransform(transform, pts, 0, pts, 0);
    final MatrixSIS a = Matrices.createZero(tgtDim + 1, srcDim + 1);
    for (int j=0; j<tgtDim; j++) {
        for (int i=0; i<srcDim; i++) {
            a.setElement(j, i, d.getElement(j, i));
        }
        a.setElement(j, srcDim, pts[j]);
        pts[j] = -position.getOrdinate(j);                  // To be used by a.translate(pts) later.
    }
    a.setElement(tgtDim, srcDim, 1);
    /*
     * At this point, the translation column in the matrix is set as if the coordinate system origin
     * was at the given position. We want to keep the original coordinate system origin. We do that
     * be applying a translation in the opposite direction before the affine transform. Translation
     * terms were opportunistically set in the previous loop.
     */
    pts = ArraysExt.resize(pts, srcDim + 1);
    pts[srcDim] = 1;
    a.translate(pts);
    return a;
}
 
Example 10
Source File: PassThroughTransformTest.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Tests the current {@linkplain #transform transform} using an array of random coordinate values,
 * and compares the result against the expected ones. This method computes itself the expected results.
 *
 * @param  subTransform           the sub transform used by the current pass-through transform.
 * @param  firstAffectedCoordinate  first input/output dimension used by {@code subTransform}.
 * @throws TransformException if a transform failed.
 */
private void verifyTransform(final MathTransform subTransform, final int firstAffectedCoordinate) throws TransformException {
    validate();
    /*
     * Prepare two arrays:
     *   - passthrough data, to be given to the transform to be tested.
     *   - sub-transform data, which we will use internally for verifying the pass-through work.
     */
    final int      sourceDim        = transform.getSourceDimensions();
    final int      targetDim        = transform.getTargetDimensions();
    final int      subSrcDim        = subTransform.getSourceDimensions();
    final int      subTgtDim        = subTransform.getTargetDimensions();
    final int      numPts           = ORDINATE_COUNT / sourceDim;
    final double[] passthroughData  = CoordinateDomain.RANGE_10.generateRandomInput(random, sourceDim, numPts);
    final double[] subTransformData = new double[numPts * StrictMath.max(subSrcDim, subTgtDim)];
    Arrays.fill(subTransformData, Double.NaN);
    for (int i=0; i<numPts; i++) {
        System.arraycopy(passthroughData, firstAffectedCoordinate + i*sourceDim,
                         subTransformData, i*subSrcDim, subSrcDim);
    }
    subTransform.transform(subTransformData, 0, subTransformData, 0, numPts);
    assertFalse(ArraysExt.hasNaN(subTransformData));
    /*
     * Build the array of expected data by copying ourself the sub-transform results.
     */
    final int numTrailingCoordinates = targetDim - subTgtDim - firstAffectedCoordinate;
    final double[] expectedData = new double[targetDim * numPts];
    for (int i=0; i<numPts; i++) {
        int srcOffset = i * sourceDim;
        int dstOffset = i * targetDim;
        final int s = firstAffectedCoordinate + subSrcDim;
        System.arraycopy(passthroughData,  srcOffset,   expectedData, dstOffset,   firstAffectedCoordinate);
        System.arraycopy(subTransformData, i*subTgtDim, expectedData, dstOffset += firstAffectedCoordinate, subTgtDim);
        System.arraycopy(passthroughData,  srcOffset+s, expectedData, dstOffset + subTgtDim, numTrailingCoordinates);
    }
    assertEquals(subTransform.isIdentity(), Arrays.equals(passthroughData, expectedData));
    /*
     * Now process to the transform and compares the results with the expected ones.
     */
    tolerance         = 0;          // Results should be strictly identical because we used the same inputs.
    final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts];
    transform.transform(passthroughData, 0, transformedData, 0, numPts);
    assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.",
            targetDim, expectedData, 0, transformedData, 0, numPts, false);
    /*
     * Test inverse transform.
     */
    if (isInverseTransformSupported) {
        tolerance         = 1E-8;
        Arrays.fill(transformedData, Double.NaN);
        transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
        assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.",
                sourceDim, passthroughData, 0, transformedData, 0, numPts, false);
    }
    /*
     * Verify the consistency between different 'transform(…)' methods.
     */
    final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData);
    final float[] targetAsFloat = verifyConsistency(sourceAsFloat);
    assertEquals("Unexpected length of transformed array.", expectedData.length, targetAsFloat.length);
}
 
Example 11
Source File: Transformer.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Creates a new transformer.
 */
Transformer(final ReferencingFunctions caller, final CoordinateReferenceSystem sourceCRS,
        final String targetCRS, final double[][] points) throws FactoryException, DataStoreException
{
    /*
     * Computes the area of interest.
     */
    final GeographicCRS domainCRS = ReferencingUtilities.toNormalizedGeographicCRS(sourceCRS, false, false);
    if (domainCRS != null) {
        final MathTransform toDomainOfValidity = CRS.findOperation(sourceCRS, domainCRS, null).getMathTransform();
        final int dimension = toDomainOfValidity.getSourceDimensions();
        final double[] domainCoord = new double[toDomainOfValidity.getTargetDimensions()];
        if (domainCoord.length >= 2) {
            westBoundLongitude = Double.POSITIVE_INFINITY;
            southBoundLatitude = Double.POSITIVE_INFINITY;
            eastBoundLongitude = Double.NEGATIVE_INFINITY;
            northBoundLatitude = Double.NEGATIVE_INFINITY;
            if (points != null) {
                for (final double[] coord : points) {
                    if (coord != null && coord.length == dimension) {
                        try {
                            toDomainOfValidity.transform(coord, 0, domainCoord, 0, 1);
                        } catch (TransformException e) {
                            if (warning == null) {
                                warning = e;
                            }
                            continue;
                        }
                        final double x = domainCoord[0];
                        final double y = domainCoord[1];
                        if (x < westBoundLongitude) westBoundLongitude = x;
                        if (x > eastBoundLongitude) eastBoundLongitude = x;
                        if (y < southBoundLatitude) southBoundLatitude = y;
                        if (y > northBoundLatitude) northBoundLatitude = y;
                    }
                }
            }
        }
    }
    /*
     * Get the coordinate operation from the cache if possible, or compute it otherwise.
     */
    final boolean hasAreaOfInterest = hasAreaOfInterest();
    final CacheKey<CoordinateOperation> key = new CacheKey<>(CoordinateOperation.class, targetCRS, sourceCRS,
            hasAreaOfInterest ? new double[] {westBoundLongitude, eastBoundLongitude,
                                              southBoundLatitude, northBoundLatitude} : null);
    operation = key.peek();
    if (operation == null) {
        final Cache.Handler<CoordinateOperation> handler = key.lock();
        try {
            operation = handler.peek();
            if (operation == null) {
                operation = CRS.findOperation(sourceCRS, caller.getCRS(targetCRS),
                        hasAreaOfInterest ? getAreaOfInterest() : null);
            }
        } finally {
            handler.putAndUnlock(operation);
        }
    }
}
 
Example 12
Source File: AbstractSingleOperation.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Checks if an operation method and a math transform have a compatible number of source and target dimensions.
 * In the particular case of a {@linkplain PassThroughTransform pass through transform} with more dimensions
 * than what we would expect from the given method, the check will rather be performed against the
 * {@linkplain PassThroughTransform#getSubTransform() sub transform}.
 * The intent is to allow creation of a three-dimensional {@code ProjectedCRS} with a two-dimensional
 * {@code OperationMethod}, where the third-dimension just pass through.
 *
 * <p>This method tries to locates what seems to be the "core" of the given math transform. The definition
 * of "core" is imprecise and may be adjusted in future SIS versions. The current algorithm is as below:</p>
 *
 * <ul>
 *   <li>If the given transform can be decomposed in {@linkplain MathTransforms#getSteps(MathTransform) steps},
 *       then the steps for {@linkplain org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes axis
 *       swapping and scaling} are ignored.</li>
 *   <li>If the given transform or its non-ignorable step is a {@link PassThroughTransform}, then its sub-transform
 *       is taken. Only one non-ignorable step may exist, otherwise we do not try to select any of them.</li>
 * </ul>
 *
 * @param  method      the operation method to compare to the math transform.
 * @param  interpDim   the number of interpolation dimension, or 0 if none.
 * @param  transform   the math transform to compare to the operation method.
 * @param  properties  properties of the caller object being constructed, used only for formatting error message.
 * @throws IllegalArgumentException if the number of dimensions are incompatible.
 */
static void checkDimensions(final OperationMethod method, final int interpDim, MathTransform transform,
        final Map<String,?> properties) throws IllegalArgumentException
{
    int actual = transform.getSourceDimensions();
    Integer expected = method.getSourceDimensions();
    if (expected != null && actual > expected + interpDim) {
        /*
         * The given MathTransform uses more dimensions than the OperationMethod.
         * Try to locate one and only one sub-transform, ignoring axis swapping and scaling.
         */
        MathTransform subTransform = null;
        for (final MathTransform step : MathTransforms.getSteps(transform)) {
            if (!isIgnorable(step)) {
                if (subTransform == null && step instanceof PassThroughTransform) {
                    subTransform = ((PassThroughTransform) step).getSubTransform();
                } else {
                    subTransform = null;
                    break;
                }
            }
        }
        if (subTransform != null) {
            transform = subTransform;
            actual = transform.getSourceDimensions();
        }
    }
    /*
     * Now verify if the MathTransform dimensions are equal to the OperationMethod ones,
     * ignoring null java.lang.Integer instances.  We do not specify whether the method
     * dimensions should include the interpolation dimensions or not, so we accept both.
     */
    int isTarget = 0;               // 0 == false: the wrong dimension is the source one.
    if (expected == null || (actual == expected) || (actual == expected + interpDim)) {
        actual = transform.getTargetDimensions();
        expected = method.getTargetDimensions();
        if (expected == null || (actual == expected) || (actual == expected + interpDim)) {
            return;
        }
        isTarget = 1;               // 1 == true: the wrong dimension is the target one.
    }
    /*
     * At least one dimension does not match.  In principle this is an error, but we make an exception for the
     * "Affine parametric transformation" (EPSG:9624). The reason is that while OGC define that transformation
     * as two-dimensional, it can easily be extended to any number of dimensions. Note that Apache SIS already
     * has special handling for this operation (a TensorParameters dedicated class, etc.)
     */
    if (!IdentifiedObjects.isHeuristicMatchForName(method, Constants.AFFINE)) {
        throw new IllegalArgumentException(Resources.forProperties(properties).getString(
                Resources.Keys.MismatchedTransformDimension_3, isTarget, expected, actual));
    }
}
 
Example 13
Source File: DefaultMathTransformFactory.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * 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 coordinates 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>
 *
 * <h4>Controlling the normalization process</h4>
 * 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 numTrailingCoordinates = step3.getSourceDimensions() - step2.getTargetDimensions();
    if (numTrailingCoordinates > 0) {
        step2 = createPassThroughTransform(0, step2, numTrailingCoordinates);
    }
    /*
     * 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);
        drop.setElement(targetDim, sourceDim, 1); // Element in the lower-right corner.
        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;
}
 
Example 14
Source File: TransformSeparator.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Creates a transform for the same mathematic than the given {@code step}
 * but producing only the given dimensions as outputs.
 * This method is invoked by {@link #separate()} when user-specified target dimensions need to be taken in account.
 * The given {@code step} and {@code dimensions} are typically the values of
 * {@link #transform} and {@link #targetDimensions} fields respectively, but not necessarily.
 *
 * <p>Subclasses can override this method if they need to handle some {@code MathTransform} implementations
 * in a special way. However all implementations of this method shall obey to the following contract:</p>
 * <ul>
 *   <li>{@link #sourceDimensions} and {@link #targetDimensions} should not be assumed accurate.</li>
 *   <li>{@link #sourceDimensions} should not be modified by this method.</li>
 *   <li>{@link #targetDimensions} should not be modified by this method.</li>
 * </ul>
 *
 * The number and nature of inputs stay unchanged. For example if the supplied {@code transform}
 * has (<var>longitude</var>, <var>latitude</var>, <var>height</var>) outputs, then a filtered
 * transform may keep only the (<var>longitude</var>, <var>latitude</var>) part for the same inputs.
 * In most cases, the filtered transform is non-invertible since it looses information.
 *
 * @param  step        the transform for which to retain only a subset of the target dimensions.
 * @param  dimensions  indices of the target dimensions of {@code step} to retain.
 * @return a transform producing only the given target dimensions.
 * @throws FactoryException if the given transform is not separable.
 */
protected MathTransform filterTargetDimensions(MathTransform step, final int[] dimensions) throws FactoryException {
    final int numSrc = step.getSourceDimensions();
          int numTgt = step.getTargetDimensions();
    final int lower  = dimensions[0];
    final int upper  = dimensions[dimensions.length - 1];
    if (lower == 0 && upper == numTgt && dimensions.length == numTgt) {
        return step;
    }
    /*
     * If the transform is an instance of passthrough transform but no dimension from its sub-transform
     * is requested, then ignore the sub-transform (i.e. treat the whole transform as identity, except
     * for the number of target dimension which may be different from the number of input dimension).
     */
    int removeAt = 0;
    int numRemoved = 0;
    if (step instanceof PassThroughTransform) {
        final PassThroughTransform passThrough = (PassThroughTransform) step;
        final int subLower  = passThrough.firstAffectedCoordinate;
        final int numSubTgt = passThrough.subTransform.getTargetDimensions();
        if (!containsAny(dimensions, subLower, subLower + numSubTgt)) {
            step = IdentityTransform.create(numTgt = numSrc);
            removeAt = subLower;
            numRemoved = numSubTgt - passThrough.subTransform.getSourceDimensions();
        }
    }
    /*                                                  ┌  ┐     ┌          ┐ ┌ ┐
     * Create the matrix to be used as a filter         │x'│     │1  0  0  0│ │x│
     * and concatenate it to the transform. The         │z'│  =  │0  0  1  0│ │y│
     * matrix will contain 1 only in the target         │1 │     │0  0  0  1│ │z│
     * dimensions to keep, as in this example:          └  ┘     └          ┘ │1│
     *                                                                        └ ┘
     */
    final Matrix matrix = Matrices.createZero(dimensions.length + 1, numTgt + 1);
    for (int j=0; j<dimensions.length; j++) {
        int i = dimensions[j];
        if (i >= removeAt) {
            i -= numRemoved;
        }
        matrix.setElement(j, i, 1);
    }
    matrix.setElement(dimensions.length, numTgt, 1);
    return factory.concatenate(step, factory.linear(matrix));
}
 
Example 15
Source File: MathTransforms.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Creates a transform which passes through a subset of coordinates to another transform.
 * This method returns a transform having the following dimensions:
 *
 * {@preformat java
 *     Source: firstAffectedCoordinate + subTransform.getSourceDimensions() + numTrailingCoordinates
 *     Target: firstAffectedCoordinate + subTransform.getTargetDimensions() + numTrailingCoordinates
 * }
 *
 * Affected coordinates will range from {@code firstAffectedCoordinate} inclusive to
 * {@code dimTarget - numTrailingCoordinates} exclusive.
 *
 * @param  firstAffectedCoordinate  index of the first affected coordinate.
 * @param  subTransform             the sub-transform to apply on modified coordinates.
 * @param  numTrailingCoordinates   number of trailing coordinates to pass through.
 * @return a pass-through transform, potentially as a {@link PassThroughTransform} instance but not necessarily.
 *
 * @since 1.0
 */
public static MathTransform passThrough(final int firstAffectedCoordinate,
                                        final MathTransform subTransform,
                                        final int numTrailingCoordinates)
{
    ArgumentChecks.ensureNonNull ("subTransform",            subTransform);
    ArgumentChecks.ensurePositive("firstAffectedCoordinate", firstAffectedCoordinate);
    ArgumentChecks.ensurePositive("numTrailingCoordinates",  numTrailingCoordinates);
    if (firstAffectedCoordinate == 0 && numTrailingCoordinates == 0) {
        return subTransform;
    }
    if (subTransform.isIdentity()) {
        final int dimension = subTransform.getSourceDimensions();
        if (dimension == subTransform.getTargetDimensions()) {
            return IdentityTransform.create(firstAffectedCoordinate + dimension + numTrailingCoordinates);
        }
    }
    return PassThroughTransform.create(firstAffectedCoordinate, subTransform, numTrailingCoordinates);
}
 
Example 16
Source File: ConcatenatedTransform.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Concatenates the two given transforms.
 * If the concatenation result works with two-dimensional input and output points,
 * then the returned transform will implement {@link MathTransform2D}.
 * Likewise if the concatenation result works with one-dimensional input and output points,
 * then the returned transform will implement {@link MathTransform1D}.
 *
 * <div class="note"><b>Implementation note:</b>
 * {@code ConcatenatedTransform} implementations are available in two versions: direct and non-direct.
 * The "non-direct" versions use an intermediate buffer when performing transformations; they are slower
 * and consume more memory. They are used only as a fallback when a "direct" version can not be created.</div>
 *
 * @param  tr1      the first math transform.
 * @param  tr2      the second math transform.
 * @param  factory  the factory which is (indirectly) invoking this method, or {@code null} if none.
 * @return the concatenated transform.
 *
 * @see MathTransforms#concatenate(MathTransform, MathTransform)
 */
public static MathTransform create(MathTransform tr1, MathTransform tr2, final MathTransformFactory factory)
        throws FactoryException, MismatchedDimensionException
{
    final int dim1 = tr1.getTargetDimensions();
    final int dim2 = tr2.getSourceDimensions();
    if (dim1 != dim2) {
        throw new MismatchedDimensionException(Resources.format(Resources.Keys.CanNotConcatenateTransforms_2, getName(tr1),
                getName(tr2)) + ' ' + Errors.format(Errors.Keys.MismatchedDimension_2, dim1, dim2));
    }
    MathTransform mt = createOptimized(tr1, tr2, factory);
    if (mt != null) {
        return mt;
    }
    /*
     * Can not avoid the creation of a ConcatenatedTransform object.
     * Check for the type to create (1D, 2D, general case...)
     */
    final int dimSource = tr1.getSourceDimensions();
    final int dimTarget = tr2.getTargetDimensions();
    if (dimSource == 1 && dimTarget == 1) {
        /*
         * Result needs to be a MathTransform1D.
         */
        if (tr1 instanceof MathTransform1D && tr2 instanceof MathTransform1D) {
            return new ConcatenatedTransformDirect1D((MathTransform1D) tr1,
                                                     (MathTransform1D) tr2);
        } else {
            return new ConcatenatedTransform1D(tr1, tr2);
        }
    } else if (dimSource == 2 && dimTarget == 2) {
        /*
         * Result needs to be a MathTransform2D.
         */
        if (tr1 instanceof MathTransform2D && tr2 instanceof MathTransform2D) {
            return new ConcatenatedTransformDirect2D((MathTransform2D) tr1,
                                                     (MathTransform2D) tr2);
        } else {
            return new ConcatenatedTransform2D(tr1, tr2);
        }
    } else if (dimSource == tr1.getTargetDimensions()   // dim1 = tr1.getTargetDimensions() and
            && dimTarget == tr2.getSourceDimensions())  // dim2 = tr2.getSourceDimensions() may not be true anymore.
    {
        return new ConcatenatedTransformDirect(tr1, tr2);
    } else {
        return new ConcatenatedTransform(tr1, tr2);
    }
}
 
Example 17
Source File: TransformCommand.java    From sis with Apache License 2.0 4 votes vote down vote up
/**
 * Transforms the given coordinates.
 */
private void transform(final List<double[]> points) throws TransformException {
    final int dimension    = operation.getSourceCRS().getCoordinateSystem().getDimension();
    final MathTransform mt = operation.getMathTransform();
    final double[] result  = new double[mt.getTargetDimensions()];
    final double[] domainCoordinate;
    final DirectPositionView positionInDomain;
    final ImmutableEnvelope domainOfValidity;
    final GeographicBoundingBox bbox;
    if (toDomainOfValidity != null && (bbox = CRS.getGeographicBoundingBox(operation)) != null) {
        domainOfValidity = new ImmutableEnvelope(bbox);
        domainCoordinate = new double[toDomainOfValidity.getTargetDimensions()];
        positionInDomain = new DirectPositionView.Double(domainCoordinate);
    } else {
        domainOfValidity = null;
        domainCoordinate = null;
        positionInDomain = null;
    }
    for (final double[] coordinates : points) {
        if (coordinates.length != dimension) {
            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimensionForCRS_3,
                        operation.getSourceCRS().getName().getCode(), dimension, coordinates.length));
        }
        /*
         * At this point we got the coordinates and they have the expected number of dimensions.
         * Now perform the coordinate operation and print each coordinate values. We will switch
         * to scientific notation if the coordinate is much larger than expected.
         */
        mt.transform(coordinates, 0, result, 0, 1);
        for (int i=0; i<result.length; i++) {
            if (i != 0) {
                out.print(',');
            }
            final double value = result[i];
            final String s;
            if (Math.abs(value) >= thresholdForScientificNotation[i]) {
                s = Double.toString(value);
            } else {
                coordinateFormat.setMinimumFractionDigits(numFractionDigits[i]);
                coordinateFormat.setMaximumFractionDigits(numFractionDigits[i]);
                s = coordinateFormat.format(value);
            }
            out.print(CharSequences.spaces(coordinateWidth - s.length()));
            out.print(s);
        }
        /*
         * Append a warning after the transformed coordinate values if the source coordinate was outside
         * the domain of validity. A failure to perform a coordinate transformation is also considered as
         * being out of the domain of valididty.
         */
        if (domainOfValidity != null) {
            boolean inside;
            try {
                toDomainOfValidity.transform(coordinates, 0, domainCoordinate, 0, 1);
                inside = domainOfValidity.contains(positionInDomain);
            } catch (TransformException e) {
                inside = false;
                warning(e);
            }
            if (!inside) {
                out.print(",    ");
                printQuotedText(Errors.getResources(locale).getString(Errors.Keys.OutsideDomainOfValidity), 0, X364.FOREGROUND_RED);
            }
        }
        out.println();
    }
}
 
Example 18
Source File: MathTransforms.java    From sis with Apache License 2.0 3 votes vote down vote up
/**
 * Creates a transform defined as one transform applied globally except in sub-areas where more accurate
 * transforms are available. Such constructs appear in some datum shift files. The result of transforming
 * a point by the returned {@code MathTransform} is as if iterating over all given {@link Envelope}s in
 * no particular order, find the smallest one containing the point to transform (envelope border considered
 * inclusive), then use the associated {@link MathTransform} for transforming the point.
 * If the point is not found in any envelope, then the global transform is applied.
 *
 * <p>The following constraints apply:</p>
 * <ul>
 *   <li>The global transform must be a reasonable approximation of the specialized transforms
 *       (this is required for calculating the inverse transform).</li>
 *   <li>All transforms in the {@code specializations} map must have the same number of source and target
 *       dimensions than the {@code global} transform.</li>
 *   <li>All envelopes in the {@code specializations} map must have the same number of dimensions
 *       than the global transform <em>source</em> dimensions.</li>
 *   <li>In current implementation, each envelope must either be fully included in another envelope,
 *       or not overlap any other envelope.</li>
 * </ul>
 *
 * @param  global  the transform to use globally where there is no suitable specialization.
 * @param  specializations  more accurate transforms available in some sub-areas.
 * @return a transform applying the given global transform except in sub-areas where specializations are available.
 * @throws IllegalArgumentException if a constraint is not met.
 *
 * @since 1.0
 */
public static MathTransform specialize(final MathTransform global, final Map<Envelope,MathTransform> specializations) {
    ArgumentChecks.ensureNonNull("generic", global);
    ArgumentChecks.ensureNonNull("specializations", specializations);
    final SpecializableTransform tr;
    if (specializations.isEmpty()) {
        return global;
    } else if (global.getSourceDimensions() == 2 && global.getTargetDimensions() == 2) {
        tr = new SpecializableTransform2D(global, specializations);
    } else {
        tr = new SpecializableTransform(global, specializations);
    }
    final MathTransform substitute = tr.getSubstitute();
    return (substitute != null) ? substitute : tr;
}