/* * 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 org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedReferenceSystemException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.geotools.util.Utilities; import org.geotools.resources.Classes; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * Base class for {@linkplain Envelope envelope} implementations. This base class * provides default implementations for {@link #toString}, {@link #equals} and * {@link #hashCode} methods. * <p> * This class do not holds any state. The decision to implement {@link java.io.Serializable} * or {@link org.geotools.util.Cloneable} interfaces is left to implementors. * * @since 2.4 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public abstract class AbstractEnvelope implements Envelope { /** * Constructs an envelope. */ protected AbstractEnvelope() { } /** * Returns the common CRS of specified points. * * @param minDP The first position. * @param maxDP The second position. * @return Their common CRS, or {@code null} if none. * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS. */ static CoordinateReferenceSystem getCoordinateReferenceSystem(final DirectPosition minDP, final DirectPosition maxDP) throws MismatchedReferenceSystemException { final CoordinateReferenceSystem crs1 = minDP.getCoordinateReferenceSystem(); final CoordinateReferenceSystem crs2 = maxDP.getCoordinateReferenceSystem(); if (crs1 == null) { return crs2; } else { if (crs2!=null && !crs1.equals(crs2)) { throw new MismatchedReferenceSystemException( Errors.format(ErrorKeys.MISMATCHED_COORDINATE_REFERENCE_SYSTEM)); } return crs1; } } /** * A coordinate position consisting of all the {@linkplain #getMinimum minimal ordinates}. * The default implementation returns a direct position backed by this envelope, so changes * in this envelope will be immediately reflected in the direct position. * * @return The lower corner. */ public DirectPosition getLowerCorner() { return new LowerCorner(); } /** * A coordinate position consisting of all the {@linkplain #getMaximum maximal ordinates}. * The default implementation returns a direct position backed by this envelope, so changes * in this envelope will be immediately reflected in the direct position. * * @return The upper corner. */ public DirectPosition getUpperCorner() { return new UpperCorner(); } /** * Returns a string representation of this envelope. The default implementation returns a * string containing {@linkplain #getLowerCorner lower corner} coordinates first, followed * by {@linkplain #getUpperCorner upper corner} coordinates. Other informations like the * CRS or class name may or may not be presents at implementor choice. * <p> * This string 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}. */ @Override public String toString() { return toString(this); } /** * Formats the specified envelope. The returned string will contain the * {@linkplain #getLowerCorner lower corner} coordinates first, followed by * {@linkplain #getUpperCorner upper corner} coordinates. */ static String toString(final Envelope envelope) { final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(envelope)); final int dimension = envelope.getDimension(); if (dimension != 0) { String separator = "[("; for (int i=0; i<dimension; i++) { buffer.append(separator).append(envelope.getMinimum(i)); separator = ", "; } separator = "), ("; for (int i=0; i<dimension; i++) { buffer.append(separator).append(envelope.getMaximum(i)); separator = ", "; } buffer.append(")]"); } return buffer.toString(); } /** * Returns a hash value for this envelope. */ @Override public int hashCode() { final int dimension = getDimension(); int code = 1; boolean p = true; do { for (int i=0; i<dimension; i++) { final long bits = Double.doubleToLongBits(p ? getMinimum(i) : getMaximum(i)); code = 31 * code + ((int)(bits) ^ (int)(bits >>> 32)); } } while ((p = !p) == false); final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); if (crs != null) { code += crs.hashCode(); } return code; } /** * Returns {@code true} if the specified object is also an {@linkplain Envelope envelope} * with equals coordinates and {@linkplain #getCoordinateReferenceSystem CRS}. * * @param object The object to compare with this envelope. * @return {@code true} if the given object is equals to this envelope. * * @todo Current implementation requires that {@code object} is of the same class. * We can not relax this rule before we ensure that every implementations in * the Geotools code base follow the same contract. */ @Override public boolean equals(final Object object) { if (object!=null && object.getClass().equals(getClass())) { final Envelope that = (Envelope) object; final int dimension = getDimension(); if (dimension == that.getDimension()) { for (int i=0; i<dimension; i++) { if (!Utilities.equals(this.getMinimum(i), that.getMinimum(i)) || !Utilities.equals(this.getMaximum(i), that.getMaximum(i))) { return false; } } if (Utilities.equals(this.getCoordinateReferenceSystem(), that.getCoordinateReferenceSystem())) { assert hashCode() == that.hashCode() : this; return true; } } } return false; } /** * Base class for direct position from an envelope. * This class delegates its work to the enclosing envelope. */ private abstract class Corner extends AbstractDirectPosition { /** The coordinate reference system in which the coordinate is given. */ public CoordinateReferenceSystem getCoordinateReferenceSystem() { return AbstractEnvelope.this.getCoordinateReferenceSystem(); } /** The length of coordinate sequence (the number of entries). */ public int getDimension() { return AbstractEnvelope.this.getDimension(); } /** Sets the ordinate value along the specified dimension. */ public void setOrdinate(int dimension, double value) { throw new UnsupportedOperationException(); } } /** * The corner returned by {@link AbstractEnvelope#getLowerCorner}. */ private final class LowerCorner extends Corner { public double getOrdinate(final int dimension) throws IndexOutOfBoundsException { return getMinimum(dimension); } } /** * The corner returned by {@link AbstractEnvelope#getUpperCorner}. */ private final class UpperCorner extends Corner { public double getOrdinate(final int dimension) throws IndexOutOfBoundsException { return getMaximum(dimension); } } }