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

The following examples show how to use org.opengis.referencing.operation.MathTransform#getSourceDimensions() . 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: TransformSeparatorTest.java    From sis with Apache License 2.0 6 votes vote down vote up
/**
 * Compares coordinate computed by a reference with coordinates computed by the transform to test.
 * We use this method when we can not easily analyze the {@link MathTransform} created by the test
 * case, for example because it may have been rearranged in arbitrary ways for optimization purpose
 * (e.g. {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, MathTransformFactory)}).
 *
 * @param  tr1     first half of the transform to use as a reference.
 * @param  tr2     second half of the transform to use as a reference.
 * @param  test    the transform to test.
 * @param  random  random number generator for coordinate values.
 */
private static void compare(final MathTransform tr1, final MathTransform tr2, final MathTransform test, final Random random)
        throws TransformException
{
    DirectPosition source   = new GeneralDirectPosition(tr1.getSourceDimensions());
    DirectPosition step     = null;
    DirectPosition expected = null;
    DirectPosition actual   = null;
    for (int t=0; t<50; t++) {
        for (int i=source.getDimension(); --i>=0;) {
            source.setOrdinate(i, random.nextDouble());
        }
        step     = tr1 .transform(source,   step);
        expected = tr2 .transform(step, expected);
        actual   = test.transform(source, actual);
        assertEquals(expected, actual);
    }
}
 
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: EllipsoidToCentricTransform.java    From sis with Apache License 2.0 6 votes vote down vote up
/**
 * If this transform expects three-dimensional inputs, and if the transform just before this one
 * unconditionally sets the height to zero, 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 3D to 2D conversion</cite> (EPSG:9659)</li>
 *   <li><cite>Geographic/geocentric conversions</cite> (EPSG:9602)</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} before this transform
 *       instead than a transform based on a matrix of size 4×3.</li>
 * </ul>
 *
 * @return the combined math transform, or {@code null} if no optimized combined transform is available.
 * @throws FactoryException if an error occurred while combining the transforms.
 */
@Override
protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other,
        final MathTransformFactory factory) throws FactoryException
{
    if (applyOtherFirst && withHeight && other instanceof LinearTransform && other.getSourceDimensions() == 2) {
        /*
         * Found a 4×3 matrix before this transform. We can reduce to a 3×3 matrix only if the row that we are
         * about to drop unconditionally set the height to zero (i.e. all coefficients in that row are zero).
         */
        Matrix matrix = ((LinearTransform) other).getMatrix();
        if (matrix.getElement(2,0) == 0 &&
            matrix.getElement(2,1) == 0 &&
            matrix.getElement(2,2) == 0)
        {
            matrix = MatrixSIS.castOrCopy(matrix).removeRows(2, 3);
            final MathTransform tr2D = create2D();
            if (factory != null) {
                return factory.createConcatenatedTransform(factory.createAffineTransform(matrix), tr2D);
            } else {
                return ConcatenatedTransform.create(MathTransforms.linear(matrix), tr2D, factory);
            }
        }
    }
    return super.tryConcatenate(applyOtherFirst, other, factory);
}
 
Example 4
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 5
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 6
Source File: PixelTranslation.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Converts a math transform from a "pixel in cell" convention to another "pixel in cell" convention.
 * This method concatenates −½, 0 or +½ translations on <em>all</em> dimensions before the given transform.
 * If the two given conventions are the same, then this method returns the given transform unchanged.
 *
 * <div class="note"><b>Example:</b>
 * if a given {@code gridToCRS} transform was mapping the <em>cell corner</em> to "real world" coordinates, then a call to
 * <code>translate(gridToCRS, {@link PixelInCell#CELL_CORNER CELL_CORNER}, {@link PixelInCell#CELL_CENTER CELL_CENTER})</code>
 * will return a new transform performing the following steps: first convert grid coordinates from <var>cell center</var>
 * convention ({@code desired}) to <var>cell corner</var> convention ({@code current}), then concatenate the given
 * {@code gridToCRS} transform which was designed for the <em>cell corner</em> convention.
 * The above-cited <var>cell center</var> → <var>cell corner</var> conversion is done by translating the grid coordinates
 * by +½, because the grid coordinates (0,0) relative to cell center is (½,½) relative to cell corner.</div>
 *
 * If the given {@code gridToCRS} is null, then this method ignores all other arguments and returns {@code null}.
 * Otherwise {@code current} and {@code desired} arguments must be non-null.
 *
 * @param  gridToCRS  a math transform from <cite>pixel</cite> coordinates to any CRS, or {@code null}.
 * @param  current    the pixel orientation of the given {@code gridToCRS} transform.
 * @param  desired    the pixel orientation of the desired transform.
 * @return the translation from {@code current} to {@code desired}, or {@code null} if {@code gridToCRS} was null.
 * @throws IllegalArgumentException if {@code current} or {@code desired} is not a known code list value.
 */
public static MathTransform translate(final MathTransform gridToCRS, final PixelInCell current, final PixelInCell desired) {
    if (gridToCRS == null || desired.equals(current)) {
        return gridToCRS;
    }
    final int dimension = gridToCRS.getSourceDimensions();
    final double offset = getPixelTranslation(desired) - getPixelTranslation(current);
    final int ci;               // Cache index.
    if (offset == -0.5) {
        ci = 2*dimension - 2;
    } else if (offset == 0.5) {
        ci = 2*dimension - 1;
    } else {
        ci = -1;
    }
    MathTransform mt;
    if (ci < 0 || ci >= translations.length) {
        mt = MathTransforms.uniformTranslation(dimension, offset);
    } else synchronized (translations) {
        mt = translations[ci];
        if (mt == null) {
            mt = MathTransforms.uniformTranslation(dimension, offset);
            translations[ci] = mt;
        }
    }
    return MathTransforms.concatenate(mt, gridToCRS);
}
 
Example 7
Source File: GridDerivation.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Drops the source dimensions that are not needed for producing the target dimensions.
 * The retained source dimensions are stored in {@link #modifiedDimensions}.
 * This method is invoked in an effort to make the transform invertible.
 *
 * @param  cornerToCRS  transform from grid coordinates to AOI coordinates.
 * @param  dimension    value of {@code cornerToCRS.getTargetDimensions()}.
 */
private MathTransform dropUnusedDimensions(MathTransform cornerToCRS, final int dimension)
        throws FactoryException, TransformException
{
    if (dimension < cornerToCRS.getSourceDimensions()) {
        final TransformSeparator sep = new TransformSeparator(cornerToCRS);
        cornerToCRS = sep.separate();
        modifiedDimensions = sep.getSourceDimensions();
        if (modifiedDimensions.length != dimension) {
            throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
        }
    }
    return cornerToCRS;
}
 
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: PixelTranslation.java    From sis with Apache License 2.0 5 votes vote down vote up
/**
 * Converts a math transform from a "pixel orientation" convention to another "pixel orientation" convention.
 * This method concatenates −½, 0 or +½ translations on <em>two</em> dimensions before the given transform.
 * The given transform can have any number of input and output dimensions, but only two of them will be converted.
 *
 * <div class="note"><b>Example:</b>
 * if a given {@code gridToCRS} transform was mapping the upper-left corner to "real world" coordinates, then a call to
 * <code>translate(gridToCRS, {@link PixelOrientation#UPPER_LEFT UPPER_LEFT}, {@link PixelOrientation#CENTER CENTER}, 0, 1)</code>
 * will return a new transform translating grid coordinates by +0.5 before to apply the given {@code gridToCRS} transform.
 * See example in above {@link #translate(MathTransform, PixelInCell, PixelInCell) translate} method for more details.</div>
 *
 * If the given {@code gridToCRS} is null, then this method ignores all other arguments and returns {@code null}.
 * Otherwise {@code current} and {@code desired} arguments must be non-null.
 *
 * @param  gridToCRS   a math transform from <cite>pixel</cite> coordinates to any CRS, or {@code null}.
 * @param  current     the pixel orientation of the given {@code gridToCRS} transform.
 * @param  desired     the pixel orientation of the desired transform.
 * @param  xDimension  the dimension of <var>x</var> coordinates (pixel columns). Often 0.
 * @param  yDimension  the dimension of <var>y</var> coordinates (pixel rows). Often 1.
 * @return the translation from {@code current} to {@code desired}, or {@code null} if {@code gridToCRS} was null.
 * @throws IllegalArgumentException if {@code current} or {@code desired} is not a known code list value.
 */
public static MathTransform translate(final MathTransform gridToCRS,
        final PixelOrientation current, final PixelOrientation desired,
        final int xDimension, final int yDimension)
{
    if (gridToCRS == null || desired.equals(current)) {
        return gridToCRS;
    }
    final int dimension = gridToCRS.getSourceDimensions();
    if (xDimension < 0 || xDimension >= dimension) {
        throw illegalDimension("xDimension", xDimension);
    }
    if (yDimension < 0 || yDimension >= dimension) {
        throw illegalDimension("yDimension", yDimension);
    }
    if (xDimension == yDimension) {
        throw illegalDimension("xDimension", "yDimension");
    }
    final PixelTranslation source = getPixelTranslation(current);
    final PixelTranslation target = getPixelTranslation(desired);
    final double dx = target.dx - source.dx;
    final double dy = target.dy - source.dy;
    MathTransform mt;
    if (dimension == 2 && (xDimension | yDimension) == 1 && dx == dy && Math.abs(dx) == 0.5) {
        final int ci = (dx >= 0) ? 3 : 2;
        synchronized (translations) {
            mt = translations[ci];
            if (mt == null) {
                mt = MathTransforms.uniformTranslation(dimension, dx);
                translations[ci] = mt;
            }
        }
    } else {
        final Matrix matrix = Matrices.createIdentity(dimension + 1);
        matrix.setElement(xDimension, dimension, dx);
        matrix.setElement(yDimension, dimension, dy);
        mt = MathTransforms.linear(matrix);
    }
    return MathTransforms.concatenate(mt, gridToCRS);
}
 
Example 10
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 11
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 12
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 13
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 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: 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 17
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 18
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 19
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 20
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;
}