/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.geometry; import java.awt.geom.Rectangle2D; import org.opengis.util.Cloneable; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.geometry.MismatchedReferenceSystemException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.AxisDirection; import org.geotools.util.Utilities; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * A two-dimensional envelope on top of {@link Rectangle2D}. This implementation is provided for * interoperability between Java2D and GeoAPI. * <p> * <strong>Note:</strong> 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 orientation and should be understood as "ordinate 0" and "ordinate 1" 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}. * * @since 2.1 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see GeneralEnvelope * @see org.geotools.geometry.jts.ReferencedEnvelope * @see org.opengis.metadata.extent.GeographicBoundingBox */ public class Envelope2D extends Rectangle2D.Double implements Envelope, Cloneable { /** * Serial number for interoperability 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. * * @since 2.5 */ public Envelope2D() { } /** * Constructs two-dimensional envelope defined by an other {@link Envelope}. * * @param envelope The envelope to copy. */ public Envelope2D(final Envelope envelope) { super(envelope.getMinimum(0), envelope.getMinimum(1), envelope.getSpan(0), envelope.getSpan(1)); // TODO: check below should be first, if only Sun could fix RFE #4093999. final int dimension = envelope.getDimension(); if (dimension != 2) { throw new MismatchedDimensionException(Errors.format( ErrorKeys.NOT_TWO_DIMENSIONAL_$1, dimension)); } setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem()); } /** * Constructs two-dimensional envelope defined by an other {@link Rectangle2D}. * * @param crs The coordinate reference system, or {@code null}. * @param rect The rectangle to copy. */ public Envelope2D(final CoordinateReferenceSystem crs, final Rectangle2D rect) { super(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); setCoordinateReferenceSystem(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 {@linkplain Envelope2D class javadoc} 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. * @param height The envelope height. */ public Envelope2D(final CoordinateReferenceSystem crs, final double x, final double y, final double width, final double height) { super(x, y, width, height); setCoordinateReferenceSystem(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 {@linkplain Envelope2D class javadoc} for details. * <p> * The {@code minDP} and {@code maxDP} arguments usually contains the minimal and maximal * ordinate values respectively, but this is not mandatory. The ordinates will be rearanged * as needed. * * @param minDP The fist position. * @param maxDP The second position. * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS. * * @since 2.4 */ public Envelope2D(final DirectPosition2D minDP, final DirectPosition2D maxDP) throws MismatchedReferenceSystemException { // Uncomment next lines if Sun fixes RFE #4093999 // ensureNonNull("minDP", minDP); // ensureNonNull("maxDP", maxDP); super(Math.min(minDP.x, maxDP.x), Math.min(minDP.y, maxDP.y), Math.abs(maxDP.x - minDP.x), Math.abs(maxDP.y - minDP.y)); setCoordinateReferenceSystem(AbstractEnvelope.getCoordinateReferenceSystem(minDP, maxDP)); } /** * Returns the coordinate reference system in which the coordinates are given. * * @return The coordinate reference system, or {@code null}. */ public final CoordinateReferenceSystem getCoordinateReferenceSystem() { return crs; } /** * Set the coordinate reference system in which the coordinate are given. * * @param crs The new coordinate reference system, or {@code null}. */ public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { AbstractDirectPosition.checkCoordinateReferenceSystemDimension(crs, getDimension()); this.crs = crs; } /** * Returns the number of dimensions. */ public final int getDimension() { return 2; } /** * A coordinate position consisting of all the minimal ordinates for each * dimension for all points within the {@code Envelope}. * * @return The lower corner. * * @todo Change the return type to {@link DirectPosition2D} when we will * be allowed to compile for J2SE 1.5. */ public DirectPosition getLowerCorner() { return new DirectPosition2D(crs, getMinX(), getMinY()); } /** * A coordinate position consisting of all the maximal ordinates for each * dimension for all points within the {@code Envelope}. * * @return The upper corner. * * @todo Change the return type to {@link DirectPosition2D} when we will * be allowed to compile for J2SE 1.5. */ public DirectPosition getUpperCorner() { return new DirectPosition2D(crs, getMaxX(), getMaxY()); } /** * Creates an exception for an index out of bounds. */ private static IndexOutOfBoundsException indexOutOfBounds(final int dimension) { return new IndexOutOfBoundsException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, dimension)); } /** * Returns the minimal ordinate along the specified dimension. * * @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. */ public final double getMinimum(final int dimension) throws IndexOutOfBoundsException { switch (dimension) { case 0: return getMinX(); case 1: return getMinY(); default: throw indexOutOfBounds(dimension); } } /** * Returns the maximal ordinate along the specified dimension. * * @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. */ public final double getMaximum(final int dimension) throws IndexOutOfBoundsException { switch (dimension) { case 0: return getMaxX(); case 1: return getMaxY(); default: throw indexOutOfBounds(dimension); } } /** * Returns the center ordinate along the specified dimension. * * @param dimension The dimension to query. * @return The mid ordinate value along the given dimension. * * @deprecated Renamed as {@link #getMedian}. */ @Deprecated public final double getCenter(final int dimension) { return getMedian(dimension); } /** * Returns the median ordinate along the specified dimension. The result should be equals * (minus rounding error) to <code>({@linkplain #getMaximum getMaximum}(dimension) - * {@linkplain #getMinimum getMinimum}(dimension)) / 2</code>. * * @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. */ public final double getMedian(final int dimension) throws IndexOutOfBoundsException { switch (dimension) { case 0: return getCenterX(); case 1: return getCenterY(); default: throw indexOutOfBounds(dimension); } } /** * Returns the envelope length along the specified dimension. * This length is equals to the maximum ordinate minus the * minimal ordinate. * * @param dimension The dimension to query. * @return The difference along maximal and minimal ordinates in the given dimension. * * @deprecated Renamed as {@link #getSpan}. */ @Deprecated public final double getLength(final int dimension) { return getSpan(dimension); } /** * Returns the envelope span (typically width or height) along the specified dimension. * The result should be equals (minus rounding error) to <code>{@linkplain #getMaximum * getMaximum}(dimension) - {@linkplain #getMinimum getMinimum}(dimension)</code>. * * @param dimension The dimension to query. * @return The difference along maximal and minimal ordinates in the given dimension. * @throws IndexOutOfBoundsException If the given index is out of bounds. */ public final double getSpan(final int dimension) throws IndexOutOfBoundsException { switch (dimension) { case 0: return getWidth (); case 1: return getHeight(); default: throw indexOutOfBounds(dimension); } } /** * Returns a hash value for this envelope. This value need not remain consistent between * different implementations of the same class. */ @Override public int hashCode() { int code = super.hashCode() ^ (int) serialVersionUID; if (crs != null) { code += crs.hashCode(); } return code; } /** * Compares the specified object with this envelope for equality. * * @param object The object to compare with this envelope. * @return {@code true} if the given object is equals to this envelope. */ @Override public boolean equals(final Object object) { if (super.equals(object)) { final CoordinateReferenceSystem otherCRS = (object instanceof Envelope2D) ? ((Envelope2D) object).crs : null; return Utilities.equals(crs, otherCRS); } return false; } /** * Returns {@code true} if {@code this} envelope bounds is equals 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 comparaisons. 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 string representation of this envelope. The default implementation is okay * for occasional formatting (for example for debugging purpose). But if there is a lot * of envelopes to format, users will get more control by using their own instance of * {@link org.geotools.measure.CoordinateFormat}. * * @since 2.4 */ @Override public String toString() { return AbstractEnvelope.toString(this); } }