/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2007-2012, Geomatys
*
* 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.geotoolkit.coverage.sql;
import java.util.Date;
import java.util.Objects;
import java.awt.Dimension;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import org.opengis.geometry.Envelope;
import org.opengis.util.FactoryException;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.SingleCRS;
import org.geotoolkit.util.Cloneable;
import org.geotoolkit.util.DateRange;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.AbstractEnvelope;
import org.apache.sis.internal.metadata.AxisDirections;
import org.geotoolkit.display.shape.XRectangle2D;
import org.geotoolkit.display.shape.FloatDimension2D;
import org.geotoolkit.display.shape.DoubleDimension2D;
import org.geotoolkit.internal.sql.table.SpatialDatabase;
import org.apache.sis.referencing.crs.DefaultTemporalCRS;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.util.Utilities;
import org.apache.sis.geometry.Envelopes;
import static java.lang.Double.doubleToLongBits;
import static java.lang.Double.NEGATIVE_INFINITY;
import static java.lang.Double.POSITIVE_INFINITY;
/**
* An envelope holding the spatio-temporal extent and preferred resolution of a coverage. The
* envelope Coordinate Reference System (CRS) is determined by the {@link CoverageDatabase}
* instance associated with this object and can not be changed. The {@code CoverageDatabase}
* may define different CRS, but the following axes can be considered typical:
* <p>
* <ul>
* <li>The longitude in decimal degrees relative to Greenwich meridian.</li>
* <li>The latitude in decimal degrees.</li>
* <li>Altitude in metres above the WGS 84 ellipsoid.</li>
* <li>Time in fractional days since epoch.</li>
* </ul>
* <p>
* This class provides convenience methods for fetching and setting the
* {@linkplain #getHorizontalRange() horizontal rectangle},
* {@linkplain #getVerticalRange() vertical range} and
* {@linkplain #getTimeRange() temporal range} of the envelope.
*
* {@section Conventions}
* In this class, the lower and upper bounds are both inclusive. This is consistent with OGC/ISO
* conventions but different than typical Java conventions, where the upper bounds is often
* exclusive.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.10
*
* @since 3.10 (derived from Seagis)
* @module
*/
public class CoverageEnvelope extends AbstractEnvelope implements Cloneable {
/**
* The database for which this extent is defined. This is used in order
* to get the horizontal, vertical and temporal components of the CRS.
*/
final SpatialDatabase database;
/**
* The envelope time component, in milliseconds since January 1st, 1970.
* May be {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} if unbounded.
*/
private long tMin, tMax;
/**
* The envelope spatial component in {@link SpatialDatabase#horizontalCRS} coordinates. The
* longitude range may be larger than needed (±360° instead of ±180°) because we don't know
* in advance if the longitudes are inside the [-180 .. +180°] range or the [0 .. 360°] range.
*/
private double xMin, xMax, yMin, yMax, zMin, zMax;
/**
* The preferred resolution along the <var>x</var> and <var>y</var> axis. Units shall be the
* same than for the horizontal bounding box (determined by {@link SpatialDatabase#horizontalCRS}).
* This information is only approximative; there is no guarantee that an image to be read will
* have that resolution. A null value (zero) means that the best resolution should be used.
*/
private float xResolution, yResolution;
/**
* The bounding box computed by {@link #getEnvelope()}, or {@code null}
* if not yet computed. This is cached for performance reasons.
*/
private transient GeneralEnvelope envelope;
/**
* Creates a new instance initialized to infinite bounds.
*
* @param database The database for which this extent is defined.
*/
CoverageEnvelope(final SpatialDatabase database) {
this.database = database;
tMin = Long.MIN_VALUE;
tMax = Long.MAX_VALUE;
xMin = NEGATIVE_INFINITY;
xMax = POSITIVE_INFINITY;
yMin = NEGATIVE_INFINITY;
yMax = POSITIVE_INFINITY;
zMin = NEGATIVE_INFINITY;
zMax = POSITIVE_INFINITY;
}
/**
* Resets all envelope attributes to their initial state, which is an infinite envelope.
*
* @return {@code true} if this envelope changed as a result of this method call.
*/
public boolean clear() {
// Line below really requires | operator, not ||.
return setHorizontalRange(null) | setVerticalRange(null) | setTimeRange(null) | setPreferredResolution(null);
}
/**
* Sets the spatio-temporal envelope and the resolution from an other
* {@code CoverageEnvelope} object. This method invokes the individual
* {@link #setHorizontalRange}, {@link #setVerticalRange} and {@link #setTimeRange}
* methods if possible, which are more efficient than {@link #setEnvelope}.
*/
final void setAll(final CoverageEnvelope envelope) throws TransformException {
if (envelope != this) {
if (envelope == null || envelope.database != database) {
setEnvelope(envelope);
} else {
setHorizontalRange(envelope.getHorizontalRange());
setVerticalRange (envelope.getVerticalRange());
setTimeRange (envelope.getTimeRange());
}
setPreferredResolution(envelope != null ? envelope.getPreferredResolution() : null);
}
}
/**
* Sets this envelope to the intersection of this envelope with the given one.
*/
final void intersect(CoverageEnvelope envelope) throws TransformException {
if (envelope.database != database) {
// Paranoiac safety - should not happen.
final CoverageEnvelope old = envelope;
envelope = clone();
envelope.setAll(old);
}
final Rectangle2D thisRect = getHorizontalRange();
final Rectangle2D envRect = envelope.getHorizontalRange();
if (thisRect instanceof XRectangle2D) {
((XRectangle2D) thisRect).intersect(envRect);
} else {
Rectangle2D.intersect(thisRect, envRect, thisRect);
}
setHorizontalRange(thisRect);
setVerticalRange(Math.max(zMin, envelope.zMin), Math.min(zMax, envelope.zMax));
setTimeRange(new Date(Math.max(tMin, envelope.tMin)), new Date(Math.min(tMax, envelope.tMax)));
}
/**
* Returns the spatio-temporal CRS of this envelope.
*/
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return database.spatioTemporalCRS;
}
/**
* Returns the CRS of this envelope containing only the requested dimensions. If an argument
* is {@code true}, then the returned CRS will contain the corresponding dimension if the
* database CRS has such dimension. If an argument is {@code false}, then the returned CRS
* is guaranteed to not have the corresponding dimension. If all arguments are {@code false},
* then this method returns {@code null}.
*
* @param horizontal {@code false} for excluding the horizontal component in the returned CRS.
* @param vertical {@code false} for excluding the vertical component in the returned CRS.
* @param temporal {@code false} for excluding the temporal component in the returned CRS.
* @return The envelope CRS excluding the components specified by {@code false} argument value,
* or {@code null} if there is no CRS for the remaining components.
*/
public CoordinateReferenceSystem getCoordinateReferenceSystem(
final boolean horizontal, final boolean vertical, final boolean temporal)
{
final CoordinateReferenceSystem crs;
if (horizontal) {
if (vertical) {
crs = temporal ? database.spatioTemporalCRS : database.spatialCRS;
} else {
crs = temporal ? database.horizTemporalCRS : database.horizontalCRS;
}
} else {
if (vertical) {
crs = temporal ? database.vertTemporalCRS : database.verticalCRS;
} else {
crs = temporal ? database.temporalCRS : null;
}
}
return crs;
}
/**
* Returns the number of dimension in this envelope, which is usually 4.
*/
@Override
public int getDimension() {
return database.spatioTemporalCRS.getCoordinateSystem().getDimension();
}
/**
* Returns the dimension in {@code spatioTemporalCS} of the first axis which is colinear
* with the axes of the given {@code crs}. If none are found, return -1.
*/
private static int dimensionColinearWith(final CoordinateSystem spatioTemporalCS, final SingleCRS crs) {
return (crs != null) ? AxisDirections.indexOfColinear(spatioTemporalCS, crs.getCoordinateSystem()) : -1;
}
/**
* Returns the spatio-temporal envelope. This implementation computes the envelope from
* the informations returned by {@link #getHorizontalRange()}, {@link #getVerticalRange()}
* and {@link #getTimeRange()}.
*
* @return The spatio-temporal envelope. This is a direct reference to the instance holds
* by this class - <strong>do not modify!</strong>.
*
* @see #getHorizontalRange()
* @see #getVerticalRange()
* @see #getTimeRange()
*/
private GeneralEnvelope getEnvelope() {
GeneralEnvelope envelope = this.envelope;
if (envelope == null) {
final CoordinateReferenceSystem crs = database.spatioTemporalCRS;
final CoordinateSystem cs = crs.getCoordinateSystem();
envelope = new GeneralEnvelope(crs);
int dim = dimensionColinearWith(cs, database.horizontalCRS);
if (dim >= 0) {
final Rectangle2D box = getHorizontalRange();
envelope.setRange(dim, box.getMinX(), box.getMaxX());
envelope.setRange(dim+1, box.getMinY(), box.getMaxY());
}
dim = dimensionColinearWith(cs, database.verticalCRS);
if (dim >= 0) {
final NumberRange<?> altitude = getVerticalRange();
envelope.setRange(dim, altitude.getMinDouble(), altitude.getMaxDouble());
}
final DefaultTemporalCRS temporalCRS = database.temporalCRS;
dim = dimensionColinearWith(cs, temporalCRS);
if (dim >= 0) {
final DateRange time = getTimeRange();
final Date startTime = time.getMinValue();
final Date endTime = time.getMaxValue();
envelope.setRange(dim,
(startTime != null) ? temporalCRS.toValue(startTime) : NEGATIVE_INFINITY,
(endTime != null) ? temporalCRS.toValue( endTime) : POSITIVE_INFINITY);
}
this.envelope = envelope;
}
return envelope;
}
/**
* Sets the spatio-temporal envelope. The default implementation delegates to
* {@link #setHorizontalRange(Rectangle2D)}, {@link #setVerticalRange(NumberRange)}
* and {@link #setTimeRange(DateRange)}, applying a coordinate transformation if needed.
* <p>
* If the given envelope has more dimensions than this
* {@linkplain #getDimension() envelope dimension}, then the extra dimensions will be ignored.
* If the given envelope has less dimensions, then the {@code CoverageEnvelope} dimensions not
* present in the given envelope will be left unchanged.
* <p>
* <b>Example:</b> If the given envelope is two-dimensional and its CRS is horizontal,
* then only the {@link #setHorizontalRange(Rectangle2D)} method will be invoked on this
* {@code CoverageEnvelope} - the vertical and temporal ordinate values will be unchanged.
*
* @param envelope The envelope, or {@code null} to reset to full coverage.
* @return {@code true} if the envelope changed as a result of this call, or
* {@code false} if the specified envelope is equals to the one already set.
* @throws TransformException if an error occurred during coordinate transformation.
*/
public boolean setEnvelope(Envelope envelope) throws TransformException {
if (envelope == null) {
// Like clear() except that we don't touch to the resolution.
// Note: line below really requires the | operator, not ||.
return setHorizontalRange(null) | setVerticalRange(null) | setTimeRange(null);
}
CoordinateReferenceSystem sourceCRS = envelope.getCoordinateReferenceSystem();
CoordinateReferenceSystem targetCRS = getCoordinateReferenceSystem(
CRS.getHorizontalComponent(sourceCRS) != null,
CRS.getVerticalComponent (sourceCRS, true) != null,
CRS.getTemporalComponent (sourceCRS) != null);
if (targetCRS == null) {
return false;
}
final CoordinateOperationFactory factory = org.geotoolkit.referencing.CRS.getCoordinateOperationFactory(true);
try {
final CoordinateOperation userToStandard = factory.createOperation(sourceCRS, targetCRS);
if (!userToStandard.getMathTransform().isIdentity()) {
envelope = Envelopes.transform(userToStandard, envelope);
}
} catch (FactoryException e) {
throw new TransformException(Errors.format(Errors.Keys.CantTransformEnvelope), e);
}
boolean changed = false;
sourceCRS = envelope.getCoordinateReferenceSystem();
final CoordinateSystem cs = sourceCRS.getCoordinateSystem();
int dim = dimensionColinearWith(cs, database.horizontalCRS);
if (dim >= 0) {
changed |= setHorizontalRange(XRectangle2D.createFromExtremums(
envelope.getMinimum(dim), envelope.getMinimum(dim+1),
envelope.getMaximum(dim), envelope.getMaximum(dim+1)));
}
dim = dimensionColinearWith(cs, database.verticalCRS);
if (dim >= 0) {
changed |= setVerticalRange(envelope.getMinimum(dim), envelope.getMaximum(dim));
}
final DefaultTemporalCRS temporalCRS = database.temporalCRS;
dim = dimensionColinearWith(cs, temporalCRS);
if (dim >= 0) {
final Date minimum = temporalCRS.toDate(envelope.getMinimum(dim));
final Date maximum = temporalCRS.toDate(envelope.getMaximum(dim));
changed |= setTimeRange(minimum, maximum);
}
return changed;
}
/**
* Returns the horizontal bounding box of the elements to be read.
* The returned rectangle may contains infinite values.
*
* @return The horizontal bounding box of the elements to be read.
*
* @see #getVerticalRange()
* @see #getTimeRange()
*/
public Rectangle2D getHorizontalRange() {
return XRectangle2D.createFromExtremums(xMin, yMin, xMax, yMax);
}
/**
* Sets the horizontal bounding box of the elements to be read.
*
* @param area The horizontal bounding box of the elements to be read.
* @return {@code true} if the bounding box changed as a result of this call, or
* {@code false} if the specified box is equals to the one already set.
*/
public boolean setHorizontalRange(final Rectangle2D area) {
boolean change;
change = (xMin != (xMin = (area != null) ? area.getMinX() : NEGATIVE_INFINITY));
change |= (xMax != (xMax = (area != null) ? area.getMaxX() : POSITIVE_INFINITY));
change |= (yMin != (yMin = (area != null) ? area.getMinY() : NEGATIVE_INFINITY));
change |= (yMax != (yMax = (area != null) ? area.getMaxY() : POSITIVE_INFINITY));
if (change) {
fireStateChanged("Envelope2D");
}
return change;
}
/**
* Returns the vertical range of the elements to be read.
*
* @return The vertical range of the elements to be read.
*
* @see #getHorizontalRange()
* @see #getTimeRange()
*/
public NumberRange<Double> getVerticalRange() {
return NumberRange.create(zMin, true, zMax, true);
}
/**
* Sets the vertical range of the elements to be read. This convenience
* method delegates to {@link #setVerticalRange(double, double)}.
*
* @param range The vertical range, or {@code null} for full coverage.
* @return {@code true} if the vertical range changed as a result of this call, or
* {@code false} if the specified range is equals to the one already set.
*/
public final boolean setVerticalRange(final NumberRange<?> range) {
final double minimum, maximum;
if (range != null) {
minimum = range.getMinDouble(true);
maximum = range.getMaxDouble(true);
} else {
minimum = NEGATIVE_INFINITY;
maximum = POSITIVE_INFINITY;
}
return setVerticalRange(minimum, maximum);
}
/**
* Sets the vertical range of the elements to be read.
*
* @param minimum The minimal <var>z</var> value, inclusive.
* @param maximum The maximal <var>z</var> value, <strong>inclusive</strong>.
* @return {@code true} if the vertical range changed as a result of this call, or
* {@code false} if the specified range is equals to the one already set.
*/
public boolean setVerticalRange(final double minimum, final double maximum) {
boolean change;
change = (doubleToLongBits(zMin) != doubleToLongBits(zMin = minimum));
change |= (doubleToLongBits(zMax) != doubleToLongBits(zMax = maximum));
if (change) {
fireStateChanged("VerticalRange");
}
return change;
}
/**
* Returns the time range of the elements to be read.
*
* @return The time range of the elements to be read.
*
* @see #getHorizontalRange()
* @see #getVerticalRange()
*/
public DateRange getTimeRange() {
return new DateRange((tMin != Long.MIN_VALUE) ? new Date(tMin) : null,
(tMax != Long.MAX_VALUE) ? new Date(tMax) : null);
}
/**
* Sets the time range of the elements to be read. This convenience method
* delegates to {@link #setTimeRange(Date, Date)}.
*
* @param timeRange The time range.
* @return {@code true} if the time range changed as a result of this call, or
* {@code false} if the specified range is equals to the one already set.
*/
public final boolean setTimeRange(final DateRange timeRange) {
Date startTime, endTime;
if (timeRange != null) {
startTime = timeRange.getMinValue();
endTime = timeRange.getMaxValue();
} else {
startTime = null;
endTime = null;
}
if (startTime != null && !timeRange.isMinIncluded()) {
startTime = new Date(startTime.getTime() + 1);
}
if (endTime != null && !timeRange.isMaxIncluded()) {
endTime = new Date(endTime.getTime() - 1);
}
return setTimeRange(startTime, endTime);
}
/**
* Sets the time range of the elements to be read by this table.
*
* @param startTime The start time, inclusive.
* @param endTime The end time, <strong>inclusive</strong>.
* @return {@code true} if the time range changed as a result of this call, or
* {@code false} if the specified range is equals to the one already set.
*/
public boolean setTimeRange(final Date startTime, final Date endTime) {
boolean change;
change = (tMin != (tMin = (startTime != null) ? startTime.getTime() : Long.MIN_VALUE));
change |= (tMax != (tMax = ( endTime != null) ? endTime.getTime() : Long.MAX_VALUE));
if (change) {
fireStateChanged("TimeRange");
}
return change;
}
/**
* Returns the approximative resolution desired, or {@code null} for best resolution.
* The units are the same than for the {@linkplain #getHorizontalRange horizontal envelope}.
*
* @return The resolution, or {@code null} for the best resolution available.
*/
public Dimension2D getPreferredResolution() {
if (xResolution > 0 || yResolution > 0) {
return new FloatDimension2D(xResolution, yResolution);
} else {
return null;
}
}
/**
* Sets the preferred resolution in units of the {@linkplain #getHorizontalRange horizontal
* envelope}. This is only an approximative hint, since there is no guarantee that an image
* will be read with that resolution. A null values means that the best available resolution
* should be used.
*
* @param resolution The preferred geographic resolution, or {@code null} for best resolution.
* @return {@code true} if the resolution changed as a result of this call, or
* {@code false} if the specified resolution is equals to the one already set.
*/
public boolean setPreferredResolution(final Dimension2D resolution) {
float dx, dy;
if (resolution != null) {
dx = (float) resolution.getWidth ();
dy = (float) resolution.getHeight();
if (!(dx >= 0)) dx = 0; // '!' for catching NaN
if (!(dy >= 0)) dy = 0;
} else {
dx = 0;
dy = 0;
}
boolean change;
change = (xResolution != (xResolution = dx));
change |= (yResolution != (yResolution = dy));
if (change) {
fireStateChanged("PreferredResolution");
}
return change;
}
/**
* Returns the approximative size of the desired image, or {@code null} if unknown.
* This is computed from the {@linkplain #getHorizontalRange() horizontal range} and
* the {@linkplain #getPreferredResolution() preferred resolution}.
*
* @return The image size computed from the horizontal range and the resolution,
* or {@code null} if the size can not be computed.
*/
public Dimension getPreferredImageSize() {
final double width = (xMax - xMin) / xResolution;
if (width >= 1 && width <= Integer.MAX_VALUE) {
final double height = (yMax - yMin) / yResolution;
if (height >= 1 && height <= Integer.MAX_VALUE) {
return new Dimension((int) Math.round(width), (int) Math.round(height));
}
}
return null;
}
/**
* Sets the approximative size of the desired image. This is a convenience method which
* {@linkplain #setPreferredResolution set the preferred resolution} to a value computed
* from the {@linkplain #getHorizontalRange() horizontal range} and the given size.
* <p>
* The {@link #setHorizontalRange(Rectangle2D)} or {@link #setEnvelope(Envelope)} method
* must have been invoked with a finite envelope before this {@code setPreferredImageSize}
* method. The previous preferred resolution, if any, is discarded.
*
* @param size The new preferred image size, or {@code null}.
* @return {@code true} if the resolution changed as a result of this call, or
* {@code false} if the specified resolution is equals to the one computed
* by this method.
* @throws IllegalStateException If the current {@linkplain #getHorizontalRange()
* horizontal range} is not finite.
*/
public boolean setPreferredImageSize(final Dimension size) throws IllegalStateException {
Dimension2D resolution = null;
if (size != null) {
double dx = size.width;
double dy = size.height;
if (!(dx > 0 && dy > 0)) {
throw new IllegalArgumentException(errors()
.getString(Errors.Keys.IllegalArgument_2, "size", size));
}
dx = (xMax - xMin) / dx;
dy = (yMax - yMin) / dy;
if (Double.isInfinite(dx) || Double.isInfinite(dy)) {
throw new IllegalStateException(errors()
.getString(Errors.Keys.UndefinedProperty_1, "envelope"));
}
resolution = new DoubleDimension2D(dx, dy);
}
return setPreferredResolution(resolution);
}
/**
* Returns the resource bundled for error messages.
*/
final Errors errors() {
return Errors.getResources(database.getLocale());
}
/**
* Notifies that the state of this extent changed. Subclasses can
* override this method if they need to be notified about any change.
*
* @param property The name of the property that changed.
*/
void fireStateChanged(final String property) {
if (!"PreferredResolution".equals(property)) {
envelope = null;
}
}
/**
* Returns the minimal ordinate along the specified dimension.
*
* @param dimension The dimension for which to obtain the ordinate value.
* @return The minimal ordinate at the given dimension.
* @throws IndexOutOfBoundsException If the given index is negative or is equals or greater
* than the {@linkplain #getDimension envelope dimension}.
*/
@Override
public double getLower(final int dimension) throws IndexOutOfBoundsException {
return getEnvelope().getLower(dimension);
}
/**
* Returns the maximal ordinate along the specified dimension.
*
* @param dimension The dimension for which to obtain the ordinate value.
* @return The maximal ordinate at the given dimension.
* @throws IndexOutOfBoundsException If the given index is negative or is equals or greater
* than the {@linkplain #getDimension envelope dimension}.
*/
@Override
public double getUpper(final int dimension) throws IndexOutOfBoundsException {
return getEnvelope().getUpper(dimension);
}
/**
* Returns the median ordinate along the specified dimension.
*
* @param dimension The dimension for which to obtain the ordinate value.
* @return The median ordinate at the given dimension.
* @throws IndexOutOfBoundsException If the given index is negative or is equals or greater
* than the {@linkplain #getDimension envelope dimension}.
*/
@Override
public double getMedian(final int dimension) throws IndexOutOfBoundsException {
return getEnvelope().getMedian(dimension);
}
/**
* Returns the envelope span along the specified dimension.
*
* @param dimension The dimension for which to obtain the ordinate value.
* @return The envelope span along the given dimension.
* @throws IndexOutOfBoundsException If the given index is negative or is equals or greater
* than the {@linkplain #getDimension envelope dimension}.
*/
@Override
public double getSpan(final int dimension) throws IndexOutOfBoundsException {
return getEnvelope().getSpan(dimension);
}
/**
* Returns a clone of this coverage envelope.
*/
@Override
public CoverageEnvelope clone() {
try {
return (CoverageEnvelope) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // Should never happen since we are Cloneable.
}
}
/**
* Returns a hash code value for this extent.
*/
@Override
public int hashCode() {
long code = tMin + 31 *
(tMax + 31 *
(Double.doubleToLongBits(xMin) + 31 *
(Double.doubleToLongBits(yMin) + 31 *
(Double.doubleToLongBits(zMin) + 31 *
(Double.doubleToLongBits(xMax) + 31 *
(Double.doubleToLongBits(yMax) + 31 *
(Double.doubleToLongBits(zMax))))))));
return (int) code ^ (int) (code >>> 32) +
31 * (Float.floatToIntBits(xResolution) +
31 * Float.floatToIntBits(yResolution));
}
/**
* Compares this extent with the given object for equality.
*
* @param other The object to compare with this one.
*/
@Override
public boolean equals(final Object other) {
if (other == this) {
return true;
}
if (other != null && other.getClass() == getClass()) {
final CoverageEnvelope that = (CoverageEnvelope) other;
return tMin == that.tMin && tMax == that.tMax &&
Utilities.equals(xMin, that.xMin) &&
Utilities.equals(xMax, that.xMax) &&
Utilities.equals(yMin, that.yMin) &&
Utilities.equals(yMax, that.yMax) &&
Utilities.equals(zMin, that.zMin) &&
Utilities.equals(zMax, that.zMax) &&
Utilities.equals(xResolution, that.xResolution) &&
Utilities.equals(yResolution, that.yResolution) &&
Objects.equals(database.spatioTemporalCRS, that.database.spatioTemporalCRS);
}
return false;
}
}