| /* |
| * 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.apache.sis.referencing.CommonCRS; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.Emptiable; |
| |
| import static java.lang.Double.NaN; |
| import static java.lang.Double.isNaN; |
| import static java.lang.Double.doubleToLongBits; |
| import static org.apache.sis.math.MathFunctions.isSameSign; |
| import static org.apache.sis.math.MathFunctions.isPositive; |
| import static org.apache.sis.math.MathFunctions.isNegative; |
| import static org.apache.sis.math.MathFunctions.isNegativeZero; |
| import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches; |
| import static org.apache.sis.internal.referencing.Formulas.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 Java2D rectangle. |
| * 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>coordinate 0</cite> and <cite>coordinate 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> |
| * |
| * <h2>Spanning the anti-meridian of a Geographic CRS</h2> |
| * 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. |
| * |
| * <div class="horizontal-flow"> |
| * <div> |
| * <img style="vertical-align: middle" src="doc-files/AntiMeridian.png" alt="Envelope spannning the anti-meridian"> |
| * </div><div> |
| * 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 #toRectangles()}</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> |
| * </div></div> |
| * |
| * 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) |
| * @version 0.8 |
| * |
| * @see GeneralEnvelope |
| * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public class Envelope2D extends Rectangle2D.Double implements Envelope, Emptiable, Cloneable { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 761232175464415062L; |
| |
| /** |
| * The number of dimensions in every {@code Envelope2D}. |
| */ |
| private static final int DIMENSION = 2; |
| |
| /** |
| * An empty array of Java2D rectangles, to be returned by {@link #toRectangles()} |
| * when en envelope is empty. |
| */ |
| private static final Rectangle2D.Double[] EMPTY = new Rectangle2D.Double[0]; |
| |
| /** |
| * 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)); |
| ensureDimensionMatches("crs", DIMENSION, crs); |
| 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 the |
| * {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}. |
| * Axis order is (<var>longitude</var>, <var>latitude</var>). |
| * |
| * @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()); |
| crs = CommonCRS.defaultGeographic(); |
| 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 crs the coordinate reference system, or {@code null}. |
| * @param rect the rectangle to copy (can not be {@code null}). |
| * @throws MismatchedDimensionException if the given CRS is not two-dimensional. |
| */ |
| public Envelope2D(final CoordinateReferenceSystem crs, final Rectangle2D rect) |
| throws MismatchedDimensionException |
| { |
| super(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); // Really 'super', not 'this'. |
| ensureDimensionMatches("crs", DIMENSION, crs); |
| 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 CoordinateReferenceSystem crs, final double x, final double y, |
| final double width, final double height) throws MismatchedDimensionException |
| { |
| super(x, y, width, height); // Really 'super', not 'this'. |
| ensureDimensionMatches("crs", DIMENSION, crs); |
| 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) { |
| ensureDimensionMatches("crs", DIMENSION, crs); |
| this.crs = crs; |
| } |
| |
| /** |
| * Sets this envelope to the given rectangle. If the given rectangle is also an instance of {@link Envelope} |
| * (typically as another {@code Envelope2D}) and has a non-null Coordinate Reference System (CRS), then the |
| * CRS of this envelope will be set to the CRS of the given envelope. |
| * |
| * @param rect the rectangle to copy coordinates from. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| public void setRect(final Rectangle2D rect) { |
| if (rect == this) { |
| return; // Optimization for methods chaining like env.setRect(Shapes.transform(…, env)) |
| } |
| if (rect instanceof Envelope) { |
| final CoordinateReferenceSystem envelopeCRS = ((Envelope) rect).getCoordinateReferenceSystem(); |
| if (envelopeCRS != null) { |
| setCoordinateReferenceSystem(envelopeCRS); |
| } |
| } |
| super.setRect(rect); |
| } |
| |
| /** |
| * Returns the number of dimensions, which is always 2. |
| * |
| * @return always 2 for bi-dimensional objects. |
| */ |
| @Override |
| public final int getDimension() { |
| return DIMENSION; |
| } |
| |
| /** |
| * The limits in the direction of decreasing coordinate values for the two dimensions. |
| * This is typically a coordinate position consisting of the minimal coordinates for |
| * the two dimensions for all points within the {@code Envelope}. |
| * |
| * <p>The object returned by this method is a copy. Change in the returned position |
| * will not affect this envelope, and conversely.</p> |
| * |
| * <div class="note"><b>Note:</b> |
| * 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} range meaning.</div> |
| * |
| * @return a copy of the lower corner, typically (but not necessarily) containing minimal coordinate values. |
| */ |
| @Override |
| public DirectPosition2D getLowerCorner() { |
| return new DirectPosition2D(crs, x, y); |
| } |
| |
| /** |
| * The limits in the direction of increasing coordinate values for the two dimensions. |
| * This is typically a coordinate position consisting of the maximal coordinates for |
| * the two dimensions for all points within the {@code Envelope}. |
| * |
| * <p>The object returned by this method is a copy. Change in the returned position |
| * will not affect this envelope, and conversely.</p> |
| * |
| * <div class="note"><b>Note:</b> |
| * 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} range meaning.</div> |
| * |
| * @return a copy of the upper corner, typically (but not necessarily) containing maximal coordinate values. |
| */ |
| @Override |
| public DirectPosition2D getUpperCorner() { |
| return new DirectPosition2D(crs, x+width, y+height); |
| } |
| |
| /** |
| * 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 coordinate 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 coordinate 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 isWrapAround(axis) ? axis.getMinimumValue() : NaN; |
| } |
| return value; |
| } |
| |
| /** |
| * Returns the maximal coordinate 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 coordinate 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 isWrapAround(axis) ? axis.getMaximumValue() : NaN; |
| } |
| return value + span; |
| } |
| |
| /** |
| * Returns the median coordinate 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 coordinate 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} coordinate value for dimension 0. |
| * The default implementation invokes <code>{@linkplain #getMinimum(int) getMinimum}(0)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the minimal coordinate value for dimension 0. |
| */ |
| @Override |
| public double getMinX() { |
| return getMinimum(0); |
| } |
| |
| /** |
| * Returns the {@linkplain #getMinimum(int) minimal} coordinate value for dimension 1. |
| * The default implementation invokes <code>{@linkplain #getMinimum(int) getMinimum}(1)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the minimal coordinate value for dimension 1. |
| */ |
| @Override |
| public double getMinY() { |
| return getMinimum(1); |
| } |
| |
| /** |
| * Returns the {@linkplain #getMaximum(int) maximal} coordinate value for dimension 0. |
| * The default implementation invokes <code>{@linkplain #getMaximum(int) getMinimum}(0)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x} + {@linkplain #width width}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the maximal coordinate value for dimension 0. |
| */ |
| @Override |
| public double getMaxX() { |
| return getMaximum(0); |
| } |
| |
| /** |
| * Returns the {@linkplain #getMaximum(int) maximal} coordinate value for dimension 1. |
| * The default implementation invokes <code>{@linkplain #getMaximum(int) getMinimum}(1)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y} + {@linkplain #height height}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the maximal coordinate value for dimension 1. |
| */ |
| @Override |
| public double getMaxY() { |
| return getMaximum(1); |
| } |
| |
| /** |
| * Returns the {@linkplain #getMedian(int) median} coordinate value for dimension 0. |
| * The default implementation invokes <code>{@linkplain #getMedian(int) getMedian}(0)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x} + {@linkplain #width width}/2) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the median coordinate value for dimension 0. |
| */ |
| @Override |
| public double getCenterX() { |
| return getMedian(0); |
| } |
| |
| /** |
| * Returns the {@linkplain #getMedian(int) median} coordinate value for dimension 1. |
| * The default implementation invokes <code>{@linkplain #getMedian(int) getMedian}(1)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y} + {@linkplain #height height}/2) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the median coordinate value for dimension 1. |
| */ |
| @Override |
| public double getCenterY() { |
| return getMedian(1); |
| } |
| |
| /** |
| * Returns the {@linkplain #getSpan(int) span} for dimension 0. |
| * The default implementation invokes <code>{@linkplain #getSpan(int) getSpan}(0)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #width width}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the span for dimension 0. |
| */ |
| @Override |
| public double getWidth() { |
| return getSpan(0); |
| } |
| |
| /** |
| * Returns the {@linkplain #getSpan(int) span} for dimension 1. |
| * The default implementation invokes <code>{@linkplain #getSpan(int) getSpan}(1)</code>. |
| * The result is the standard {@link Rectangle2D} value (namely {@linkplain #height height}) |
| * only if the envelope is not spanning the anti-meridian. |
| * |
| * @return the 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.</p> |
| * |
| * @return {@code true} if this envelope is empty. |
| */ |
| @Override |
| public boolean isEmpty() { |
| return !((width > 0 || (isNegative(width) && isWrapAround(crs, 0))) |
| && (height > 0 || (isNegative(height) && isWrapAround(crs, 1)))); |
| } |
| |
| /** |
| * Returns this envelope as non-empty Java2D rectangle objects. This method returns an array of length 0, 1, |
| * 2 or 4 depending on whether the envelope crosses the anti-meridian or the limit of any other axis having |
| * {@linkplain org.opengis.referencing.cs.RangeMeaning#WRAPAROUND wraparound} range meaning. |
| * More specifically: |
| * |
| * <ul> |
| * <li>If this envelope {@linkplain #isEmpty() is empty}, then this method returns an empty array.</li> |
| * <li>If this envelope does not have any wraparound behavior, then this method returns a copy |
| * of this envelope as an instance of {@code Rectangle2D.Double} in an array of length 1.</li> |
| * <li>If this envelope crosses the <cite>anti-meridian</cite> (a.k.a. <cite>date line</cite>) |
| * then this method represents this envelope as two separated rectangles. |
| * <li>While uncommon, the envelope could theoretically crosses the limit of other axis having |
| * wraparound range meaning. If wraparound occur along the two axes, then this method |
| * represents this envelope as four separated rectangles. |
| * </ul> |
| * |
| * <div class="note"><b>API note:</b> |
| * The return type is the {@code Rectangle2D.Double} implementation class rather than the {@code Rectangle2D} |
| * abstract class because the {@code Envelope2D} class hierarchy already exposes this implementation choice.</div> |
| * |
| * @return a representation of this envelope as an array of non-empty Java2D rectangles. |
| * The array never contains {@code this}. |
| * |
| * @see GeneralEnvelope#toSimpleEnvelopes() |
| * |
| * @since 0.4 |
| */ |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| public Rectangle2D.Double[] toRectangles() { |
| int isWrapAround = 0; // A bitmask of the dimensions having a "wrap around" behavior. |
| for (int i=0; i!=DIMENSION; i++) { |
| final double span = (i == 0) ? width : height; |
| if (!(span > 0)) { // Use '!' for catching NaN. |
| if (!isNegative(span) || !isWrapAround(crs, i)) { |
| return EMPTY; |
| } |
| isWrapAround |= (1 << i); |
| } |
| } |
| /* |
| * The number of rectangles is 2ⁿ where n is the number of wraparound found. |
| */ |
| final Rectangle2D.Double[] rect = new Rectangle2D.Double[1 << Integer.bitCount(isWrapAround)]; |
| for (int i=0; i<rect.length; i++) { |
| rect[i] = new Rectangle2D.Double(x, y, width, height); |
| } |
| if ((isWrapAround & 1) != 0) { |
| /* |
| * (x+width) (x) |
| * ↓ ↓ |
| * ──────┐ ┌─────── |
| * …next │ │ start… |
| * ──────┘ └─────── |
| */ |
| final CoordinateSystemAxis axis = getAxis(crs, 0); |
| final Rectangle2D.Double start = rect[0]; |
| final Rectangle2D.Double next = rect[1]; |
| start.width = axis.getMaximumValue() - x; |
| next.x = axis.getMinimumValue(); |
| next.width += x - next.x; |
| } |
| if ((isWrapAround & 2) != 0) { |
| /* |
| * │ ⋮ │ |
| * │ start │ |
| * (y) → └───────┘ |
| * (y+height) → ┌───────┐ |
| * │ next │ |
| * │ ⋮ │ |
| */ |
| final CoordinateSystemAxis axis = getAxis(crs, 1); |
| final Rectangle2D.Double start = rect[0]; |
| final Rectangle2D.Double next = rect[isWrapAround - 1]; // == 1 if y is the only wraparound axis, or 2 otherwise. |
| start.height = axis.getMaximumValue() - y; |
| next.y = axis.getMinimumValue(); |
| next.height += y - next.y; |
| } |
| if (isWrapAround == 3) { |
| /* |
| * If there is a wraparound along both axes, copy the values. |
| * The (x) and (y) labels indicate which values to copy. |
| * |
| * (y) R1 │ │ R0 |
| * ─────────┘ └───────── |
| * ─────────┐ ┌───────── |
| * (x,y) R3 │ │ R2 (x) |
| */ |
| rect[1].height = rect[0].height; |
| rect[2].width = rect[0].width; |
| rect[3].x = rect[1].x; |
| rect[3].width = rect[1].width; |
| rect[3].y = rect[2].y; |
| rect[3].height = rect[2].height; |
| } |
| return rect; |
| } |
| |
| /** |
| * Tests if a specified coordinate is inside the boundary of this envelope. If it least one |
| * of the given coordinate value is {@link java.lang.Double#NaN NaN}, then this method returns |
| * {@code false}. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link AbstractEnvelope#contains(DirectPosition)}. |
| * |
| * @param px the first coordinate value of the point to text. |
| * @param py the second coordinate value of the point to text. |
| * @return {@code true} if the specified coordinate is inside the boundary of this envelope; |
| * {@code false} otherwise. |
| */ |
| @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}. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link AbstractEnvelope#contains(Envelope)}. |
| * |
| * @param rect the rectangle to test for inclusion. |
| * @return {@code true} if this envelope completely encloses the specified rectangle. |
| */ |
| @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}. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link AbstractEnvelope#contains(Envelope)}. |
| * |
| * @param rx the <var>x</var> coordinate of the lower corner of the rectangle to test for inclusion. |
| * @param ry the <var>y</var> coordinate 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. |
| */ |
| @Override |
| public boolean contains(final double rx, final double ry, final double rw, final double rh) { |
| for (int i=0; i!=DIMENSION; 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(crs, i))) { |
| continue; |
| } |
| } else if (minCondition != maxCondition) { |
| if (isNegative(span0) && isPositive(span1)) { |
| continue; |
| } |
| } else if (isNegativeZero(span0)) { |
| 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}. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link AbstractEnvelope#intersects(Envelope)}. |
| * |
| * @param rect the rectangle to test for intersection. |
| * @return {@code true} if this envelope intersects the specified rectangle. |
| */ |
| @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.intersects(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}. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link AbstractEnvelope#intersects(Envelope)}. |
| * |
| * @param rx the <var>x</var> coordinate of the lower corner of the rectangle to test for intersection. |
| * @param ry the <var>y</var> coordinate 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!=DIMENSION; 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. |
| * We use < operator, not <=, for consistency with the standard "intersects" definition. |
| */ |
| 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. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * 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(crs, NaN, NaN, NaN, NaN); |
| for (int i=0; i!=DIMENSION; 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(); |
| } |
| /* |
| * The purpose for (min != 0) test before addition is to preserve the sign of zero. |
| * In the [0 … -0] range, the span is -0. But computing max = 0 + -0 result in +0, |
| * while we need max = -0 in this case. |
| */ |
| final double max0 = (min0 != 0) ? min0 + span0 : span0; |
| final double max1 = (min1 != 0) ? min1 + span1 : 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 coordinate values to NaN |
| } |
| } else if (isNaN(span0) || isNaN(span1)) { |
| continue; // Leave coordinate 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 || isNegativeZero(span1)) { |
| min = min0; |
| max = max0; |
| } else if (span0 >= csSpan || isNegativeZero(span0)) { |
| min = min1; |
| max = max1; |
| } else { |
| continue; // Leave coordinate 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 = 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. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * 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!=DIMENSION; 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: coordinates are left to NaN. |
| */ |
| final boolean sp0 = isNegative(span0); |
| final boolean sp1 = isNegative(span1); |
| if (sp0 == sp1) { |
| if (sp0 && !isNegativeUnsafe(max - min)) { |
| continue; // Leave coordinates 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 coordinates 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 coordinates 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 coordinates was |
| * {@link java.lang.Double#NaN} in which case the corresponding coordinate has been ignored. |
| * |
| * <h4>Spanning the anti-meridian of a Geographic CRS</h4> |
| * This method supports anti-meridian spanning in the same way than |
| * {@link GeneralEnvelope#add(DirectPosition)}. |
| * |
| * @param px the first coordinate of the point to add. |
| * @param py the second coordinate 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. |
| * |
| * <h4>Note on {@code hashCode()}</h4> |
| * 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; |
| } |
| |
| /** |
| * Returns a clone of this envelope. |
| * |
| * @return a clone of this envelope. |
| */ |
| @Override |
| public Envelope2D clone() { |
| return (Envelope2D) super.clone(); |
| } |
| |
| /** |
| * Formats this envelope as a "{@code BOX}" element. |
| * The output is of the form "{@code BOX(}{@linkplain #getLowerCorner() |
| * lower corner}{@code ,}{@linkplain #getUpperCorner() upper corner}{@code )}". |
| * Example: |
| * |
| * {@preformat wkt |
| * BOX(-90 -180, 90 180) |
| * } |
| * |
| * @see Envelopes#toString(Envelope) |
| */ |
| @Override |
| public String toString() { |
| return AbstractEnvelope.toString(this, false); |
| } |
| } |