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> < <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