blob: 99110de4714a70dca08ea3609d172da5d7ff42f0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.operation.transform;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.io.Serializable;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.apache.sis.geometry.MismatchedReferenceSystemException;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.internal.referencing.DirectPositionView;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Utilities;
/**
* A transform having sub-areas where more accurate transforms can be used.
* The global transform must be a reasonable approximation of the specialized transforms.
* The lower and upper values of given envelopes are inclusive.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
*
* @see MathTransforms#specialize(MathTransform, Map)
*
* @since 1.0
* @module
*/
class SpecializableTransform extends AbstractMathTransform implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -7379277748632094312L;
/**
* The global transform to use if there is no suitable specialization.
*/
private final MathTransform global;
/**
* The region where a transform is valid, together with the transform.
* Contains also a chain of {@code SubArea}s fully included in this area.
* Shall be unmodified after {@link SpecializableTransform} construction.
*/
@SuppressWarnings("CloneableClassWithoutClone") // We will not use clone().
private static final class SubArea extends GeneralEnvelope {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 4197316795428796526L;
/**
* The transform to apply in this area.
*/
final MathTransform transform;
/**
* The inverse of the transform, computed when first needed.
* Synchronization for multi-threading is done (indirectly) in {@link SpecializableTransform#inverse()}.
*
* @see #createInverse(SubArea)
*/
MathTransform inverse;
/**
* Specialization, or {@code null} if none. If non-null, that sub-area shall be fully included
* in this {@code SubArea}. The specialization may itself contain another specialization. This
* form a chain from this wider area to smallest area, where each step is a smaller area.
*
* <p>Note that this is note a substitute for an R-Tree. This is an optimization for the common
* case where coordinates are close to each other (e.g. when iterating in a geometry), in which
* case we can check immediately for inclusion in smaller areas before to check the wider area.</p>
*/
private SubArea specialization;
/**
* Creates a new area where a transform is valid.
*/
SubArea(final Envelope area, final MathTransform transform) {
super(area);
this.transform = transform;
}
/**
* Tries to add a nested sub-area (a specialization of a specialization).
*
* @return whether the given area has been added.
*/
boolean addSpecialization(final SubArea candidate) {
if (specialization == null) {
if (!contains(candidate)) {
return false;
}
} else if (!candidate.addSpecialization(specialization)) {
return specialization.addSpecialization(candidate);
}
specialization = candidate;
return true;
}
/**
* Sets the CRS of all given ares to a common value. An exception is thrown if incompatible CRS are found.
* This method does not verify the number of dimensions; this check should have been done by the caller.
*/
static void uniformize(final SubArea[] domains) {
CoordinateReferenceSystem common = null;
for (SubArea area : domains) {
do {
final CoordinateReferenceSystem crs = area.getCoordinateReferenceSystem();
if (common == null) {
common = crs;
} else if (crs != null && !Utilities.equalsIgnoreMetadata(common, crs)) {
throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.MismatchedCRS));
}
} while ((area = area.specialization) != null);
}
for (SubArea area : domains) {
do area.setCoordinateReferenceSystem(common);
while ((area = area.specialization) != null);
}
}
/**
* Creates the inverse transforms. This method should be invoked only once when first needed
* in a block synchronized (indirectly) by {@link SpecializableTransform#inverse()}.
*/
static void createInverse(SubArea area) throws NoninvertibleTransformException {
do {
area.inverse = area.transform.inverse();
area = area.specialization;
} while (area != null);
}
/**
* Returns the area that contains the given position, or {@code null} if none.
* This method may be replaced by an R-Tree in a future Apache SIS version.
*/
static SubArea find(final SubArea[] domains, final DirectPosition pos) {
for (SubArea area : domains) {
if (area.contains(pos)) {
SubArea next = area.specialization;
while (next != null && next.contains(pos)) {
area = next;
next = next.specialization;
}
return area;
}
}
return null;
}
/**
* Returns the area that contains the given position, looking only in the given area or its specializations.
* Returns {@code null} if no area has been found.
*/
static SubArea find(SubArea area, final DirectPosition pos) {
SubArea found = null;
while (area.contains(pos)) {
found = area;
area = area.specialization;
if (area == null) break;
}
return found;
}
/**
* Formats the given area and its transform as a pseudo-WKT.
* For {@link SpecializableTransform#formatTo(Formatter)} implementation only.
*/
static void format(SubArea area, final Formatter formatter) {
while (area != null) {
formatter.newLine(); formatter.append(area);
formatter.newLine(); formatter.append(area.transform);
area = area.specialization;
}
}
/**
* For {@link SpecializableTransform#computeHashCode()} implementation.
*/
@Override
public int hashCode() {
int code = super.hashCode() ^ transform.hashCode();
if (specialization != null) {
code += 37 * specialization.hashCode();
}
return code;
}
/**
* For {@link SpecializableTransform#equals(Object)} implementation.
*/
@Override
public boolean equals(final Object obj) {
if (super.equals(obj)) {
final SubArea other = (SubArea) obj;
return transform.equals(other.transform) && Objects.equals(specialization, other.specialization);
}
return false;
}
/**
* Formats this envelope as a "{@code DOMAIN}" element (non-standard).
* This is used by {@link SpecializableTransform#formatTo(Formatter)}.
*/
@Override
protected String formatTo(final Formatter formatter) {
super.formatTo(formatter);
return "Domain";
}
}
/**
* Domains where specialized transforms are valid. The array should be very small.
* In current implementation, elements in this array shall not overlap.
* This array may be replaced by an R-Tree in a future Apache SIS version.
*/
private final SubArea[] domains;
/**
* The inverse of this transform, computed when first needed.
* Part of serialization for avoiding rounding error issues.
*
* @see #inverse()
*/
private MathTransform inverse;
/**
* 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;
final int sourceDim = global.getSourceDimensions();
final int targetDim = global.getTargetDimensions();
final List<SubArea> areas = new ArrayList<>(specializations.size());
for (final Map.Entry<Envelope,MathTransform> entry : specializations.entrySet()) {
MathTransform tr = entry.getValue();
ensureDimensionMatches(0, sourceDim, tr.getSourceDimensions());
ensureDimensionMatches(1, targetDim, tr.getTargetDimensions());
SubArea[] inherited = null;
if (tr instanceof SpecializableTransform) {
inherited = ((SpecializableTransform) tr).domains;
tr = ((SpecializableTransform) tr).global;
}
final SubArea area = new SubArea(entry.getKey(), tr);
addSpecialization(area, areas, sourceDim);
/*
* At this point we are usually done for the current SubArea. But if the given MathTransform
* is another SpecializableTransform, then instead of storing nested SpecializableTransforms
* we will store directly the specializations that it contains. This will reduce the amount
* of steps when transforming coordinates.
*/
if (inherited != null) {
for (final SubArea other : inherited) {
final SubArea e = new SubArea(other, other.transform);
e.intersect(area);
addSpecialization(e, areas, sourceDim);
}
}
}
domains = areas.toArray(new SubArea[areas.size()]);
SubArea.uniformize(domains);
}
/**
* Helper method for verifying transform dimension consistency.
*
* @param type 0 if verifying source dimension, or 1 if verifying target dimension.
*/
private static void ensureDimensionMatches(final int type, final int expected, final int actual) {
if (expected != actual) {
throw new MismatchedDimensionException(Resources.format(
Resources.Keys.MismatchedTransformDimension_3, type, expected, actual));
}
}
/**
* Verifies if the given {@code area} has the expected number of dimensions,
* then adds it to {@code domains} list (eventually as a child of an existing node).
*
* @param area the new sub-area to add.
* @param domains where to add the sub-area (not necessarily directly; maybe as a child of an existing node).
* @param dim expected number of dimensions, for verification purpose.
*/
private static void addSpecialization(final SubArea area, final List<SubArea> domains, final int dim) {
if (!area.isEmpty()) {
if (area.getDimension() != dim) {
throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
"envelope", dim, area.getDimension()));
}
for (final SubArea previous : domains) {
if (previous.addSpecialization(area)) {
return;
}
}
for (final SubArea previous : domains) {
if (area.intersects(previous)) {
// Pending implementation of R-Tree in Apache SIS.
throw new IllegalArgumentException("Current implementation does not accept overlapping envelopes.");
}
}
domains.add(area);
}
}
/**
* Gets the dimension of input points.
*/
@Override
public final int getSourceDimensions() {
return global.getSourceDimensions();
}
/**
* Gets the dimension of output points.
*/
@Override
public final int getTargetDimensions() {
return global.getTargetDimensions();
}
/**
* Returns the transform to use for the given domain.
*/
private MathTransform forDomain(final SubArea domain) {
return (domain != null) ? domain.transform : global;
}
/**
* Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
* This method delegates to the most specialized transform.
*/
@Override
public final DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst) throws TransformException {
return forDomain(SubArea.find(domains, ptSrc)).transform(ptSrc, ptDst);
}
/**
* Gets the derivative of this transform at a point.
* This method delegates to the most specialized transform.
*/
@Override
public final Matrix derivative(final DirectPosition point) throws TransformException {
return forDomain(SubArea.find(domains, point)).derivative(point);
}
/**
* Transforms a single coordinate point in an array, and optionally computes the transform
* derivative at that location. This method delegates to the most specialized transform.
*/
@Override
public final Matrix transform(final double[] srcPts, final int srcOff,
final double[] dstPts, final int dstOff,
boolean derivate) throws TransformException
{
final DirectPositionView pos = new DirectPositionView.Double(srcPts, srcOff, global.getSourceDimensions());
final MathTransform tr = forDomain(SubArea.find(domains, pos));
if (tr instanceof AbstractMathTransform) {
return ((AbstractMathTransform) tr).transform(srcPts, srcOff, dstPts, dstOff, derivate);
} else {
Matrix derivative = derivate ? tr.derivative(pos) : null; // Must be before transform(srcPts, …).
if (dstPts != null) {
tr.transform(srcPts, srcOff, dstPts, dstOff, 1);
}
return derivative;
}
}
/**
* Call of a {@code MathTransform.transform(…)} method with source and target arrays fixed at
* {@code TransformCall} creation time. This is used for allowing the same implementation to
* be shared by most {@code transform(…)} methods in {@link SpecializableTransform}.
*/
@FunctionalInterface
private interface TransformCall {
/** Performs the transform at the given offsets. */
void apply(MathTransform tr, int srcOff, int dstOff, int numPts) throws TransformException;
}
/**
* Transforms a list of coordinate points. This method delegates to the most specialized transform,
* with single {@code transform(…)} calls for coordinate sequences as long as possible.
*
* @param transform caller for a {@code MathTransform.transform(…)} method.
* @param src a window over the source points. May be backed by a {@code float[]} or {@code double[]} array.
* @param dstOff where to write the first coordinate in the target array.
* @param srcInc the source dimension, negative if we must iterate backward.
* @param dstInc the target dimension, negative if we must iterate backward.
* @param numPts number of points to transform.
*/
private void transform(final TransformCall transform, final DirectPositionView src,
int dstOff, int srcInc, int dstInc, int numPts) throws TransformException
{
final boolean downard = (srcInc < 0);
SubArea domain = SubArea.find(domains, src);
while (numPts > 0) {
int srcOff = src.offset;
final MathTransform tr;
if (domain == null) {
tr = global; // The transform to apply when no specialization is found.
do { // Count how many points will use that transform.
src.offset += srcInc;
if (--numPts <= 0) break;
domain = SubArea.find(domains, src); // More expansive check than the case where domain is non-null.
} while (domain == null);
} else {
final SubArea previous = domain;
tr = domain.transform; // The specialized transform to apply.
do { // Count how many points will use that transform.
src.offset += srcInc;
if (--numPts <= 0) break;
domain = SubArea.find(domain, src); // Cheaper check compared to the case where domain is null.
} while (domain == previous);
if (domain == null) {
domain = SubArea.find(domains, src); // Need to update with the more expansive check.
}
}
final int num = (src.offset - srcOff) / srcInc;
int dstLow = dstOff;
dstOff += dstInc * num;
if (downard) {
srcOff = src.offset - srcInc;
dstLow = dstOff - dstInc;
}
transform.apply(tr, srcOff, dstLow, num);
}
}
/**
* Transforms a list of coordinate points.
* This method delegates to the most specialized transform.
*/
@Override
public final void transform(double[] srcPts, int srcOff, final double[] dstPts, int dstOff, final int numPts)
throws TransformException
{
int srcInc = getSourceDimensions();
int dstInc = getTargetDimensions();
if (srcPts == dstPts) {
switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
case ASCENDING: {
break;
}
case DESCENDING: {
srcOff += (numPts-1) * srcInc; srcInc = -srcInc;
dstOff += (numPts-1) * dstInc; dstInc = -dstInc;
break;
}
default: {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
srcOff = 0;
break;
}
}
}
final double[] refPts = srcPts;
transform((tr, src, dst, num) -> tr.transform(refPts, src, dstPts, dst, num),
new DirectPositionView.Double(srcPts, srcOff, Math.abs(srcInc)),
dstOff, srcInc, dstInc, numPts);
}
/**
* Transforms a list of coordinate points.
* This method delegates to the most specialized transform.
*/
@Override
public final void transform(float[] srcPts, int srcOff, final float[] dstPts, int dstOff, final int numPts)
throws TransformException
{
int srcInc = getSourceDimensions();
int dstInc = getTargetDimensions();
if (srcPts == dstPts) {
switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
case ASCENDING: {
break;
}
case DESCENDING: {
srcOff += (numPts-1) * srcInc; srcInc = -srcInc;
dstOff += (numPts-1) * dstInc; dstInc = -dstInc;
break;
}
default: {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
srcOff = 0;
break;
}
}
}
final float[] refPts = srcPts;
transform((tr, src, dst, num) -> tr.transform(refPts, src, dstPts, dst, num),
new DirectPositionView.Float(srcPts, srcOff, Math.abs(srcInc)),
dstOff, srcInc, dstInc, numPts);
}
/**
* Transforms a list of coordinate points. This method delegates to the most specialized transform,
* with single {@code transform(…)} calls for coordinate sequences as long as possible.
*/
@Override
public final void transform(final double[] srcPts, final int srcOff,
final float [] dstPts, final int dstOff,
final int numPts) throws TransformException
{
final int srcDim = getSourceDimensions();
final int dstDim = getTargetDimensions();
transform((tr, src, dst, num) -> tr.transform(srcPts, src, dstPts, dst, num),
new DirectPositionView.Double(srcPts, srcOff, srcDim),
dstOff, srcDim, dstDim, numPts);
}
/**
* Transforms a list of coordinate points. This method delegates to the most specialized transform,
* with single {@code transform(…)} calls for coordinate sequences as long as possible.
*/
@Override
public final void transform(final float [] srcPts, final int srcOff,
final double[] dstPts, final int dstOff,
final int numPts) throws TransformException
{
final int srcDim = getSourceDimensions();
final int dstDim = getTargetDimensions();
transform((tr, src, dst, num) -> tr.transform(srcPts, src, dstPts, dst, num),
new DirectPositionView.Float(srcPts, srcOff, srcDim),
dstOff, srcDim, dstDim, numPts);
}
/**
* Computes a hash value for this transform.
* This method is invoked by {@link #hashCode()} when first needed.
*/
@Override
protected final int computeHashCode() {
return super.computeHashCode() + 7*global.hashCode() ^ Arrays.hashCode(domains);
}
/**
* Compares the specified object with this math transform for equality.
*/
@Override
public final boolean equals(final Object object, final ComparisonMode mode) {
if (super.equals(object, mode)) {
final SpecializableTransform other = (SpecializableTransform) object;
return Utilities.deepEquals(global, other.global, mode) &&
Utilities.deepEquals(domains, other.domains, mode);
}
return false;
}
/**
* Formats the inner part of a <cite>Well Known Text</cite> version 1 (WKT 1) element.
*
* <div class="note"><b>Compatibility note:</b>
* The {@code SPECIALIZABLE_MT} element formatted here is an Apache SIS-specific extension.</div>
*
* @param formatter the formatter to use.
* @return the WKT element name, which is {@code "Specializable_MT"}.
*/
@Override
protected final String formatTo(final Formatter formatter) {
formatter.newLine();
formatter.append(global);
for (SubArea domain : domains) {
SubArea.format(domain, formatter);
}
formatter.setInvalidWKT(SpecializableTransform.class, null);
return "Specializable_MT";
}
/**
* Returns the inverse of this transform.
*/
@Override
public synchronized MathTransform inverse() throws NoninvertibleTransformException {
if (inverse == null) {
inverse = createInverse();
}
return inverse;
}
/**
* Invoked at construction time for creating the inverse transform.
* Overridden by {@link SpecializableTransform2D} for the two-dimensional variant.
*/
Inverse createInverse() throws NoninvertibleTransformException {
return new Inverse(this);
}
/**
* The inverse of {@link SpecializableTransform}.
*/
static class Inverse extends AbstractMathTransform.Inverse implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 1060617594604917167L;
/**
* The enclosing transform.
*/
private final SpecializableTransform forward;
/**
* The inverse of {@link SpecializableTransform#global}.
*/
private final MathTransform global;
/**
* Creates the inverse of a specialized transform having the given properties.
*/
Inverse(final SpecializableTransform forward) throws NoninvertibleTransformException {
this.forward = forward;
this.global = forward.global.inverse();
for (final SubArea domain : forward.domains) {
SubArea.createInverse(domain);
}
}
/**
* Returns the inverse of this math transform.
*/
@Override
public MathTransform inverse() {
return forward;
}
/**
* Inverse transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
*/
@Override
public final DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst) throws TransformException {
final double[] source = ptSrc.getCoordinate(); // Needs to be first in case ptDst overwrites ptSrc.
ptDst = global.transform(ptSrc, ptDst);
final SubArea domain = SubArea.find(forward.domains, ptDst);
if (domain != null) {
ptDst = domain.inverse.transform(new DirectPositionView.Double(source), ptDst);
}
return ptDst;
}
/**
* Gets the inverse derivative of this transform at a point.
* This method is overridden for consistency.
*/
@Override
public final Matrix derivative(final DirectPosition point) throws TransformException {
return transform(point.getCoordinate(), 0, null, 0, true);
}
/**
* Inverse transforms a single coordinate point in an array, and optionally computes the transform
* derivative at that location.
*/
@Override
public final Matrix transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, final boolean derivate)
throws TransformException
{
final int srcInc = global.getSourceDimensions();
final int dstInc = global.getTargetDimensions();
if (dstPts == null) {
dstPts = new double[dstInc]; // Needed for checking if inside a sub-area.
dstOff = 0;
} else if (srcPts == dstPts && srcOff + srcInc > dstOff && srcOff < dstOff + dstInc) {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcInc);
srcOff = 0;
}
/*
* Above 'srcPts' dhould keep the source coordinates unchanged even if the source and destination
* given in arguments overlap. We need this stability because the source coordinates may be used
* twice, if 'secondTry' become true.
*/
MathTransform tr = global;
boolean secondTry = false;
Matrix derivative;
do {
if (tr instanceof AbstractMathTransform) {
derivative = ((AbstractMathTransform) tr).transform(srcPts, srcOff, dstPts, dstOff, derivate);
} else {
tr.transform(srcPts, srcOff, dstPts, dstOff, 1);
derivative = derivate ? tr.derivative(new DirectPositionView.Double(srcPts, srcOff, srcInc)) : null;
}
if (secondTry) break;
final SubArea domain = SubArea.find(forward.domains, new DirectPositionView.Double(dstPts, dstOff, dstInc));
if (domain != null) {
tr = domain.inverse;
secondTry = true;
}
} while (secondTry);
return derivative;
}
/**
* Invoked for transforming, then verifying if more appropriate transform exists for the result.
* This implementation is similar to the algorithm applied by {@link SpecializableTransform} parent
* class, except that {@link SubArea} is verified <em>after</em> transformations instead than before.
*/
private void transform(final TransformCall transform, final double[] dstPts,
int srcOff, int dstOff, int srcInc, int dstInc, int numPts) throws TransformException
{
final SubArea[] domains = forward.domains;
transform.apply(global, srcOff, dstOff, numPts);
final DirectPositionView dst = new DirectPositionView.Double(dstPts, dstOff, dstInc);
while (numPts > 0) {
SubArea domain = SubArea.find(domains, dst);
if (domain == null) {
dst.offset += dstInc;
numPts--;
continue;
}
do {
final SubArea specialized = domain; // Contains the specialized transform to use.
int num = (dst.offset - dstOff) / dstInc; // Number of points that are not retransformeD.
srcOff += num * srcInc; // Skip the source coordinates that are not retransformed.
dstOff = dst.offset; // Destination index of the first coordinate to retransform.
do {
dst.offset += dstInc; // Destination index after the last coordinate to transform.
if (--numPts <= 0) {
domain = null;
break;
}
domain = SubArea.find(domain, dst);
} while (domain == specialized);
num = (dst.offset - dstOff) / dstInc; // Number of points to retransform.
transform.apply(specialized.inverse, srcOff, dstOff, num);
srcOff += srcInc * num;
dstOff = dst.offset;
} while (domain != null);
}
}
/**
* Inverse transforms a list of coordinate points.
* The transformed points are written directly in the destination array.
*/
@Override
public void transform(double[] srcPts, int srcOff, final double[] dstPts, final int dstOff, final int numPts)
throws TransformException
{
if (numPts <= 0) return;
final int srcInc = global.getSourceDimensions();
final int dstInc = global.getTargetDimensions();
if (srcPts == dstPts) {
final int srcEnd = srcOff + numPts*srcInc;
if (srcEnd > dstOff || dstOff + numPts*dstInc > srcOff) {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcEnd);
srcOff = 0;
}
}
final double[] refPts = srcPts;
transform((tr, src, dst, num) -> tr.transform(refPts, src, dstPts, dst, num),
dstPts, srcOff, dstOff, srcInc, dstInc, numPts);
}
/**
* Inverse transforms a list of coordinate points. This method uses an temporary {@code double[]} buffer
* for testing {@code SubArea} inclusion with full precision before to cast to {@code float} values.
*/
@Override
public void transform(final float[] srcPts, int srcOff,
final float[] dstPts, int dstOff, int numPts)
throws TransformException
{
if (numPts <= 0) return;
final int srcInc = global.getSourceDimensions();
final int dstInc = global.getTargetDimensions();
final double[] buffer = new double[numPts * dstInc];
transform((tr, src, dst, num) -> tr.transform(srcPts, src, buffer, dst, num),
buffer, srcOff, 0, srcInc, dstInc, numPts);
numPts *= dstInc;
for (int i=0; i<numPts; i++) {
dstPts[dstOff++] = (float) buffer[i];
}
}
/**
* Inverse transforms a list of coordinate points. This method uses an temporary {@code double[]} buffer
* for testing {@code SubArea} inclusion with full precision before to cast to {@code float} values.
*/
@Override
public void transform(final double[] srcPts, int srcOff,
final float [] dstPts, int dstOff, int numPts) throws TransformException
{
if (numPts <= 0) return;
final int srcInc = global.getSourceDimensions();
final int dstInc = global.getTargetDimensions();
final double[] buffer = new double[numPts * dstInc];
transform((tr, src, dst, num) -> tr.transform(srcPts, src, buffer, dst, num),
buffer, srcOff, 0, srcInc, dstInc, numPts);
numPts *= dstInc;
for (int i=0; i<numPts; i++) {
dstPts[dstOff++] = (float) buffer[i];
}
}
/**
* Inverse transforms a list of coordinate points.
* The transformed points are written directly in the destination array.
*/
@Override
public void transform(final float [] srcPts, int srcOff,
final double[] dstPts, int dstOff, int numPts) throws TransformException
{
if (numPts <= 0) return;
final int srcInc = global.getSourceDimensions();
final int dstInc = global.getTargetDimensions();
transform((tr, src, dst, num) -> tr.transform(srcPts, src, dstPts, dst, num),
dstPts, srcOff, dstOff, srcInc, dstInc, numPts);
}
}
}