Author: desruisseaux
Date: Sun Dec 16 17:55:53 2012
New Revision: 1422641

URL: http://svn.apache.org/viewvc?rev=1422641&view=rev
Log:
Ported the Envelope2D class.

Added:
    
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
   (with props)

Added: 
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java?rev=1422641&view=auto
==============================================================================
--- 
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
 (added)
+++ 
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
 Sun Dec 16 17:55:53 2012
@@ -0,0 +1,983 @@
+/*
+ * 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.geometry;
+
+import java.util.Objects;
+import java.awt.geom.Rectangle2D;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.geometry.MismatchedReferenceSystemException;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.util.FactoryException;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.referencing.CRS;
+
+import static java.lang.Double.NaN;
+import static java.lang.Double.isNaN;
+import static java.lang.Double.POSITIVE_INFINITY;
+import static java.lang.Double.NEGATIVE_INFINITY;
+import static java.lang.Double.doubleToLongBits;
+import static org.apache.sis.math.MathFunctions.isPositive;
+import static org.apache.sis.math.MathFunctions.isNegative;
+import static org.apache.sis.math.MathFunctions.isSameSign;
+import static org.apache.sis.internal.referencing.Utilities.isPoleToPole;
+
+// Following imports are needed because we can't extend AbstractEnvelope.
+// We want to write this class as if it was an AbstractEnvelope subclass.
+import static org.apache.sis.geometry.AbstractEnvelope.getAxis;
+import static org.apache.sis.geometry.AbstractEnvelope.getCommonCRS;
+import static org.apache.sis.geometry.AbstractEnvelope.fixSpan;
+import static org.apache.sis.geometry.AbstractEnvelope.fixMedian;
+import static org.apache.sis.geometry.AbstractEnvelope.isWrapAround;
+import static org.apache.sis.geometry.AbstractEnvelope.isNegativeUnsafe;
+
+
+/**
+ * A two-dimensional envelope on top of {@link Rectangle2D}.
+ * This implementation is provided for inter-operability between Java2D and 
GeoAPI.
+ *
+ * <p>This class inherits {@linkplain #x x} and {@linkplain #y y} fields.
+ * But despite their names, they don't need to be oriented toward {@linkplain 
AxisDirection#EAST East} and
+ * {@linkplain AxisDirection#NORTH North} respectively. The 
(<var>x</var>,<var>y</var>) axis can have any
+ * direction and should be understood as <cite>ordinate 0</cite> and 
<cite>ordinate 1</cite> values instead.
+ * This is not specific to this implementation; in Java2D too, the visual axis 
orientation depend
+ * on the {@linkplain java.awt.Graphics2D#getTransform() affine transform in 
the graphics context}.</p>
+ *
+ * {@section Spanning the anti-meridian of a Geographic CRS}
+ * The <cite>Web Coverage Service</cite> (WCS) specification authorizes (with 
special treatment)
+ * cases where <var>upper</var> &lt; <var>lower</var> at least in the 
longitude case. They are
+ * envelopes spanning the anti-meridian, like the red box below (the green box 
is the usual case).
+ * For {@code Envelope2D} objects, they are rectangle with negative 
{@linkplain #width width} or
+ * {@linkplain #height height} field values. The default implementation of 
methods listed in the
+ * right column can handle such cases.
+ *
+ * <center><table class="compact"><tr><td>
+ *   <img src="doc-files/AntiMeridian.png">
+ * </td><td>
+ * Supported methods:
+ * <ul>
+ *   <li>{@link #getMinimum(int)}</li>
+ *   <li>{@link #getMaximum(int)}</li>
+ *   <li>{@link #getSpan(int)}</li>
+ *   <li>{@link #getMedian(int)}</li>
+ *   <li>{@link #isEmpty()}</li>
+ *   <li>{@link #contains(double,double)}</li>
+ *   <li>{@link #contains(Rectangle2D)} and its variant receiving {@code 
double} arguments</li>
+ *   <li>{@link #intersects(Rectangle2D)} and its variant receiving {@code 
double} arguments</li>
+ *   <li>{@link #createIntersection(Rectangle2D)}</li>
+ *   <li>{@link #createUnion(Rectangle2D)}</li>
+ *   <li>{@link #add(Rectangle2D)}</li>
+ *   <li>{@link #add(double,double)}</li>
+ * </ul>
+ * </td></tr></table></center>
+ *
+ * The {@link #getMinX()}, {@link #getMinY()}, {@link #getMaxX()}, {@link 
#getMaxY()},
+ * {@link #getCenterX()}, {@link #getCenterY()}, {@link #getWidth()} and 
{@link #getHeight()}
+ * methods delegate to the above-cited methods.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @since   0.3 (derived from geotk-2.1)
+ * @version 0.3
+ * @module
+ *
+ * @see GeneralEnvelope
+ * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
+ */
+public class Envelope2D extends Rectangle2D.Double implements Envelope, 
Cloneable {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -3319231220761419350L;
+
+    /**
+     * The coordinate reference system, or {@code null}.
+     */
+    private CoordinateReferenceSystem crs;
+
+    /**
+     * Constructs an initially empty envelope with no CRS.
+     */
+    public Envelope2D() {
+    }
+
+    /**
+     * Creates a new envelope from the given bounding box. This constructor 
can not be public,
+     * because the {@code xmax} and {@code ymax} arguments are not the ones 
usually expected for
+     * {@link Rectangle2D} objects (the standard arguments are {@code width} 
and {@code height}).
+     * Making this constructor public would probably be a too high risk of 
confusion.
+     *
+     * <p>This constructor is needed because the other constructors (expecting 
envelopes or other
+     * rectangles) can not query directly the {@link Envelope#getSpan(int)} or 
equivalent methods,
+     * because the return value is not the one expected by this class when the 
envelope spans the
+     * anti-meridian.</p>
+     */
+    private Envelope2D(final double xmin, final double ymin, final double 
xmax, final double ymax) {
+        super(xmin, ymin, xmax - xmin, ymax - ymin);
+    }
+
+    /**
+     * Creates a new envelope from the given positions and CRS.
+     * It is the caller responsibility to check the validity of the given CRS.
+     *
+     * @see #Envelope2D(DirectPosition, DirectPosition)
+     */
+    private Envelope2D(final CoordinateReferenceSystem crs,
+                       final DirectPosition lowerCorner,
+                       final DirectPosition upperCorner)
+    {
+        /*
+         * JDK constraint: The call to ensureDimensionMatch(…) should have 
been first if Sun/Oracle
+         * fixed RFE #4093999 (Relax constraint on placement of this()/super() 
call in constructors).
+         */
+        this(lowerCorner.getOrdinate(0), lowerCorner.getOrdinate(1),
+             upperCorner.getOrdinate(0), upperCorner.getOrdinate(1));
+        AbstractDirectPosition.ensureDimensionMatch(crs, 2);
+        this.crs = crs;
+    }
+
+    /**
+     * Constructs a two-dimensional envelope defined by the specified 
coordinates.
+     * The {@code lowerCorner} and {@code upperCorner} arguments are not 
necessarily
+     * the minimal and maximal values respectively.
+     * See the class javadoc about anti-meridian spanning for more details.
+     *
+     * @param  lowerCorner The fist position.
+     * @param  upperCorner The second position.
+     * @throws MismatchedReferenceSystemException if the two positions don't 
use the same CRS.
+     * @throws MismatchedDimensionException If the two positions are not 
two-dimensional.
+     */
+    public Envelope2D(final DirectPosition lowerCorner, final DirectPosition 
upperCorner)
+            throws MismatchedReferenceSystemException, 
MismatchedDimensionException
+    {
+        // The call to getCommonCRS(…) performs a check against null values.
+        this(getCommonCRS(lowerCorner, upperCorner), lowerCorner, upperCorner);
+    }
+
+    /**
+     * Constructs a two-dimensional envelope defined by an other {@link 
Envelope}.
+     *
+     * @param  envelope The envelope to copy (can not be {@code null}).
+     * @throws MismatchedDimensionException If the given envelope is not 
two-dimensional.
+     */
+    public Envelope2D(final Envelope envelope) throws 
MismatchedDimensionException {
+        this(envelope.getCoordinateReferenceSystem(), 
envelope.getLowerCorner(), envelope.getUpperCorner());
+    }
+
+    /**
+     * Constructs a new envelope with the same data than the specified 
geographic bounding box.
+     * The coordinate reference system is set to {@code "CRS:84"}.
+     *
+     * @param box The bounding box to copy (can not be {@code null}).
+     */
+    public Envelope2D(final GeographicBoundingBox box) {
+        this(box.getWestBoundLongitude(),
+             box.getSouthBoundLatitude(),
+             box.getEastBoundLongitude(),
+             box.getNorthBoundLatitude());
+        try {
+            crs = CRS.forCode("CRS:84");
+        } catch (FactoryException e) {
+            // Should never happen since we asked for a CRS which should 
always be present.
+            throw new AssertionError(e);
+        }
+        if (Boolean.FALSE.equals(box.getInclusion())) {
+            x += width;
+            width = -width;
+            if (!isPoleToPole(y, y+height)) {
+                y += height;
+                height = -height;
+            }
+        }
+    }
+
+    /**
+     * Constructs two-dimensional envelope defined by an other {@link 
Rectangle2D}.
+     * If the given rectangle has negative width or height, they will be 
interpreted
+     * as an envelope spanning the anti-meridian.
+     *
+     * @param rect The rectangle to copy (can not be {@code null}).
+     * @param crs  The coordinate reference system, or {@code null}.
+     * @throws MismatchedDimensionException If the given CRS is not 
two-dimensional.
+     */
+    public Envelope2D(final Rectangle2D rect, final CoordinateReferenceSystem 
crs)
+            throws MismatchedDimensionException
+    {
+        super(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); // 
Really 'super', not 'this'.
+        AbstractDirectPosition.ensureDimensionMatch(crs, 2);
+        this.crs = crs;
+    }
+
+    /**
+     * Constructs two-dimensional envelope defined by the specified 
coordinates. Despite
+     * their name, the (<var>x</var>,<var>y</var>) coordinates don't need to 
be oriented
+     * toward ({@linkplain AxisDirection#EAST East}, {@linkplain 
AxisDirection#NORTH North}).
+     * Those parameter names simply match the {@linkplain #x x} and 
{@linkplain #y y} fields.
+     * The actual axis orientations are determined by the specified CRS.
+     * See the <a href="#skip-navbar_top">class javadoc</a> for details.
+     *
+     * @param  crs    The coordinate reference system, or {@code null}.
+     * @param  x      The <var>x</var> minimal value.
+     * @param  y      The <var>y</var> minimal value.
+     * @param  width  The envelope width. May be negative for envelope 
spanning the anti-meridian.
+     * @param  height The envelope height. May be negative for envelope 
spanning the anti-meridian.
+     * @throws MismatchedDimensionException If the given CRS is not 
two-dimensional.
+     */
+    public Envelope2D(final double x, final double y, final double width, 
final double height,
+            final CoordinateReferenceSystem crs) throws 
MismatchedDimensionException
+    {
+        super(x, y, width, height); // Really 'super', not 'this'.
+        AbstractDirectPosition.ensureDimensionMatch(crs, 2);
+        this.crs = crs;
+    }
+
+    /**
+     * Returns the coordinate reference system in which the coordinates are 
given.
+     *
+     * @return The coordinate reference system, or {@code null}.
+     */
+    @Override
+    public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return crs;
+    }
+
+    /**
+     * Sets the coordinate reference system in which the coordinate are given.
+     * This method <strong>does not</strong> reproject the envelope.
+     * If the envelope coordinates need to be transformed to the new CRS, 
consider using
+     * {@link Envelopes#transform(Envelope, CoordinateReferenceSystem)} 
instead.
+     *
+     * @param crs The new coordinate reference system, or {@code null}.
+     */
+    public void setCoordinateReferenceSystem(final CoordinateReferenceSystem 
crs) {
+        AbstractDirectPosition.ensureDimensionMatch(crs, 2);
+        this.crs = crs;
+    }
+
+    /**
+     * Returns the number of dimensions, which is always 2.
+     */
+    @Override
+    public final int getDimension() {
+        return 2;
+    }
+
+    /**
+     * A coordinate position consisting of all the starting ordinates for each
+     * dimension for all points within the {@code Envelope}.
+     *
+     * {@note The <cite>Web Coverage Service</cite> (WCS) 1.1 specification 
uses an extended
+     * interpretation of the bounding box definition. In a WCS 1.1 data 
structure, the lower
+     * corner defines the edges region in the directions of 
<em>decreasing</em> coordinate
+     * values in the envelope CRS. This is usually the algebraic minimum 
coordinates, but not
+     * always. For example, an envelope spanning the anti-meridian could have 
a lower corner
+     * longitude greater than the upper corner longitude. Such extended 
interpretation applies
+     * mostly to axes having <code>WRAPAROUND</code> range meaning.}
+     *
+     * @return The lower corner, typically (but not necessarily) containing 
minimal ordinate values.
+     */
+    @Override
+    public DirectPosition2D getLowerCorner() {
+        return new DirectPosition2D(x, y, crs);
+    }
+
+    /**
+     * A coordinate position consisting of all the ending ordinates for each
+     * dimension for all points within the {@code Envelope}.
+     *
+     * {@note The <cite>Web Coverage Service</cite> (WCS) 1.1 specification 
uses an extended
+     * interpretation of the bounding box definition. In a WCS 1.1 data 
structure, the upper
+     * corner defines the edges region in the directions of 
<em>increasing</em> coordinate
+     * values in the envelope CRS. This is usually the algebraic maximum 
coordinates, but not
+     * always. For example, an envelope spanning the anti-meridian could have 
an upper corner
+     * longitude less than the lower corner longitude. Such extended 
interpretation applies
+     * mostly to axes having <code>WRAPAROUND</code> range meaning.}
+     *
+     * @return The upper corner, typically (but not necessarily) containing 
maximal ordinate values.
+     */
+    @Override
+    public DirectPosition2D getUpperCorner() {
+        return new DirectPosition2D(x+width, y+height, crs);
+    }
+
+    /**
+     * Creates an exception for an index out of bounds.
+     */
+    private static IndexOutOfBoundsException indexOutOfBounds(final int 
dimension) {
+        return new 
IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, 
dimension));
+    }
+
+    /**
+     * Returns the minimal ordinate along the specified dimension. This method 
handles
+     * anti-meridian spanning as documented in the {@link 
AbstractEnvelope#getMinimum(int)}
+     * method.
+     *
+     * @param dimension The dimension to query.
+     * @return The minimal ordinate value along the given dimension.
+     * @throws IndexOutOfBoundsException If the given index is out of bounds.
+     */
+    @Override
+    public double getMinimum(final int dimension) throws 
IndexOutOfBoundsException {
+        final double value, span;
+        switch (dimension) {
+            case 0:  value=x; span=width;  break;
+            case 1:  value=y; span=height; break;
+            default: throw indexOutOfBounds(dimension);
+        }
+        if (isNegative(span)) { // Special handling for -0.0
+            final CoordinateSystemAxis axis = getAxis(crs, dimension);
+            return (axis != null) ? axis.getMinimumValue() : NEGATIVE_INFINITY;
+        }
+        return value;
+    }
+
+    /**
+     * Returns the maximal ordinate along the specified dimension. This method 
handles
+     * anti-meridian spanning as documented in the {@link 
AbstractEnvelope#getMaximum(int)}
+     * method.
+     *
+     * @param dimension The dimension to query.
+     * @return The maximal ordinate value along the given dimension.
+     * @throws IndexOutOfBoundsException If the given index is out of bounds.
+     */
+    @Override
+    public double getMaximum(final int dimension) throws 
IndexOutOfBoundsException {
+        final double value, span;
+        switch (dimension) {
+            case 0:  value=x; span=width;  break;
+            case 1:  value=y; span=height; break;
+            default: throw indexOutOfBounds(dimension);
+        }
+        if (isNegative(span)) { // Special handling for -0.0
+            final CoordinateSystemAxis axis = getAxis(crs, dimension);
+            return (axis != null) ? axis.getMaximumValue() : POSITIVE_INFINITY;
+        }
+        return value + span;
+    }
+
+    /**
+     * Returns the median ordinate along the specified dimension. This method 
handles
+     * anti-meridian spanning as documented in the {@link 
AbstractEnvelope#getMedian(int)}
+     * method.
+     *
+     * @param dimension The dimension to query.
+     * @return The mid ordinate value along the given dimension.
+     * @throws IndexOutOfBoundsException If the given index is out of bounds.
+     */
+    @Override
+    public double getMedian(final int dimension) throws 
IndexOutOfBoundsException {
+        double value, span;
+        switch (dimension) {
+            case 0:  value=x; span=width;  break;
+            case 1:  value=y; span=height; break;
+            default: throw indexOutOfBounds(dimension);
+        }
+        value += 0.5*span;
+        if (isNegative(span)) { // Special handling for -0.0
+            value = fixMedian(getAxis(crs, dimension), value);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the envelope span along the specified dimension. This method 
handles anti-meridian
+     * spanning as documented in the {@link AbstractEnvelope#getSpan(int)} 
method.
+     *
+     * @param  dimension The dimension to query.
+     * @return The rectangle width or height, depending the given dimension.
+     * @throws IndexOutOfBoundsException If the given index is out of bounds.
+      */
+    @Override
+    public double getSpan(final int dimension) throws 
IndexOutOfBoundsException {
+        double span;
+        switch (dimension) {
+            case 0:  span=width;  break;
+            case 1:  span=height; break;
+            default: throw indexOutOfBounds(dimension);
+        }
+        if (isNegative(span)) { // Special handling for -0.0
+            span = fixSpan(getAxis(crs, dimension), span);
+        }
+        return span;
+    }
+
+    // Do not override getX() and getY() - their default implementations is 
okay.
+
+    /**
+     * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for 
dimension 0.
+     */
+    @Override
+    public double getMinX() {
+        return getMinimum(0);
+    }
+
+    /**
+     * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for 
dimension 1.
+     */
+    @Override
+    public double getMinY() {
+        return getMinimum(1);
+    }
+
+    /**
+     * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for 
dimension 0.
+     */
+    @Override
+    public double getMaxX() {
+        return getMaximum(0);
+    }
+
+    /**
+     * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for 
dimension 1.
+     */
+    @Override
+    public double getMaxY() {
+        return getMaximum(1);
+    }
+
+    /**
+     * Returns the {@linkplain #getMedian(int) median} ordinate value for 
dimension 0.
+     */
+    @Override
+    public double getCenterX() {
+        return getMedian(0);
+    }
+
+    /**
+     * Returns the {@linkplain #getMedian(int) median} ordinate value for 
dimension 1.
+     */
+    @Override
+    public double getCenterY() {
+        return getMedian(1);
+    }
+
+    /**
+     * Returns the {@linkplain #getSpan(int) span} for dimension 0.
+     */
+    @Override
+    public double getWidth() {
+        return getSpan(0);
+    }
+
+    /**
+     * Returns the {@linkplain #getSpan(int) span} for dimension 1.
+     */
+    @Override
+    public double getHeight() {
+        return getSpan(1);
+    }
+
+    /**
+     * Determines whether the envelope is empty. A negative {@linkplain 
#width} or
+     * (@linkplain #height} is considered as a non-empty area if the 
corresponding
+     * axis has the {@linkplain 
org.opengis.referencing.cs.RangeMeaning#WRAPAROUND
+     * wraparound} range meaning.
+     * <p>
+     * Note that if the {@linkplain #width} or {@linkplain #height} value is
+     * {@link java.lang.Double#NaN NaN}, then the envelope is considered empty.
+     * This is different than the default {@link 
java.awt.geom.Rectangle2D.Double#isEmpty()}
+     * implementation, which doesn't check for {@code NaN} values.
+     *
+     * @since 3.20
+     */
+    @Override
+    public boolean isEmpty() {
+        return !((width  > 0 || (isNegative(width)  && isWrapAround(crs, 0)))
+              && (height > 0 || (isNegative(height) && isWrapAround(crs, 1))));
+    }
+
+    /**
+     * Tests if a specified coordinate is inside the boundary of this 
envelope. If it least one
+     * of the given ordinate value is {@link java.lang.Double#NaN NaN}, then 
this method returns
+     * {@code false}.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link AbstractEnvelope#contains(DirectPosition)}.
+     *
+     * @param  px The first ordinate value of the point to text.
+     * @param  py The second ordinate value of the point to text.
+     * @return {@code true} if the specified coordinate is inside the boundary
+     *         of this envelope; {@code false} otherwise.
+     *
+     * @since 3.20
+     */
+    @Override
+    public boolean contains(final double px, final double py) {
+        boolean c1 = (px >= x);
+        boolean c2 = (px <= x + width);
+        // See AbstractEnvelope.contains(DirectPosition) for explanation.
+        if ((c1 & c2) || ((c1 | c2) && isNegative(width))) {
+            // Same check, but for y axis.
+            c1 = (py >= y);
+            c2 = (py <= y + height);
+            return (c1 & c2) || ((c1 | c2) && isNegative(height));
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if this envelope completely encloses the specified 
rectangle. If this
+     * envelope or the given rectangle have at least one {@link 
java.lang.Double#NaN NaN} value,
+     * then this method returns {@code false}.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link AbstractEnvelope#contains(Envelope, boolean)}.
+     *
+     * @param  rect The rectangle to test for inclusion.
+     * @return {@code true} if this envelope completely encloses the specified 
rectangle.
+     *
+     * @since 3.20
+     */
+    @Override
+    public boolean contains(final Rectangle2D rect) {
+        if (rect instanceof Envelope2D) {
+            // Need to bypass the overriden getWidth()/getHeight().
+            final Envelope2D env = (Envelope2D) rect;
+            return contains(env.x, env.y, env.width, env.height);
+        }
+        return super.contains(rect);
+    }
+
+    /**
+     * Returns {@code true} if this envelope completely encloses the specified 
rectangle. If this
+     * envelope or the given rectangle have at least one {@link 
java.lang.Double#NaN NaN} value,
+     * then this method returns {@code false}.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link AbstractEnvelope#contains(Envelope, boolean)}.
+     *
+     * @param  rx The <var>x</var> ordinate of the lower corner of the 
rectangle to test for inclusion.
+     * @param  ry The <var>y</var> ordinate of the lower corner of the 
rectangle to test for inclusion.
+     * @param  rw The width of the rectangle to test for inclusion. May be 
negative if the rectangle spans the anti-meridian.
+     * @param  rh The height of the rectangle to test for inclusion. May be 
negative.
+     * @return {@code true} if this envelope completely encloses the specified 
one.
+     *
+     * @since 3.20
+     */
+    @Override
+    public boolean contains(final double rx, final double ry, final double rw, 
final double rh) {
+        for (int i=0; i!=2; i++) {
+            final double min0, min1, span0, span1;
+            if (i == 0) {
+                min0 =  x;  span0 = width;
+                min1 = rx;  span1 = rw;
+            } else {
+                min0 =  y;  span0 = height;
+                min1 = ry;  span1 = rh;
+            }
+            // See AbstractEnvelope.contains(Envelope) for an
+            // illustration of the algorithm applied here.
+            final boolean minCondition = (min1 >= min0);
+            final boolean maxCondition = (min1 + span1 <= min0 + span0);
+            if (minCondition & maxCondition) {
+                if (!isNegativeUnsafe(span1) || isNegativeUnsafe(span0)) {
+                    continue;
+                }
+                if (span0 >= 
AbstractEnvelope.getSpan(getAxis(getCoordinateReferenceSystem(), i))) {
+                    continue;
+                }
+            } else if (minCondition != maxCondition) {
+                if (isNegative(span0) && isPositive(span1)) {
+                    continue;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns {@code true} if this envelope intersects the specified 
envelope. If this envelope
+     * or the given rectangle have at least one {@link java.lang.Double#NaN 
NaN} value, then this
+     * method returns {@code false}.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link AbstractEnvelope#intersects(Envelope, boolean)}.
+     *
+     * @param  rect The rectangle to test for intersection.
+     * @return {@code true} if this envelope intersects the specified 
rectangle.
+     *
+     * @since 3.20
+     */
+    @Override
+    public boolean intersects(final Rectangle2D rect) {
+        if (rect instanceof Envelope2D) {
+            // Need to bypass the overriden getWidth()/getHeight().
+            final Envelope2D env = (Envelope2D) rect;
+            return intersects(env.x, env.y, env.width, env.height);
+        }
+        return super.contains(rect);
+    }
+
+    /**
+     * Returns {@code true} if this envelope intersects the specified 
envelope. If this envelope
+     * or the given rectangle have at least one {@link java.lang.Double#NaN 
NaN} value, then this
+     * method returns {@code false}.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link AbstractEnvelope#intersects(Envelope, boolean)}.
+     *
+     * @param  rx The <var>x</var> ordinate of the lower corner of the 
rectangle to test for intersection.
+     * @param  ry The <var>y</var> ordinate of the lower corner of the 
rectangle to test for intersection.
+     * @param  rw The width of the rectangle to test for inclusion. May be 
negative if the rectangle spans the anti-meridian.
+     * @param  rh The height of the rectangle to test for inclusion. May be 
negative.
+     * @return {@code true} if this envelope intersects the specified 
rectangle.
+     */
+    @Override
+    public boolean intersects(final double rx, final double ry, final double 
rw, final double rh) {
+        for (int i=0; i!=2; i++) {
+            final double min0, min1, span0, span1;
+            if (i == 0) {
+                min0 =  x;  span0 = width;
+                min1 = rx;  span1 = rw;
+            } else {
+                min0 =  y;  span0 = height;
+                min1 = ry;  span1 = rh;
+            }
+            // See AbstractEnvelope.intersects(Envelope) for an
+            // illustration of the algorithm applied here.
+            final boolean minCondition = (min1 <= min0 + span0);
+            final boolean maxCondition = (min1 + span1 >= min0);
+            if (maxCondition & minCondition) {
+                continue;
+            }
+            final boolean sp0 = isNegative(span0);
+            final boolean sp1 = isNegative(span1);
+            if (sp0 | sp1) {
+                if ((sp0 & sp1) | (maxCondition | minCondition)) {
+                    continue;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the intersection of this envelope with the specified rectangle. 
If this envelope
+     * or the given rectangle have at least one {@link java.lang.Double#NaN 
NaN} values, then this
+     * method returns an {@linkplain #isEmpty() empty} envelope.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link GeneralEnvelope#intersect(Envelope)}.
+     *
+     * @param rect The rectangle to be intersected with this envelope.
+     * @return The intersection of the given rectangle with this envelope.
+     */
+    @Override
+    public Envelope2D createIntersection(final Rectangle2D rect) {
+        final Envelope2D env = (rect instanceof Envelope2D) ? (Envelope2D) 
rect : null;
+        final Envelope2D inter = new Envelope2D(NaN, NaN, NaN, NaN, crs);
+        for (int i=0; i!=2; i++) {
+            final double min0, min1, span0, span1;
+            if (i == 0) {
+                min0  = x;
+                span0 = width;
+                min1  = rect.getX();
+                span1 = (env != null) ? env.width : rect.getWidth();
+            } else {
+                min0  = y;
+                span0 = height;
+                min1  = rect.getY();
+                span1 = (env != null) ? env.height : rect.getHeight();
+            }
+            final double max0 = min0 + span0;
+            final double max1 = min1 + span1;
+            double min = Math.max(min0, min1);
+            double max = Math.min(max0, max1);
+            /*
+             * See GeneralEnvelope.intersect(Envelope) for an explanation of 
the algorithm applied
+             * below.
+             */
+            if (isSameSign(span0, span1)) { // Always 'false' if any value is 
NaN.
+                if ((min1 > max0 || max1 < min0) && !isNegativeUnsafe(span0)) {
+                    continue; // No intersection: leave ordinate values to NaN
+                }
+            } else if (isNaN(span0) || isNaN(span1)) {
+                continue; // Leave ordinate values to NaN
+            } else {
+                int intersect = 0; // A bitmask of intersections (two bits).
+                if (isNegativeUnsafe(span0)) {
+                    if (min1 <= max0) {min = min1; intersect  = 1;}
+                    if (max1 >= min0) {max = max1; intersect |= 2;}
+                } else {
+                    if (min0 <= max1) {min = min0; intersect  = 1;}
+                    if (max0 >= min1) {max = max0; intersect |= 2;}
+                }
+                if (intersect == 0 || intersect == 3) {
+                    final double csSpan = 
AbstractEnvelope.getSpan(getAxis(crs, i));
+                    if (span1 >= csSpan) {
+                        min = min0;
+                        max = max0;
+                    } else if (span0 >= csSpan) {
+                        min = min1;
+                        max = max1;
+                    } else {
+                        continue; // Leave ordinate values to NaN
+                    }
+                }
+            }
+            inter.setRange(i, min, max);
+        }
+        assert inter.isEmpty() || (contains(inter) && rect.contains(inter)) : 
inter;
+        return inter;
+    }
+
+    /**
+     * Returns the union of this envelope with the specified rectangle.
+     * The default implementation clones this envelope, then delegates
+     * to {@link #add(Rectangle2D)}.
+     *
+     * @param rect The rectangle to add to this envelope.
+     * @return The union of the given rectangle with this envelope.
+     */
+    @Override
+    public Envelope2D createUnion(final Rectangle2D rect) {
+        final Envelope2D union = (Envelope2D) clone();
+        union.add(rect);
+        assert union.isEmpty() || (union.contains(this) && 
union.contains(rect)) : union;
+        return union;
+    }
+
+    /**
+     * Adds an other rectangle to this rectangle. The resulting rectangle is 
the union of the
+     * two {@code Rectangle} objects.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link GeneralEnvelope#add(Envelope)}, except if the result is a 
rectangle expanding to
+     * infinities. In the later case, the field values are set to {@code NaN} 
because infinite
+     * values are a little bit problematic in {@link Rectangle2D} objects.
+     *
+     * @param rect The rectangle to add to this envelope.
+     */
+    @Override
+    public void add(final Rectangle2D rect) {
+        final Envelope2D env = (rect instanceof Envelope2D) ? (Envelope2D) 
rect : null;
+        for (int i=0; i!=2; i++) {
+            final double min0, min1, span0, span1;
+            if (i == 0) {
+                min0  = x;
+                span0 = width;
+                min1  = rect.getX();
+                span1 = (env != null) ? env.width : rect.getWidth();
+                x = width = NaN;
+            } else {
+                min0  = y;
+                span0 = height;
+                min1  = rect.getY();
+                span1 = (env != null) ? env.height : rect.getHeight();
+                y = height = NaN;
+            }
+            final double max0 = min0 + span0;
+            final double max1 = min1 + span1;
+            double min = Math.min(min0, min1);
+            double max = Math.max(max0, max1);
+            /*
+             * See GeneralEnvelope.add(Envelope) for an explanation of the 
algorithm applied below.
+             * Note that the "continue" statement has reverse meaning: 
ordinates are left to NaN.
+             */
+            final boolean sp0 = isNegative(span0);
+            final boolean sp1 = isNegative(span1);
+            if (sp0 == sp1) {
+                if (sp0 && !isNegativeUnsafe(max - min)) {
+                    continue; // Leave ordinates to NaN.
+                }
+            } else if (sp0) {
+                if (max1 <= max0 || min1 >= min0) {
+                    min = min0;
+                    max = max0;
+                } else {
+                    final double left  = min1 - max0;
+                    final double right = min0 - max1;
+                    if (!(left > 0 || right > 0)) {
+                        continue; // Leave ordinates to NaN.
+                    }
+                    if (left > right) {min = min1; max = max0;}
+                    if (right > left) {min = min0; max = max1;}
+                }
+            } else {
+                if (max0 <= max1 || min0 >= min1) {
+                    min = min1;
+                    max = max1;
+                } else {
+                    final double left  = min0 - max1;
+                    final double right = min1 - max0;
+                    if (!(left > 0 || right > 0)) {
+                        continue; // Leave ordinates to NaN.
+                    }
+                    if (left > right) {min = min0; max = max1;}
+                    if (right > left) {min = min1; max = max0;}
+                }
+            }
+            setRange(i, min, max);
+        }
+    }
+
+    /**
+     * Sets the envelope range along the specified dimension.
+     *
+     * @param  dimension The dimension to set.
+     * @param  minimum   The minimum value along the specified dimension.
+     * @param  maximum   The maximum value along the specified dimension.
+     * @throws IndexOutOfBoundsException If the given index is out of bounds.
+     */
+    private void setRange(final int dimension, final double minimum, final 
double maximum)
+            throws IndexOutOfBoundsException
+    {
+        final double span = maximum - minimum;
+        switch (dimension) {
+            case 0: x = minimum; width  = span; break;
+            case 1: y = minimum; height = span; break;
+            default: throw indexOutOfBounds(dimension);
+        }
+    }
+
+    /**
+     * Adds a point to this rectangle. The resulting rectangle is the smallest 
rectangle that
+     * contains both the original rectangle and the specified point.
+     * <p>
+     * After adding a point, a call to {@link #contains(double, double)} with 
the added point
+     * as an argument will return {@code true}, except if one of the point 
ordinates was
+     * {@link Double#NaN} in which case the corresponding ordinate has been 
ignored.
+     *
+     * {@section Spanning the anti-meridian of a Geographic CRS}
+     * This method supports anti-meridian spanning in the same way than
+     * {@link GeneralEnvelope#add(DirectPosition)}.
+     *
+     * @param px The first ordinate of the point to add.
+     * @param py The second ordinate of the point to add.
+     */
+    @Override
+    public void add(final double px, final double py) {
+        double off = px - x;
+        if (!isNegative(width)) { // Standard case, or NaN.
+            if (off < 0) {x=px; width -= off;}
+            if (off > width)   {width  = off;}
+        } else if (off < 0) {
+            final double r = width - off;
+            if (r < 0) {
+                if (r > off) width  = off;
+                else {x=px;  width -= off;}
+            }
+        }
+        off = py - y;
+        if (!isNegative(height)) {
+            if (off < 0) {y=py; height -= off;}
+            if (off > height)  {height  = off;}
+        } else if (off < 0) {
+            final double r = height - off;
+            if (r < 0) {
+                if (r > off) height  = off;
+                else {y=py;  height -= off;}
+            }
+        }
+        assert contains(px, py) || isEmpty() || isNaN(px) || isNaN(py);
+    }
+
+    /**
+     * Compares the specified object with this envelope for equality. If the 
given object is not
+     * an instance of {@code Envelope2D}, then the two objects are compared as 
plain rectangles,
+     * i.e. the {@linkplain #getCoordinateReferenceSystem() coordinate 
reference system} of this
+     * envelope is ignored.
+     *
+     * {@section Note on <code>hashCode()</code>}
+     * This class does not override the {@link #hashCode()} method for 
consistency with the
+     * {@link Rectangle2D#equals(Object)} method, which compare arbitrary 
{@code Rectangle2D}
+     * implementations.
+     *
+     * @param object The object to compare with this envelope.
+     * @return {@code true} if the given object is equal to this envelope.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object instanceof Envelope2D) {
+            final Envelope2D other = (Envelope2D) object;
+            return doubleToLongBits(x)      == doubleToLongBits(other.x)      
&&
+                   doubleToLongBits(y)      == doubleToLongBits(other.y)      
&&
+                   doubleToLongBits(width)  == doubleToLongBits(other.width)  
&&
+                   doubleToLongBits(height) == doubleToLongBits(other.height) 
&&
+                   Objects.equals(crs, other.crs);
+        } else {
+            return super.equals(object);
+        }
+    }
+
+    /**
+     * Returns {@code true} if {@code this} envelope bounds is equal to {@code 
that} envelope
+     * bounds in two specified dimensions. The coordinate reference system is 
not compared, since
+     * it doesn't need to have the same number of dimensions.
+     *
+     * @param that The envelope to compare to.
+     * @param xDim The dimension of {@code that} envelope to compare to the 
<var>x</var> dimension
+     *             of {@code this} envelope.
+     * @param yDim The dimension of {@code that} envelope to compare to the 
<var>y</var> dimension
+     *             of {@code this} envelope.
+     * @param eps  A small tolerance number for floating point number 
comparisons. This value will
+     *             be scaled according this envelope {@linkplain #width width} 
and
+     *             {@linkplain #height height}.
+     * @return {@code true} if the envelope bounds are the same (up to the 
specified tolerance
+     *         level) in the specified dimensions, or {@code false} otherwise.
+     */
+    public boolean boundsEquals(final Envelope that, final int xDim, final int 
yDim, double eps) {
+        eps *= 0.5*(width + height);
+        for (int i=0; i<4; i++) {
+            final int dim2D = (i & 1);
+            final int dimND = (dim2D == 0) ? xDim : yDim;
+            final double value2D, valueND;
+            if ((i & 2) == 0) {
+                value2D = this.getMinimum(dim2D);
+                valueND = that.getMinimum(dimND);
+            } else {
+                value2D = this.getMaximum(dim2D);
+                valueND = that.getMaximum(dimND);
+            }
+            // Use '!' for catching NaN values.
+            if (!(Math.abs(value2D - valueND) <= eps)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Formats this envelope in the <cite>Well Known Text</cite> (WKT) format.
+     * The output is of the form "{@code BOX2D(}{@linkplain #getLowerCorner()
+     * lower corner}{@code ,}{@linkplain #getUpperCorner() upper corner}{@code 
)}".
+     * Example:
+     *
+     * {@preformat wkt
+     *   BOX3D(-90 -180, 90 180)
+     * }
+     *
+     * @see Envelopes#toWKT(Envelope)
+     */
+    @Override
+    public String toString() {
+        return AbstractEnvelope.toString(this);
+    }
+}

Propchange: 
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK7/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain


Reply via email to