in endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Shapes2D.java [414:639]
public static Rectangle2D transform(final CoordinateOperation operation,
final Rectangle2D envelope,
Rectangle2D destination)
throws TransformException
{
ArgumentChecks.ensureNonNull("operation", operation);
if (envelope == null) {
return null;
}
final MathTransform2D mt = MathTransforms.bidimensional(operation.getMathTransform());
final double[] center = new double[2];
destination = transform(mt, envelope, destination, center);
/*
* If the source envelope crosses the expected range of valid coordinates, also projects
* the range bounds as a safety. See the comments in transform(Envelope, ...).
*/
final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
if (sourceCRS != null) {
final CoordinateSystem cs = sourceCRS.getCoordinateSystem();
if (cs != null && cs.getDimension() == 2) { // Paranoiac check.
CoordinateSystemAxis axis = cs.getAxis(0);
double min = envelope.getMinX();
double max = envelope.getMaxX();
Point2D.Double pt = null;
for (int i=0; i<4; i++) {
if (i == 2) {
axis = cs.getAxis(1);
min = envelope.getMinY();
max = envelope.getMaxY();
}
final double v = (i & 1) == 0 ? axis.getMinimumValue() : axis.getMaximumValue();
if (!(v > min && v < max)) {
continue;
}
if (pt == null) {
pt = new Point2D.Double();
}
if ((i & 2) == 0) {
pt.x = v;
pt.y = envelope.getCenterY();
} else {
pt.x = envelope.getCenterX();
pt.y = v;
}
destination.add(mt.transform(pt, pt));
}
}
}
/*
* Now take the target CRS in account.
*/
final CoordinateReferenceSystem targetCRS = operation.getTargetCRS();
if (targetCRS == null) {
return destination;
}
final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
if (targetCS == null || targetCS.getDimension() != 2) {
// It should be an error, but we keep this method tolerant.
return destination;
}
/*
* Checks for singularity points. See the Envelopes.transform(CoordinateOperation, Envelope)
* method for comments about the algorithm. The code below is the same algorithm adapted for
* the 2D case and the related objects (Point2D, Rectangle2D, etc.).
*
* The `border` variable in the loop below is used in order to compress 2 dimensions
* and 2 extremums in a single loop, in this order: (xmin, xmax, ymin, ymax).
*/
MathTransform2D inverse = null;
TransformException warning = null;
Point2D sourcePt = null;
Point2D targetPt = null;
Point2D revertPt = null;
int includedBoundsValue = 0; // A bitmask for each (dimension, extremum) pairs.
for (int border=0; border<4; border++) { // 2 dimensions and 2 extremums compacted in a flag.
final int dimension = border >>> 1; // The dimension index being examined.
final CoordinateSystemAxis axis = targetCS.getAxis(dimension);
if (axis == null) { // Should never be null, but check as a paranoiac safety.
continue;
}
final double extremum = (border & 1) == 0 ? axis.getMinimumValue() : axis.getMaximumValue();
if (!Double.isFinite(extremum)) {
continue;
}
if (inverse == null) {
try {
inverse = mt.inverse();
} catch (NoninvertibleTransformException exception) {
Envelopes.recoverableException(Shapes2D.class, exception);
return destination;
}
targetPt = new Point2D.Double();
}
switch (dimension) {
case 0: targetPt.setLocation(extremum, center[1]); break;
case 1: targetPt.setLocation(center[0], extremum ); break;
default: throw new AssertionError(border);
}
try {
sourcePt = inverse.transform(targetPt, sourcePt);
if (CoordinateOperations.isWrapAround(axis)) {
revertPt = mt.transform(sourcePt, revertPt);
final double delta = Math.abs((dimension == 0 ? revertPt.getX() : revertPt.getY()) - extremum);
if (!(delta < Envelopes.SPAN_FRACTION_AS_BOUND * (axis.getMaximumValue() - axis.getMinimumValue()))) {
continue;
}
}
if (envelope.contains(sourcePt)) {
destination.add(targetPt);
includedBoundsValue |= (1 << border);
}
} catch (TransformException exception) {
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
}
}
/*
* Iterate over all dimensions of type "WRAPAROUND" for which minimal or maximal axis
* values have not yet been included in the envelope. We could inline this check inside
* the above loop, but we don't in order to have a chance to exclude the dimensions for
* which the point have already been added.
*
* See transform(CoordinateOperation, Envelope) for more comments about the algorithm.
*/
if (includedBoundsValue != 0) {
/*
* Bits mask transformation:
* 1) Swaps the two dimensions (YyXx → XxYy)
* 2) Insert a space between each bits (XxYy → X.x.Y.y.)
* 3) Fill the space with duplicated values (X.x.Y.y. → XXxxYYyy)
*
* In terms of bit positions 1,2,4,8 (not bit values), we have:
*
* 8421 → 22881144
* i.e. (ymax, ymin, xmax, xmin) → (xmax², ymax², xmin², ymin²)
*
* Now look at the last part: (xmin², ymin²). The next step is to perform a bitwise
* AND operation in order to have only both of the following conditions:
*
* Borders not yet added to the envelope: ~(ymax, ymin, xmax, xmin)
* Borders in which a singularity exists: (xmin, xmin, ymin, ymin)
*
* The same operation is repeated on the next 4 bits for (xmax, xmax, ymax, ymax).
*/
int toTest = ((includedBoundsValue & 1) << 3) | ((includedBoundsValue & 4) >>> 1) |
((includedBoundsValue & 2) << 6) | ((includedBoundsValue & 8) << 2);
toTest |= (toTest >>> 1); // Duplicate the bit values.
toTest &= ~(includedBoundsValue | (includedBoundsValue << 4));
/*
* Forget any axes that are not of kind "WRAPAROUND". Then get the final
* bit pattern indicating which points to test. Iterate over that bits.
*/
if ((toTest & 0x33333333) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC;
if ((toTest & 0xCCCCCCCC) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(1))) toTest &= 0x33333333;
while (toTest != 0) {
final int border = Integer.numberOfTrailingZeros(toTest);
final int bitMask = 1 << border;
toTest &= ~bitMask; // Clear now the bit, for the next iteration.
final int dimensionToAdd = (border >>> 1) & 1;
final CoordinateSystemAxis toAdd = targetCS.getAxis(dimensionToAdd);
final CoordinateSystemAxis added = targetCS.getAxis(dimensionToAdd ^ 1);
final double x = (border & 1) == 0 ? toAdd.getMinimumValue() : toAdd.getMaximumValue();
final double y = (border & 4) == 0 ? added.getMinimumValue() : added.getMaximumValue();
if (dimensionToAdd == 0) {
targetPt.setLocation(x, y);
} else {
targetPt.setLocation(y, x);
}
try {
sourcePt = inverse.transform(targetPt, sourcePt);
if (envelope.contains(sourcePt)) {
destination.add(targetPt);
}
} catch (TransformException exception) {
if (warning == null) {
warning = exception;
} else {
warning.addSuppressed(exception);
}
}
}
}
/*
* At this point we finished envelope transformation. Verify if some coordinates need to be "wrapped around"
* as a result of the coordinate operation. This is usually the longitude axis where the source CRS uses
* the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. In such case we
* set the rectangle to the full range (we do not use the mechanism documented in Envelope2D) because most
* Rectangle2D implementations do not support crossing the anti-meridian. This results in larger rectangle
* than what would be possible with GeneralEnvelope or Envelope2D, but we try to limit the situation where
* this expansion is applied.
*/
final Set<Integer> wrapAroundChanges;
if (operation instanceof AbstractCoordinateOperation) {
wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
} else {
wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
}
for (int dim : wrapAroundChanges) { // Empty in the vast majority of cases.
final CoordinateSystemAxis axis = targetCS.getAxis(dim);
final double minimum = axis.getMinimumValue();
final double maximum = axis.getMaximumValue();
final double o1, o2;
if (dim == 0) {
o1 = destination.getMinX();
o2 = destination.getMaxX();
} else {
o1 = destination.getMinY();
o2 = destination.getMaxY();
}
if (o1 < minimum || o2 > maximum) {
final double span = maximum - minimum;
if (dim == 0) {
destination.setRect(minimum, destination.getY(), span, destination.getHeight());
} else {
destination.setRect(destination.getX(), minimum, destination.getWidth(), span);
}
}
}
if (warning != null) {
Envelopes.recoverableException(Shapes2D.class, warning);
}
return destination;
}