All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.geometry.Envelopes Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotoolkit.geometry;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;

import org.opengis.util.FactoryException;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.NoninvertibleTransformException;

import org.geotoolkit.lang.Static;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.display.shape.XRectangle2D;
import org.geotoolkit.display.shape.ShapeUtilities;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.referencing.operation.matrix.XAffineTransform;
import org.geotoolkit.referencing.operation.transform.AbstractMathTransform;
import org.geotoolkit.internal.referencing.DirectPositionView;
import org.geotoolkit.resources.Errors;

import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.util.Strings.trimFractionalPart;


/**
 * Utility methods for envelopes. This utility class is made up of static functions working
 * with arbitrary implementations of GeoAPI interfaces.
 *
 * @author Martin Desruisseaux (IRD, Geomatys)
 * @author Jody Garnett (Refractions)
 * @author Andrea Aime (TOPP)
 * @author Johann Sorel (Geomatys)
 * @version 3.20
 *
 * @see CRS
 *
 * @since 3.19 (derived from 2.4)
 * @module
 */
public final class Envelopes extends Static {
    /**
     * Enumeration of the 4 corners in an envelope, with repetition of the first point.
     * The values are (x,y) pairs with {@code false} meaning "minimal value" and {@code true}
     * meaning "maximal value". This is used by {@link #toPolygonWKT(Envelope)} only.
     */
    private static final boolean[] CORNERS = {
        false, false,
        false, true,
        true,  true,
        true,  false,
        false, false
    };

    /**
     * Do not allow instantiation of this class.
     */
    private Envelopes() {
    }

    /**
     * Returns the domain of validity for the specified coordinate reference system,
     * or {@code null} if unknown. The returned envelope is expressed in terms of the
     * specified CRS.
     * 

* This method performs the work documented in the * {@link CRS#getEnvelope(CoordinateReferenceSystem)} method. * It is defined in this class for convenience. * * @param crs The coordinate reference system, or {@code null}. * @return The envelope in terms of the specified CRS, or {@code null} if none. * * @see CRS#getEnvelope(CoordinateReferenceSystem) * @see org.geotoolkit.geometry.GeneralEnvelope#reduceToDomain(boolean) */ public static Envelope getDomainOfValidity(final CoordinateReferenceSystem crs) { return CRS.getEnvelope(crs); } /** * A buckle method for calculating derivative and coordinate transformation in a single step, * if the given {@code derivative} argument is {@code true}. * * @param transform The transform to use. * @param srcPts The array containing the source coordinate at offset 0. * @param dstPts the array into which the transformed coordinate is returned. * @param dstOff The offset to the location of the transformed point that is * stored in the destination array. * @param derivate {@code true} for computing the derivative, or {@code false} if not needed. * @return The matrix of the transform derivative at the given source position, or {@code null} * if the {@code derivate} argument is {@code false}. * @throws TransformException If the point can't be transformed or if a problem occurred while * calculating the derivative. */ private static Matrix derivativeAndTransform(final MathTransform transform, final double[] srcPts, final double[] dstPts, final int dstOff, final boolean derivate) throws TransformException { if (transform instanceof AbstractMathTransform) { return ((AbstractMathTransform) transform).transform(srcPts, 0, dstPts, dstOff, derivate); } // Derivative must be calculated before to transform the coordinate. final Matrix derivative = derivate ? transform.derivative(new DirectPositionView(srcPts, 0, transform.getSourceDimensions())) : null; transform.transform(srcPts, 0, dstPts, dstOff, 1); return derivative; } /** * Transforms the given envelope to the specified CRS. If any argument is null, or if the * {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS} is null or the same * instance than the given target CRS, then the given envelope is returned unchanged. * Otherwise a new transformed envelope is returned. * * {@section Performance tip} * If there is many envelopes to transform with the same source and target CRS, then it * is more efficient to get the {@linkplain CoordinateOperation coordinate operation} or * {@linkplain MathTransform math transform} once for ever and invoke one of the methods * below. * * @param envelope The envelope to transform (may be {@code null}). * @param targetCRS The target CRS (may be {@code null}). * @return A new transformed envelope, or directly {@code envelope} if no change was required. * @throws TransformException If a transformation was required and failed. */ public static Envelope transform(Envelope envelope, final CoordinateReferenceSystem targetCRS) throws TransformException { if (envelope != null && targetCRS != null) { final CoordinateReferenceSystem sourceCRS = envelope.getCoordinateReferenceSystem(); if (sourceCRS != targetCRS) { if (sourceCRS == null) { // Slight optimization: just copy the given Envelope. envelope = new GeneralEnvelope(envelope); ((GeneralEnvelope) envelope).setCoordinateReferenceSystem(targetCRS); } else { final CoordinateOperationFactory factory = CRS.getCoordinateOperationFactory(true); final CoordinateOperation operation; try { operation = factory.createOperation(sourceCRS, targetCRS); } catch (FactoryException exception) { throw new TransformException(Errors.format( Errors.Keys.CANT_TRANSFORM_ENVELOPE), exception); } envelope = transform(operation, envelope); } assert AbstractEnvelope.equalsIgnoreMetadata(targetCRS, envelope.getCoordinateReferenceSystem(), true); } } return envelope; } /** * Transforms an envelope using the given {@linkplain MathTransform math transform}. * The transformation is only approximative: the returned envelope may be bigger than * necessary, or smaller than required if the bounding box contains a pole. *

* This method can not handle the case where the envelope contains the North or South pole, * or when it cross the ±180° longitude, because {@linkplain MathTransform math * transforms} does not carry sufficient informations. For a more robust envelope * transformation, use {@link #transform(CoordinateOperation, Envelope)} instead. * * @param transform The transform to use. * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. * @return The transformed envelope, or {@code null} if {@code envelope} was null. * @throws TransformException if a transform failed. * * @see #transform(CoordinateOperation, Envelope) */ public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) throws TransformException { ensureNonNull("transform", transform); if (envelope == null) { return null; } if (transform instanceof MathTransform2D && envelope.getDimension() == 2) { final XRectangle2D tmp = XRectangle2D.createFromExtremums( envelope.getMinimum(0), envelope.getMinimum(1), envelope.getMaximum(0), envelope.getMaximum(1)); transform((MathTransform2D) transform, tmp, tmp); return new GeneralEnvelope(tmp); } return transform(transform, envelope, null); } /** * Implementation of {@link #transform(MathTransform, Envelope)} with the opportunity to * save the projected center coordinate. * * @param targetPt After this method call, the center of the source envelope projected to * the target CRS. The length of this array must be the number of target dimensions. * May be {@code null} if this information is not needed. */ private static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope, final double[] targetPt) throws TransformException { if (transform.isIdentity()) { /* * Slight optimization: Just copy the envelope. Note that we need to set the CRS * to null because we don't know what the target CRS was supposed to be. Even if * an identity transform often imply that the target CRS is the same one than the * source CRS, it is not always the case. The metadata may be differents, or the * transform may be a datum shift without Bursa-Wolf parameters, etc. */ final GeneralEnvelope transformed = new GeneralEnvelope(envelope); transformed.setCoordinateReferenceSystem(null); if (targetPt != null) { for (int i=envelope.getDimension(); --i>=0;) { targetPt[i] = transformed.getMedian(i); } } return transformed; } /* * Checks argument validity: envelope and math transform dimensions must be consistent. */ final int sourceDim = transform.getSourceDimensions(); final int targetDim = transform.getTargetDimensions(); if (envelope.getDimension() != sourceDim) { throw new MismatchedDimensionException(Errors.format(Errors.Keys.MISMATCHED_DIMENSION_$2, sourceDim, envelope.getDimension())); } /* * Allocates all needed objects. The value '3' below is because the following 'while' * loop uses a 'pointIndex' to be interpreted as a number in base 3 (see the comment * inside the loop). The coordinate to transform must be initialized to the minimal * ordinate values. This coordinate will be updated in the 'switch' statement inside * the 'while' loop. */ int pointIndex = 0; boolean isDerivativeSupported = true; GeneralEnvelope transformed = null; final Matrix[] derivatives = new Matrix[(int) Math.round(Math.pow(3, sourceDim))]; final double[] ordinates = new double[derivatives.length * targetDim]; final double[] sourcePt = new double[sourceDim]; for (int i=sourceDim; --i>=0;) { sourcePt[i] = envelope.getMinimum(i); } /* * Iterates over every minimal, maximal and median ordinate values (3 points) along each * dimension. The total number of iterations is 3 ^ (number of source dimensions). */ transformPoint: while (true) { /* * Compute the derivative (optional operation). If this operation fails, we will * set a flag to 'false' so we don't try again for all remaining points. We try * to compute the derivative and the transformed point in a single operation if * we can. If we can not, we will compute those two information separately. * * Note that the very last point to be projected must be the envelope center. * There is usually no need to calculate the derivative for that last point, * but we let it does anyway for safety. */ final int offset = pointIndex * targetDim; try { derivatives[pointIndex] = derivativeAndTransform(transform, sourcePt, ordinates, offset, isDerivativeSupported); } catch (TransformException e) { if (!isDerivativeSupported) { throw e; // Derivative were already disabled, so something went wrong. } isDerivativeSupported = false; transform.transform(sourcePt, 0, ordinates, offset, 1); recoverableException(e); // Log only if the above call was successful. } /* * The transformed point has been savec for future reuse after the enclosing * 'while' loop. Now add the transformed point to the destination envelope. */ if (transformed == null) { transformed = new GeneralEnvelope(targetDim); for (int i=0; i=0; indexBase3 /= 3) { switch (indexBase3 % 3) { case 0: sourcePt[dim] = envelope.getMinimum(dim); break; // Continue the loop. case 1: sourcePt[dim] = envelope.getMaximum(dim); continue transformPoint; case 2: sourcePt[dim] = envelope.getMedian (dim); continue transformPoint; default: throw new AssertionError(indexBase3); // Should never happen } } break; } assert pointIndex == derivatives.length : pointIndex; /* * At this point we finished to build an envelope from all sampled positions. Now iterate * over all points. For each point, iterate over all line segments from that point to a * neighbor median point. Use the derivate information for approximating the transform * behavior in that area by a cubic curve. We can then find analytically the curve extremum. * * The same technic is applied in transform(MathTransform, Rectangle2D), except that in * the Rectangle2D case the calculation was bundled right inside the main loop in order * to avoid the need for storage. */ double[] temporary = targetPt; for (pointIndex=0; pointIndex < derivatives.length; pointIndex++) { final Matrix D1 = derivatives[pointIndex]; if (D1 != null) { int indexBase3=pointIndex, power3=1; for (int i=sourceDim; --i>=0; indexBase3 /= 3, power3 *= 3) { final int digitBase3 = indexBase3 % 3; if (digitBase3 != 2) { // Process only if we are not already located on the median along the dimension i. final int medianIndex = pointIndex + power3*(2-digitBase3); final Matrix D2 = derivatives[medianIndex]; if (D2 != null) { final double xmin = envelope.getMinimum(i); final double xmax = envelope.getMaximum(i); final double x2 = envelope.getMedian (i); final double x1 = (digitBase3 == 0) ? xmin : xmax; final int offset1 = targetDim * pointIndex; final int offset2 = targetDim * medianIndex; for (int j=0; j xmin && x < xmax) { final double y = isP2 ? extremum.y2 : extremum.y1; if (y < transformed.getMinimum(j) || y > transformed.getMaximum(j)) { /* * At this point, we have determined that adding the extremum point * would expand the envelope. However we will not add that point * directly because its position may not be quite right (since we * used a cubic curve approximation). Instead, we project the point * on the envelope border which is located vis-à-vis the extremum. */ for (int ib3=pointIndex, dim=sourceDim; --dim>=0; ib3 /= 3) { final double ordinate; if (dim == i) { ordinate = x; // Position of the extremum. } else switch (ib3 % 3) { case 0: ordinate = envelope.getMinimum(dim); break; case 1: ordinate = envelope.getMaximum(dim); break; case 2: ordinate = envelope.getMedian (dim); break; default: throw new AssertionError(ib3); // Should never happen } sourcePt[dim] = ordinate; } if (temporary == null) { temporary = new double[targetDim]; } transform.transform(sourcePt, 0, temporary, 0, 1); transformed.add(temporary, 0); } } } while ((isP2 = !isP2) == true); } } } } derivatives[pointIndex] = null; // Let GC do its job earlier. } } if (targetPt != null) { // Copy the coordinate of the center point. System.arraycopy(ordinates, ordinates.length - targetDim, targetPt, 0, targetDim); } return transformed; } /** * Transforms an envelope using the given {@linkplain CoordinateOperation coordinate operation}. * The transformation is only approximative: the returned envelope may be bigger than the * smallest possible bounding box, but should not be smaller in most cases. *

* This method can handle the case where the envelope contains the North or South pole, * or when it cross the ±180° longitude. * * {@note If the envelope CRS is non-null, then the caller should ensure that the operation * source CRS is the same than the envelope CRS. In case of mismatch, this method transforms * the envelope to the operation source CRS before to apply the operation. This extra step * may cause a lost of accuracy. In order to prevent this method from performing such * pre-transformation (if not desired), callers can ensure that the envelope CRS is * null before to call this method.} * * @param operation The operation to use. * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. * @return The transformed envelope, or {@code null} if {@code envelope} was null. * @throws TransformException if a transform failed. * * @see #transform(MathTransform, Envelope) */ public static GeneralEnvelope transform(final CoordinateOperation operation, Envelope envelope) throws TransformException { ensureNonNull("operation", operation); if (envelope == null) { return null; } final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS(); if (sourceCRS != null) { final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); if (crs != null && !CRS.equalsIgnoreMetadata(crs, sourceCRS)) { /* * Argument-check: the envelope CRS seems inconsistent with the given operation. * However we need to push the check a little bit further, since 3D-GeographicCRS * are considered not equal to CompoundCRS[2D-GeographicCRS + ellipsoidal height]. * Checking for identity MathTransform is a more powerfull (but more costly) check. * Since we have the MathTransform, perform an opportunist envelope transform if it * happen to be required. */ final MathTransform mt; try { mt = CRS.findMathTransform(crs, sourceCRS, false); } catch (FactoryException e) { throw new TransformException(Errors.format(Errors.Keys.CANT_TRANSFORM_ENVELOPE), e); } if (!mt.isIdentity()) { envelope = transform(mt, envelope); } } } MathTransform mt = operation.getMathTransform(); final double[] centerPt = new double[mt.getTargetDimensions()]; final GeneralEnvelope transformed = transform(mt, envelope, centerPt); /* * If the source envelope crosses the expected range of valid coordinates, also projects * the range bounds as a safety. Example: if the source envelope goes from 150 to 200°E, * some map projections will interpret 200° as if it was -160°, and consequently produce * an envelope which do not include the 180°W extremum. We will add those extremum points * explicitly as a safety. It may leads to bigger than necessary target envelope, but the * contract is to include at least the source envelope, not to return the smallest one. */ if (sourceCRS != null) { final CoordinateSystem cs = sourceCRS.getCoordinateSystem(); if (cs != null) { // Should never be null, but check as a paranoiac safety. DirectPosition sourcePt = null; DirectPosition targetPt = null; final int dimension = cs.getDimension(); for (int i=0; i min && v1 < max); final boolean b2 = (v2 > min && v2 < max); if (!b1 && !b2) { continue; } if (sourcePt == null) { sourcePt = new GeneralDirectPosition(dimension); for (int j=0; j= mt.getSourceDimensions()) { recoverableException(exception); } return transformed; } targetPt = new GeneralDirectPosition(mt.getSourceDimensions()); for (int j=0; j * Invoking this method is equivalent to invoking the following: * * {@preformat java * transform(transform, new GeneralEnvelope(envelope)).toRectangle2D() * } * * Note that this method can not handle the case where the rectangle contains the North or South * pole, or when it cross the ±180° longitude, because {@linkplain MathTransform * math transforms} do not carry sufficient informations. For a more robust rectangle * transformation, use {@link #transform(CoordinateOperation, Rectangle2D, Rectangle2D)} * instead. * * @param transform The transform to use. Source and target dimension must be 2. * @param envelope The rectangle to transform (may be {@code null}). * @param destination The destination rectangle (may be {@code envelope}). * If {@code null}, a new rectangle will be created and returned. * @return {@code destination}, or a new rectangle if {@code destination} was non-null * and {@code envelope} was null. * @throws TransformException if a transform failed. * * @see #transform(CoordinateOperation, Rectangle2D, Rectangle2D) * @see org.geotoolkit.referencing.operation.matrix.XAffineTransform#transform(AffineTransform, Rectangle2D, Rectangle2D) */ public static Rectangle2D transform(final MathTransform2D transform, final Rectangle2D envelope, Rectangle2D destination) throws TransformException { ensureNonNull("transform", transform); if (transform instanceof AffineTransform) { // Common case implemented in a more efficient way (less points to transform). return XAffineTransform.transform((AffineTransform) transform, envelope, destination); } return transform(transform, envelope, destination, new double[2]); } /** * Implementation of {@link #transform(MathTransform, Rectangle2D, Rectangle2D)} with the * opportunity to save the projected center coordinate. This method sets {@code point} to * the center of the source envelope projected to the target CRS. */ @SuppressWarnings("fallthrough") private static Rectangle2D transform(final MathTransform2D transform, final Rectangle2D envelope, Rectangle2D destination, final double[] point) throws TransformException { if (envelope == null) { return null; } double xmin = Double.POSITIVE_INFINITY; double ymin = Double.POSITIVE_INFINITY; double xmax = Double.NEGATIVE_INFINITY; double ymax = Double.NEGATIVE_INFINITY; /* * Notation (as if we were applying a map projection, but this is not necessarily the case): * - (λ,φ) are ordinate values before projection. * - (x,y) are ordinate values after projection. * - D[00|01|10|11] are the ∂x/∂λ, ∂x/∂φ, ∂y/∂λ and ∂y/∂φ derivatives respectively. * - Variables with indice 0 are for the very first point in iteration order. * - Variables with indice 1 are for the values of the previous iteration. * - Variables with indice 2 are for the current values in the iteration. * - P1-P2 form a line segment to be checked for curvature. */ double x0=0, y0=0, λ0=0, φ0=0; double x1=0, y1=0, λ1=0, φ1=0; Matrix D0=null, D1=null, D2=null; // x2 and y2 defined inside the loop. boolean isDerivativeSupported = true; for (int i=0; i<=8; i++) { /* * Iteration order (center must be last): * * (6)────(5)────(4) * | | * (7) (8) (3) * | | * (0)────(1)────(2) */ double λ2, φ2; switch (i) { case 0: case 6: case 7: λ2 = envelope.getMinX(); break; case 1: case 5: case 8: λ2 = envelope.getCenterX(); break; case 2: case 3: case 4: λ2 = envelope.getMaxX(); break; default: throw new AssertionError(i); } switch (i) { case 0: case 1: case 2: φ2 = envelope.getMinY(); break; case 3: case 7: case 8: φ2 = envelope.getCenterY(); break; case 4: case 5: case 6: φ2 = envelope.getMaxY(); break; default: throw new AssertionError(i); } point[0] = λ2; point[1] = φ2; try { D1 = D2; D2 = derivativeAndTransform(transform, point, point, 0, isDerivativeSupported && i != 8); } catch (TransformException e) { if (!isDerivativeSupported) { throw e; // Derivative were already disabled, so something went wrong. } isDerivativeSupported = false; D2 = null; point[0] = λ2; point[1] = φ2; transform.transform(point, 0, point, 0, 1); recoverableException(e); // Log only if the above call was successful. } double x2 = point[0]; double y2 = point[1]; if (x2 < xmin) xmin = x2; if (x2 > xmax) xmax = x2; if (y2 < ymin) ymin = y2; if (y2 > ymax) ymax = y2; switch (i) { case 0: { // Remember the first point. λ0=λ2; x0=x2; φ0=φ2; y0=y2; D0=D2; break; } case 8: { // Close the iteration with the first point. λ2=λ0; x2=x0; // Discard P2 because it is the rectangle center. φ2=φ0; y2=y0; D2=D0; break; } } /* * At this point, we expanded the rectangle using the projected points. Now try * to use the information provided by derivatives at those points, if available. * For the following block, notation is: * * - s are ordinate values in the source space (λ or φ) * - t are ordinate values in the target space (x or y) * * They are not necessarily in the same dimension. For example would could have * s=λ while t=y. This is typically the case when inspecting the top or bottom * line segment of the rectangle. * * The same technic is also applied in the transform(MathTransform, Envelope) method. * The general method is more "elegant", at the cost of more storage requirement. */ if (D1 != null && D2 != null) { final int srcDim; final double s1, s2; // Ordinate values in source space (before projection) switch (i) { case 1: case 2: case 5: case 6: {assert φ2==φ1; srcDim=0; s1=λ1; s2=λ2; break;} // Horizontal segment case 3: case 4: case 7: case 8: {assert λ2==λ1; srcDim=1; s1=φ1; s2=φ2; break;} // Vertical segment default: throw new AssertionError(i); } final double min, max; if (s1 < s2) {min=s1; max=s2;} else {min=s2; max=s1;} int tgtDim = 0; do { // Executed exactly twice, for dimensions 0 and 1 in the projected space. final Line2D.Double extremum = ShapeUtilities.cubicCurveExtremum( s1, (tgtDim == 0) ? x1 : y1, D1.getElement(tgtDim, srcDim), s2, (tgtDim == 0) ? x2 : y2, D2.getElement(tgtDim, srcDim)); /* * At this point we found the extremum of the projected line segment * using a cubic curve t = A + Bs + Cs² + Ds³ approximation. Before * to add those extremum into the projected bounding box, we need to * ensure that the source ordinate is inside the the original * (unprojected) bounding box. */ boolean isP2 = false; do { // Executed exactly twice, one for each point. final double se = isP2 ? extremum.x2 : extremum.x1; if (se > min && se < max) { final double te = isP2 ? extremum.y2 : extremum.y1; if ((tgtDim == 0) ? (te < xmin || te > xmax) : (te < ymin || te > ymax)) { /* * At this point, we have determined that adding the extremum point * to the rectangle would have expanded it. However we will not add * that point directly, because maybe its position is not quite right * (since we used a cubic curve approximation). Instead, we project * the point on the rectangle border which is located vis-à-vis the * extremum. Our tests show that the correction can be as much as 50 * metres. */ final double oldX = point[0]; final double oldY = point[1]; if (srcDim == 0) { point[0] = se; point[1] = φ1; // == φ2 since we have an horizontal segment. } else { point[0] = λ1; // == λ2 since we have a vertical segment. point[1] = se; } transform.transform(point, 0, point, 0, 1); final double x = point[0]; final double y = point[1]; if (x < xmin) xmin = x; if (x > xmax) xmax = x; if (y < ymin) ymin = y; if (y > ymax) ymax = y; point[0] = oldX; point[1] = oldY; } } } while ((isP2 = !isP2) == true); } while (++tgtDim == 1); } λ1=λ2; x1=x2; φ1=φ2; y1=y2; D1=D2; } if (destination != null) { destination.setRect(xmin, ymin, xmax-xmin, ymax-ymin); } else { destination = XRectangle2D.createFromExtremums(xmin, ymin, xmax, ymax); } /* * Note: a previous version had an "assert" statement here comparing our calculation * with the calculation performed by the more general method working on Envelope. We * verified that the same values (coordinate points and derivatives) were ultimately * passed to the ShapeUtilities.cubicCurveExtremum(...) method, so we would expect * the same result. However the iteration order is different. The result seems * insensitive to iteration order most of the time, but not always. However, it seems * that the cases were the results are different are the cases where the methods working * with CoordinateOperation object wipe out that difference anyway. */ return destination; } /** * Transforms a rectangular envelope using the given {@linkplain CoordinateOperation coordinate * operation}. The transformation is only approximative: the returned envelope may be bigger * than the smallest possible bounding box, but should not be smaller in most cases. *

* Invoking this method is equivalent to invoking the following: * * {@preformat java * transform(operation, new GeneralEnvelope(envelope)).toRectangle2D() * } * * This method can handle the case where the rectangle contains the North or South pole, * or when it cross the ±180° longitude. * * @param operation The operation to use. Source and target dimension must be 2. * @param envelope The rectangle to transform (may be {@code null}). * @param destination The destination rectangle (may be {@code envelope}). * If {@code null}, a new rectangle will be created and returned. * @return {@code destination}, or a new rectangle if {@code destination} was non-null * and {@code envelope} was null. * @throws TransformException if a transform failed. * * @see #transform(MathTransform2D, Rectangle2D, Rectangle2D) * @see org.geotoolkit.referencing.operation.matrix.XAffineTransform#transform(AffineTransform, Rectangle2D, Rectangle2D) */ public static Rectangle2D transform(final CoordinateOperation operation, final Rectangle2D envelope, Rectangle2D destination) throws TransformException { ensureNonNull("operation", operation); if (envelope == null) { return null; } final MathTransform transform = operation.getMathTransform(); if (!(transform instanceof MathTransform2D)) { throw new MismatchedDimensionException(Errors.format(Errors.Keys.NO_TRANSFORM2D_AVAILABLE)); } MathTransform2D mt = (MathTransform2D) transform; 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 takes 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 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). */ TransformException warning = null; Point2D sourcePt = null; Point2D targetPt = 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.isInfinite(extremum) || Double.isNaN(extremum)) { continue; } if (targetPt == null) { try { mt = mt.inverse(); } catch (NoninvertibleTransformException exception) { recoverableException(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 = mt.transform(targetPt, sourcePt); } catch (TransformException exception) { if (warning == null) { warning = exception; } else { // TODO: addSuppress with JDK7. } continue; } if (envelope.contains(sourcePt)) { destination.add(targetPt); includedBoundsValue |= (1 << border); } } /* * 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 && !isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC; if ((toTest & 0xCCCCCCCC) != 0 && !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); double x = (border & 1) == 0 ? toAdd.getMinimumValue() : toAdd.getMaximumValue(); double y = (border & 4) == 0 ? added.getMinimumValue() : added.getMaximumValue(); if (dimensionToAdd != 0) { final double t=x; x=y; y=t; } targetPt.setLocation(x, y); try { sourcePt = mt.transform(targetPt, sourcePt); } catch (TransformException exception) { if (warning == null) { warning = exception; } else { // TODO: addSuppress with JDK7. } continue; } if (envelope.contains(sourcePt)) { destination.add(targetPt); } } } if (warning != null) { recoverableException(warning); } return destination; } /** * Returns {@code true} if the given axis is of kind "Wrap Around". */ private static boolean isWrapAround(final CoordinateSystemAxis axis) { return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning()); } /** * Invoked when a recoverable exception occurred. Those exceptions must be minor enough * that they can be silently ignored in most cases. */ private static void recoverableException(final TransformException exception) { Logging.recoverableException(Envelopes.class, "transform", exception); } /** * Returns an envelope from the given Well Known Text (WKT). * This method is quite lenient. For example all the following strings are accepted: *

*

    *
  • {@code BOX(-180 -90, 180 90)}
  • *
  • {@code POINT(6 10)}
  • *
  • {@code MULTIPOLYGON(((1 1, 5 1, 1 5, 1 1),(2 2, 3 2, 3 3, 2 2)))}
  • *
  • {@code GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(3 8,7 10))}
  • *
*

* See {@link GeneralEnvelope#GeneralEnvelope(String)} for more information about the * parsing rules. * * @param wkt The {@code BOX}, {@code POLYGON} or other kind of element to parse. * @return The envelope of the given geometry. * @throws FactoryException If the given WKT can not be parsed. * * @see CRS#parseWKT(String) * @see #toWKT(Envelope) * @see org.geotoolkit.measure.CoordinateFormat * @see org.geotoolkit.io.wkt */ public static Envelope parseWKT(final String wkt) throws FactoryException { ensureNonNull("wkt", wkt); try { return new GeneralEnvelope(wkt); } catch (RuntimeException e) { throw new FactoryException(Errors.format( Errors.Keys.CANT_CREATE_OBJECT_FROM_TEXT_$1, Envelope.class), e); } } /** * Formats a {@code BOX} element from an envelope. This method formats the given envelope in * the Well Known Text (WKT) format. The output is like below, where n * is the {@linkplain Envelope#getDimension() number of dimensions}: * *

{@code BOX}n{@code D(}{@linkplain Envelope#getLowerCorner() lower * corner}{@code ,} {@linkplain Envelope#getUpperCorner() upper corner}{@code )}
* * The output of this method can be {@linkplain GeneralEnvelope#GeneralEnvelope(String) parsed} * by the {@link GeneralEnvelope} constructor. * * @param envelope The envelope to format. * @return The envelope as a {@code BOX2D} or {@code BOX3D} in WKT format. * * @see #parseWKT(String) * @see org.geotoolkit.measure.CoordinateFormat * @see org.geotoolkit.io.wkt */ public static String toWKT(final Envelope envelope) { return AbstractEnvelope.toString(envelope); } /** * Formats a {@code POLYGON} element from an envelope. This method formats the given envelope * as a geometry in the Well Known Text (WKT) format. This is provided as an * alternative to the {@code BOX} element formatted by the above {@link #toWKT(Envelope)} * method, because the {@code BOX} element is usually not considered a geometry while * {@code POLYGON} is. *

* The output of this method can be {@linkplain GeneralEnvelope#GeneralEnvelope(String) parsed} * by the {@link GeneralEnvelope} constructor. * * @param envelope The envelope to format. * @return The envelope as a {@code POLYGON} in WKT format. * @throws IllegalArgumentException if the given envelope can not be formatted. * * @see org.geotoolkit.io.wkt */ public static String toPolygonWKT(final Envelope envelope) throws IllegalArgumentException { /* * Get the dimension, ignoring the trailing ones which have infinite values. */ int dimension = envelope.getDimension(); while (dimension != 0) { final double length = envelope.getSpan(dimension - 1); if (!Double.isNaN(length) && !Double.isInfinite(length)) { break; } dimension--; } if (dimension < 2) { throw new IllegalArgumentException(Errors.format(Errors.Keys.EMPTY_ENVELOPE_2D)); } final StringBuilder buffer = new StringBuilder("POLYGON("); String separator = "("; for (int corner=0; corner





© 2015 - 2025 Weber Informatics LLC | Privacy Policy