Search in sources :

Example 1 with MathTransformsOrFactory

use of org.apache.sis.internal.referencing.MathTransformsOrFactory in project sis by apache.

the class SliceGeometry method reduce.

/**
 * Creates a new grid geometry over the specified dimensions of the geometry specified at construction time.
 * The number of grid dimensions will be the length of the {@link #gridDimensions} array, and the number of
 * CRS dimensions will be reduced by the same amount.
 *
 * <p>If a non-null {@link #sliceExtent} has been specified, that extent shall be a sub-extent of the extent
 * of the original grid geometry. In particular it must have the same number of dimensions in same order and
 * the original "grid to CRS" transform shall be valid with that {@link #sliceExtent}. That sub-extent will
 * be used in replacement of the original extent for computing the geospatial area and the resolution.</p>
 *
 * <p>If a non-null {@code relativeExtent} is specified, a translation will be inserted before "grid to CRS"
 * conversion in order that lowest coordinate values of {@link #sliceExtent} (or original extent if there is
 * no slice extent) will map to (0,0,…,0) coordinate values in relative extent. This is used for taking in
 * account the translation between {@link #sliceExtent} coordinates and coordinates of the image returned by
 * {@link GridCoverage#render(GridExtent)}, in which case the relative extent is the location and size of the
 * {@link RenderedImage}. The number of dimensions of relative extent must be equal to {@code gridDimensions}
 * array length (i.e. the dimensionality reduction must be already done).</p>
 *
 * @param  relativeExtent  if non-null, an extent <em>relative</em> to {@link #sliceExtent} to assign to
 *                         the grid geometry to return. Dimensionality reduction shall be already applied.
 * @param  dimCRS          desired number of CRS dimensions, or -1 for automatic.
 * @throws FactoryException if an error occurred while separating the "grid to CRS" transform.
 *
 * @see GridGeometry#reduce(int...)
 */
final GridGeometry reduce(final GridExtent relativeExtent, final int dimCRS) throws FactoryException {
    GridExtent extent = geometry.extent;
    MathTransform gridToCRS = geometry.gridToCRS;
    MathTransform cornerToCRS = geometry.cornerToCRS;
    double[] resolution = geometry.resolution;
    /*
         * If a `gridToCRS` transform is available, retain the source dimensions specified by `gridDimensions`.
         * We work on source dimensions because they are the grid dimensions. The CRS dimensions to retain are
         * often the same than the grid dimensions, but not necessarily. In particular the CRS may have more
         * elements if `TransformSeparator` detected that dropping a grid dimension does not force us to drop
         * the corresponding CRS dimension, for example because it has a constant value.
         */
    int[] crsDimensions = gridDimensions;
    if (gridToCRS != null) {
        TransformSeparator sep = new TransformSeparator(gridToCRS, factory);
        sep.addSourceDimensions(gridDimensions);
        /*
             * Try to reduce the CRS by the same amount of dimensions than the grid.
             */
        crsDimensions = findTargetDimensions(gridToCRS, extent, resolution, gridDimensions, dimCRS);
        if (crsDimensions != null) {
            sep.addTargetDimensions(crsDimensions);
        }
        gridToCRS = sep.separate();
        crsDimensions = sep.getTargetDimensions();
        /*
             * We redo a separation for `cornerToCRS` instead of applying a translation of the `gridToCRS`
             * computed above because we don't know which of `gridToCRS` and `cornerToCRS` has less NaN values.
             * We require however the exact same sequence of target dimensions.
             */
        sep = new TransformSeparator(cornerToCRS, factory);
        sep.addSourceDimensions(gridDimensions);
        sep.addTargetDimensions(crsDimensions);
        cornerToCRS = sep.separate();
    }
    /*
         * Get an extent over only the specified grid dimensions. This code may opportunistically substitute
         * the full grid geometry extent by a sub-region. The use of a sub-region happens if this `reduce(…)`
         * method is invoked (indirectly) from a method like `GridGeometry.render(…)`.
         */
    final boolean useSubExtent = (sliceExtent != null) && !sliceExtent.equals(extent, ComparisonMode.IGNORE_METADATA);
    if (useSubExtent) {
        extent = sliceExtent;
    }
    if (extent != null) {
        extent = extent.reduceDimension(gridDimensions);
    }
    GeneralEnvelope subArea = null;
    if (useSubExtent && cornerToCRS != null)
        try {
            subArea = extent.toCRS(cornerToCRS, gridToCRS, null);
        } catch (TransformException e) {
            // GridGeometry.reduce(…) is the public method invoking indirectly this method.
            GridGeometry.recoverableException("reduce", e);
        }
    /*
         * Create an envelope with only the requested dimensions, clipped to the sub-area if one has been
         * computed from `sliceExtent`.  The result after this code may still be a null envelope if there
         * is not enough information.
         */
    final int n = crsDimensions.length;
    ImmutableEnvelope envelope = geometry.envelope;
    if (envelope != null) {
        if (subArea != null || envelope.getDimension() != n) {
            final CoordinateReferenceSystem crs = CRS.reduce(envelope.getCoordinateReferenceSystem(), crsDimensions);
            final double[] min = new double[n];
            final double[] max = new double[n];
            for (int i = 0; i < n; i++) {
                final int j = crsDimensions[i];
                min[i] = envelope.getLower(j);
                max[i] = envelope.getUpper(j);
            }
            if (subArea != null) {
                for (int i = 0; i < n; i++) {
                    double v;
                    if ((v = subArea.getLower(i)) > min[i])
                        min[i] = v;
                    if ((v = subArea.getUpper(i)) < max[i])
                        max[i] = v;
                }
            }
            envelope = new ImmutableEnvelope(min, max, crs);
        }
    } else if (subArea != null) {
        envelope = new ImmutableEnvelope(subArea);
    }
    /*
         * If a `sliceExtent` has been specified, the resolution may differ because the "point of interest"
         * which is by default in extent center, may now be at a different location. In such case recompute
         * the resolution. Otherwise (same extent than original grid geometry), just copy resolution values
         * from the original grid geometry.
         */
    if (useSubExtent || resolution == null) {
        resolution = GridGeometry.resolution(gridToCRS, extent);
    } else if (resolution.length != n) {
        resolution = new double[n];
        for (int i = 0; i < n; i++) {
            resolution[i] = geometry.resolution[crsDimensions[i]];
        }
    }
    /*
         * Coordinate (0,0) in `RenderedImage` corresponds to the lowest coordinates in `sliceExtent` request.
         * For taking that offset in account, we need to apply a translation. It happens when this method is
         * invoked (indirectly) from `GridCoverage.render(…)` but not when invoked from `GridGeometry.reduce(…)`
         */
    if (relativeExtent != null) {
        if (extent != null && !extent.startsAtZero()) {
            final double[] offset = new double[gridDimensions.length];
            for (int i = 0; i < gridDimensions.length; i++) {
                offset[i] = extent.getLow(gridDimensions[i]);
            }
            final LinearTransform translation = MathTransforms.translation(offset);
            final MathTransformsOrFactory f = MathTransformsOrFactory.wrap(factory);
            if (gridToCRS != null) {
                gridToCRS = f.concatenate(translation, gridToCRS);
                cornerToCRS = f.concatenate(translation, cornerToCRS);
            }
        }
        extent = relativeExtent;
    }
    /*
         * Slicing should not alter whether conversion in a dimension is a linear operation or not.
         * So we just copy the flags from the original grid geometry, selecting only the flags for
         * the specified dimensions.
         */
    long nonLinears = 0;
    for (int i = 0; i < n; i++) {
        nonLinears |= ((geometry.nonLinears >>> crsDimensions[i]) & 1L) << i;
    }
    return new GridGeometry(extent, gridToCRS, cornerToCRS, envelope, resolution, nonLinears);
}
Also used : MathTransform(org.opengis.referencing.operation.MathTransform) TransformException(org.opengis.referencing.operation.TransformException) LinearTransform(org.apache.sis.referencing.operation.transform.LinearTransform) MathTransformsOrFactory(org.apache.sis.internal.referencing.MathTransformsOrFactory) ImmutableEnvelope(org.apache.sis.geometry.ImmutableEnvelope) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) GeneralEnvelope(org.apache.sis.geometry.GeneralEnvelope) TransformSeparator(org.apache.sis.referencing.operation.transform.TransformSeparator)

Example 2 with MathTransformsOrFactory

use of org.apache.sis.internal.referencing.MathTransformsOrFactory in project sis by apache.

the class WraparoundTransform method tryConcatenate.

/**
 * Concatenates in an optimized way this math transform with the given one, if possible.
 * If this method detects a chain of operations like below:
 *
 * <blockquote>[wraparound]  ⇄  [affine]  ⇄  [wraparound or something else]</blockquote>
 *
 * Then this method tries to move some dimensions of the [affine] step before or after the
 * [wraparound] step in order to simplify (or ideally remove) the [affine] step in the middle.
 * This move increases the chances that [affine] step is combined with other affine operations.
 * Only dimensions that do not depend on {@link #wraparoundDimension} can be moved.
 */
@Override
protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other, final MathTransformFactory factory) throws FactoryException {
    /*
         * If the other transform is also a `WraparoundTransform` for the same dimension,
         * then there is no need to concatenate those two consecutive redudant transforms.
         * Keep the first transform because it will be the last one (having precedence)
         * in inverse transform.
         */
    if (other instanceof WraparoundTransform && equalsIgnoreInverse((WraparoundTransform) other)) {
        return applyOtherFirst ? other : this;
    }
    final List<MathTransform> steps = MathTransforms.getSteps(other);
    final int count = steps.size();
    if (count >= 2) {
        final MathTransform middleTr = steps.get(applyOtherFirst ? count - 1 : 0);
        Matrix middle = MathTransforms.getMatrix(middleTr);
        if (middle != null)
            try {
                /*
                 * We have a matrix between this `WraparoundTransform` and something else,
                 * potentially another `WraparoundTransform`. Try to move as many rows as
                 * possible outside that `middle` matrix. Ideally we will be able to move
                 * the matrix completely, which increase the chances to multiply (outside
                 * this method) with another matrix.
                 */
                final MathTransformsOrFactory mf = MathTransformsOrFactory.wrap(factory);
                boolean modified = false;
                MathTransform step1 = this;
                MathTransform move = movable(middleTr, middle, mf);
                if (move != null) {
                    /*
                     * Update the middle matrix with everything that we could not put in `move`.
                     * If `applyOtherFirst` is false:
                     *
                     *     [move]  →  [redim]  →  [remaining]  →  [other]
                     *
                     * If `applyOtherFirst` is true:
                     *
                     *     [other]  →  [remaining]  →  [redim]  →  [move]
                     *
                     * Usually the matrix is square before the multiplication. But if it was not the case,
                     * the new matrix will have the same number of columns (source coordinates) but a new
                     * number of rows (target coordinates). The result should be a square matrix.
                     */
                    final Matrix remaining = remaining(applyOtherFirst, move, middle);
                    final WraparoundTransform redim = redim(applyOtherFirst, remaining);
                    if (redim != null) {
                        step1 = mf.concatenate(applyOtherFirst, move, redim);
                        middle = remaining;
                        modified = true;
                    }
                }
                /*
                 * Now look at the non-linear transform. If it is another instance of `WraparoundTransform`,
                 * then we may move the calculation of some coordinates before it. This is the same algorithm
                 * than above but with `applyOtherFirst` reversed.
                 */
                MathTransform step2 = steps.get(applyOtherFirst ? count - 2 : 1);
                if (step2 instanceof WraparoundTransform) {
                    WraparoundTransform redim = (WraparoundTransform) step2;
                    move = redim.movable(null, middle, mf);
                    if (move != null) {
                        final Matrix remaining = remaining(!applyOtherFirst, move, middle);
                        redim = redim.redim(!applyOtherFirst, remaining);
                        if (redim != null) {
                            step2 = mf.concatenate(applyOtherFirst, redim, move);
                            middle = remaining;
                            modified = true;
                        }
                    }
                }
                /*
                 * Done moving the linear operations that we can move. Put everything together.
                 * The `middle` transform should become simpler, ideally the identity transform.
                 *
                 * As an heuristic rule, we assume that it was worth simplifying if the implementation class changed.
                 * For example a `ProjectiveTransform` middle transform may be replaced by `IdentityTransform` (ideal
                 * case, but replacement by `TranslationTransform` is still good). But if we got the same class, then
                 * even if the matrix is a little bit simpler it is probably not simpler enough; we will probably get
                 * no performance benefit. In such case abandon this `tryConcatenate(…)` attempt for reducing risk of
                 * confusing WKT representation. It may happen in particular if `other` is a `NormalizedProjection`
                 * with normalization/denormalization matrices. "Simplifying" a (de)normalization matrix may actually
                 * complexify the map projection WKT representation.
                 *
                 * NOTE 1: the decision to apply simplification or not has no incidence on the numerical values
                 *         of transformation results; the transform chains should be equivalent in either cases.
                 *         It is only an attempt to avoid unnecessary changes (from a performance point of view)
                 *         in order to produce less surprising WKT representations during debugging.
                 *
                 * NOTE 2: we assume that changes of implementation class can only be simplifications (not more
                 *         costly classes) because changes applied on the `middle` matrix by above code makes
                 *         that matrix closer to an identity matrix.
                 */
                if (modified) {
                    MathTransform tr = mf.linear(middle);
                    if (tr.getClass() != middleTr.getClass()) {
                        // See above comment for rational.
                        tr = mf.concatenate(applyOtherFirst, tr, step2);
                        tr = mf.concatenate(applyOtherFirst, step1, tr);
                        if (applyOtherFirst) {
                            for (int i = count - 2; --i >= 0; ) {
                                tr = mf.concatenate(steps.get(i), tr);
                            }
                        } else {
                            for (int i = 2; i < count; i++) {
                                tr = mf.concatenate(tr, steps.get(i));
                            }
                        }
                        return tr;
                    }
                }
            } catch (NoninvertibleTransformException e) {
                // Should not happen. But if it is the case, just abandon the optimization effort.
                Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), getClass(), "tryConcatenate", e);
            }
    }
    return null;
}
Also used : NoninvertibleTransformException(org.opengis.referencing.operation.NoninvertibleTransformException) Matrix(org.opengis.referencing.operation.Matrix) MathTransform(org.opengis.referencing.operation.MathTransform) MathTransformsOrFactory(org.apache.sis.internal.referencing.MathTransformsOrFactory)

Example 3 with MathTransformsOrFactory

use of org.apache.sis.internal.referencing.MathTransformsOrFactory in project sis by apache.

the class PassThroughTransform method tryConcatenate.

/**
 * Concatenates or pre-concatenates in an optimized way this transform with the given transform, if possible.
 * This method applies the following special cases:
 *
 * <ul>
 *  <li>If the other transform is also a {@code PassThroughTransform}, then the two transforms may be merged
 *      in a single {@code PassThroughTransform} instance.</li>
 *  <li>If the other transform discards some dimensions, verify if we still need a {@code PassThroughTransform}.</li>
 * </ul>
 *
 * @return the simplified transform, or {@code null} if no such optimization is available.
 * @throws FactoryException if an error occurred while combining the transforms.
 *
 * @since 1.0
 */
@Override
protected MathTransform tryConcatenate(final boolean applyOtherFirst, final MathTransform other, final MathTransformFactory factory) throws FactoryException {
    final MathTransformsOrFactory proxy = MathTransformsOrFactory.wrap(factory);
    if (other instanceof PassThroughTransform) {
        final PassThroughTransform opt = (PassThroughTransform) other;
        if (opt.firstAffectedCoordinate == firstAffectedCoordinate && opt.numTrailingCoordinates == numTrailingCoordinates) {
            final MathTransform sub = proxy.concatenate(applyOtherFirst, subTransform, opt.subTransform);
            return proxy.passThrough(firstAffectedCoordinate, sub, numTrailingCoordinates);
        }
    }
    final Matrix m = MathTransforms.getMatrix(other);
    if (m != null) {
        /*
             * If the other transform is a linear transform and all passthrough coordinates are unchanged by the matrix,
             * we can move the matrix inside the passthrough transform. It reduces the number of dimension on which the
             * linear transform operate, and gives a chance for another optimization in the concatenation between that
             * linear transform and the sub-transform.
             */
        final Matrix sub = toSubMatrix(applyOtherFirst, m);
        if (sub != null) {
            MathTransform tr = proxy.linear(sub);
            tr = proxy.concatenate(applyOtherFirst, subTransform, tr);
            return proxy.passThrough(firstAffectedCoordinate, tr, numTrailingCoordinates);
        }
        /*
             * If this PassThroughTransform is followed by a matrix discarding some dimensions, identify which dimensions
             * are discarded. If all dimensions computed by the sub-transform are discarded, then we no longer need it.
             * If some pass-through dimensions are discarded, then we can reduce the number of pass-through dimensions.
             */
        if (!applyOtherFirst) {
            // Number of source dimensions (ignore translations column).
            final int dimension = m.getNumCol() - 1;
            if (dimension <= Long.SIZE) {
                // Because retained dimensions stored as a mask on 64 bits.
                long retainedDimensions = 0;
                // Number of target dimensions + 1.
                final int numRows = m.getNumRow();
                for (int i = 0; i < dimension; i++) {
                    for (int j = 0; j < numRows; j++) {
                        if (m.getElement(j, i) != 0) {
                            // Found a source dimension which is required by target dimension.
                            retainedDimensions |= (1L << i);
                            break;
                        }
                    }
                }
                /*
                     * Verify if matrix discards the sub-transform. If it does not, then we need to keep all the sub-transform
                     * dimensions (discarding them is a "all or nothing" operation). Other dimensions (leading and trailing)
                     * can be keep or discarded on a case-by-case basis.
                     */
                final long fullTransformMask = maskLowBits(dimension);
                final long subTransformMask = maskLowBits(subTransform.getTargetDimensions()) << firstAffectedCoordinate;
                final boolean keepSubTransform = (retainedDimensions & subTransformMask) != 0;
                if (keepSubTransform) {
                    // Ensure that we keep all sub-transform dimensions.
                    retainedDimensions |= subTransformMask;
                }
                if (retainedDimensions != fullTransformMask) {
                    final int change = subTransform.getSourceDimensions() - subTransform.getTargetDimensions();
                    if (change == 0 && !keepSubTransform) {
                        // Shortcut avoiding creation of new MathTransforms.
                        return other;
                    }
                    /*
                         * We enter in this block if some dimensions can be discarded. We want to discard them before the
                         * PassThroughTransform instead of after. The matrix for that purpose will be computed later.
                         * Before that, the loop below modifies a copy of the 'other' matrix as if those dimensions were
                         * already removed.
                         */
                    MatrixSIS reduced = MatrixSIS.castOrCopy(m);
                    // Can not be 0 at this point.
                    long columnsToRemove = ~retainedDimensions & fullTransformMask;
                    do {
                        final int lower = Long.numberOfTrailingZeros(columnsToRemove);
                        final int upper = Long.numberOfTrailingZeros(~(columnsToRemove | maskLowBits(lower)));
                        reduced = reduced.removeColumns(lower, upper);
                        columnsToRemove &= ~maskLowBits(upper);
                        columnsToRemove >>>= (upper - lower);
                    } while (columnsToRemove != 0);
                    /*
                         * Expands the 'retainedDimensions' bitmask into a list of indices of dimensions to keep.   However
                         * those indices are for dimensions to keep after the PassThroughTransform.  Because we rather want
                         * indices for dimensions to keep before the PassThroughTransform, we need to adjust for difference
                         * in number of dimensions. This change is represented by the 'change' integer computed above.
                         * We apply two strategies:
                         *
                         *    1) If we keep the sub-transform, then the loop while surely sees the 'firstAffectedCoordinate'
                         *       dimension since we ensured that we keep all sub-transform dimensions. When it happens, we
                         *       add or remove bits at that point for the dimensionality changes.
                         *
                         *    2) If we do not keep the sub-transform, then code inside 'if (dim == firstAffectedCoordinate)'
                         *       should not have been executed. Instead we will adjust the indices after the loop.
                         */
                    final long leadPassThroughMask = maskLowBits(firstAffectedCoordinate);
                    final int numKeepAfter = Long.bitCount(retainedDimensions & ~(leadPassThroughMask | subTransformMask));
                    final int numKeepBefore = Long.bitCount(retainedDimensions & leadPassThroughMask);
                    final int[] indices = new int[Long.bitCount(retainedDimensions) + change];
                    for (int i = 0; i < indices.length; i++) {
                        int dim = Long.numberOfTrailingZeros(retainedDimensions);
                        if (dim == firstAffectedCoordinate) {
                            if (change < 0) {
                                // Discard dimensions to skip.
                                retainedDimensions >>>= -change;
                                // Clear previous dimension flags.
                                retainedDimensions &= ~leadPassThroughMask;
                            } else {
                                // Add dimensions.
                                retainedDimensions <<= change;
                                // Set flags for new dimensions.
                                retainedDimensions |= maskLowBits(change) << dim;
                            }
                        }
                        retainedDimensions &= ~(1L << dim);
                        indices[i] = dim;
                    }
                    if (!keepSubTransform) {
                        for (int i = indices.length; --i >= 0; ) {
                            final int dim = indices[i];
                            if (dim <= firstAffectedCoordinate)
                                break;
                            indices[i] = dim - change;
                        }
                    }
                    /*
                         * Concatenate:
                         *   1) An affine transform discarding some dimensions (no other operation).
                         *   2) The passthrough transform with less input and output dimensions.
                         *   3) The 'other' transform with less input dimensions.
                         */
                    MathTransform tr = proxy.linear(Matrices.createDimensionSelect(dimension + change, indices));
                    if (keepSubTransform) {
                        tr = proxy.concatenate(tr, proxy.passThrough(numKeepBefore, subTransform, numKeepAfter));
                    }
                    tr = proxy.concatenate(tr, proxy.linear(reduced));
                    return tr;
                }
            }
        }
    }
    /*
         * Do not invoke super.tryConcatenate(applyOtherFirst, other, factory); we do not want to test if this transform
         * is the inverse of the other transform as it is costly and unnecessary.  If it was the case, the concatenation
         * of 'this.subTransform' with 'other.subTransform' done at the beginning of this method would have produced the
         * identity transform already.
         */
    return null;
}
Also used : Matrix(org.opengis.referencing.operation.Matrix) MathTransform(org.opengis.referencing.operation.MathTransform) MathTransformsOrFactory(org.apache.sis.internal.referencing.MathTransformsOrFactory) MatrixSIS(org.apache.sis.referencing.operation.matrix.MatrixSIS)

Aggregations

MathTransformsOrFactory (org.apache.sis.internal.referencing.MathTransformsOrFactory)3 MathTransform (org.opengis.referencing.operation.MathTransform)3 Matrix (org.opengis.referencing.operation.Matrix)2 GeneralEnvelope (org.apache.sis.geometry.GeneralEnvelope)1 ImmutableEnvelope (org.apache.sis.geometry.ImmutableEnvelope)1 MatrixSIS (org.apache.sis.referencing.operation.matrix.MatrixSIS)1 LinearTransform (org.apache.sis.referencing.operation.transform.LinearTransform)1 TransformSeparator (org.apache.sis.referencing.operation.transform.TransformSeparator)1 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)1 NoninvertibleTransformException (org.opengis.referencing.operation.NoninvertibleTransformException)1 TransformException (org.opengis.referencing.operation.TransformException)1