org.geotoolkit.geometry.GeneralEnvelope Maven / Gradle / Ivy
Show all versions of geotk-referencing Show documentation
/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2004-2011, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2011, 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.geometry; import java.util.Arrays; import java.io.Serializable; import java.awt.geom.Rectangle2D; import java.lang.reflect.Field; import javax.measure.unit.Unit; import javax.measure.converter.ConversionException; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.RangeMeaning; import org.opengis.geometry.Envelope; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.metadata.spatial.PixelOrientation; import org.geotoolkit.resources.Errors; import org.geotoolkit.math.XMath; import org.geotoolkit.util.XArrays; import org.geotoolkit.util.Cloneable; import org.geotoolkit.util.Utilities; import org.geotoolkit.util.converter.Classes; import org.geotoolkit.display.shape.XRectangle2D; import org.geotoolkit.referencing.crs.DefaultGeographicCRS; import org.geotoolkit.metadata.iso.spatial.PixelTranslation; import static org.geotoolkit.util.ArgumentChecks.*; /** * A minimum bounding box or rectangle. Regardless of dimension, an {@code Envelope} can * be represented without ambiguity as two {@linkplain DirectPosition direct positions} * (coordinate points). To encode an {@code Envelope}, it is sufficient to encode these * two points. * * {@note
exclusive. *Envelope
uses an arbitrary Coordinate Reference System, which * doesn't need to be geographic. This is different than theGeographicBoundingBox
* class provided in the metadata package, which can be used as a kind of envelope restricted to * a Geographic CRS having Greenwich prime meridian.} * * This particular implementation of {@code Envelope} is said "General" because it * uses coordinates of an arbitrary dimension. This is in contrast with {@link Envelope2D}, * which can use only two-dimensional coordinates. ** A {@code GeneralEnvelope} can be created in various ways: *
*
*
* * @author Martin Desruisseaux (IRD, Geomatys) * @author Simone Giannecchini (Geosolutions) * @version 3.19 * * @see Envelope2D * @see org.geotoolkit.geometry.jts.ReferencedEnvelope * @see org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox * * @since 1.2 * @module */ public class GeneralEnvelope extends AbstractEnvelope implements Cloneable, Serializable { /** * Serial number for inter-operability with different versions. */ private static final long serialVersionUID = 1752330560227688940L; /** * Used for setting the {@link #ordinates} field during a {@link #clone()} operation only. * Will be fetch when first needed. */ private static volatile Field ordinatesField; /** * Minimum and maximum ordinate values. The first half contains minimum ordinates, while the * last half contains maximum ordinates. This layout is convenient for the creation of lower * and upper corner direct positions. */ private final double[] ordinates; /** * The coordinate reference system, or {@code null}. */ private CoordinateReferenceSystem crs; /** * Constructs an empty envelope of the specified dimension. All ordinates * are initialized to 0 and the coordinate reference system is undefined. * * @param dimension The envelope dimension. */ public GeneralEnvelope(final int dimension) { ordinates = new double[dimension << 1]; } /** * Constructs one-dimensional envelope defined by a range of values. * * @param min The minimal value. * @param max The maximal value. */ public GeneralEnvelope(final double min, final double max) { ordinates = new double[] {min, max}; checkCoordinates(ordinates); } /** * Constructs a envelope defined by two positions. * * @param minDP Minimum ordinate values. * @param maxDP Maximum ordinate values. * @throws MismatchedDimensionException if the two positions don't have the same dimension. * @throws IllegalArgumentException if an ordinate value in the minimum point is not * less than or equal to the corresponding ordinate value in the maximum point. */ public GeneralEnvelope(final double[] minDP, final double[] maxDP) throws IllegalArgumentException { ensureNonNull("minDP", minDP); ensureNonNull("maxDP", maxDP); ensureSameDimension(minDP.length, maxDP.length); ordinates = new double[minDP.length + maxDP.length]; System.arraycopy(minDP, 0, ordinates, 0, minDP.length); System.arraycopy(maxDP, 0, ordinates, minDP.length, maxDP.length); checkCoordinates(ordinates); } /** * Constructs a envelope defined by two positions. The coordinate * reference system is inferred from the supplied direct position. * * @param minDP Point containing minimum ordinate values. * @param maxDP Point containing maximum ordinate values. * @throws MismatchedDimensionException if the two positions don't have the same dimension. * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS. * @throws IllegalArgumentException if an ordinate value in the minimum point is not * less than or equal to the corresponding ordinate value in the maximum point. */ public GeneralEnvelope(final GeneralDirectPosition minDP, final GeneralDirectPosition maxDP) throws IllegalArgumentException { // Uncomment next lines if Sun fixes RFE #4093999 // ensureNonNull("minDP", minDP); // ensureNonNull("maxDP", maxDP); this(minDP.ordinates, maxDP.ordinates); crs = getCoordinateReferenceSystem(minDP, maxDP); AbstractDirectPosition.checkCoordinateReferenceSystemDimension(crs, ordinates.length >>> 1); } /** * Constructs an empty envelope with the specified coordinate reference system. * All ordinates are initialized to 0. * * @param crs The coordinate reference system. * * @since 2.2 */ public GeneralEnvelope(final CoordinateReferenceSystem crs) { // Uncomment next line if Sun fixes RFE #4093999 // ensureNonNull("crs", crs); this(crs.getCoordinateSystem().getDimension()); this.crs = crs; } /** * Constructs a new envelope with the same data than the specified envelope. * * @param envelope The envelope to copy. */ public GeneralEnvelope(final Envelope envelope) { ensureNonNull("envelope", envelope); if (envelope instanceof GeneralEnvelope) { final GeneralEnvelope e = (GeneralEnvelope) envelope; ordinates = e.ordinates.clone(); crs = e.crs; } else { crs = envelope.getCoordinateReferenceSystem(); final int dimension = envelope.getDimension(); ordinates = new double[2*dimension]; for (int i=0; i- {@linkplain #GeneralEnvelope(int) From a given number of dimension}, with all ordinates initialized to 0.
*- {@linkplain #GeneralEnvelope(GeneralDirectPosition, GeneralDirectPosition) From two coordinate points}.
*- {@linkplain #GeneralEnvelope(Envelope) From a an other envelope} (copy constructor).
*- {@linkplain #GeneralEnvelope(GeographicBoundingBox) From a geographic bounding box} * or a {@linkplain #GeneralEnvelope(Rectangle2D) Java2D rectangle}.
*- {@linkplain #GeneralEnvelope(GridEnvelope, PixelInCell, MathTransform, CoordinateReferenceSystem) * From a grid envelope} together with a Grid to CRS transform.
*- {@linkplain #GeneralEnvelope(String) From a string} representing a {@code BBOX} in * Well Known Text format.
*Well Known Text (WKT) format. The given string is typically a {@code BOX} * element like below: * * {@preformat wkt * BOX(-180 -90, 180 90) * } * * However this constructor is lenient to other geometry types like {@code POLYGON}. * Actually this constructor ignores the geometry type and just applies the following * simple rules: * *
*
*- Character sequences complying to the rules of Java identifiers are skipped.
*- Coordinates are separated by a coma ({@code ,}) character.
*- The ordinates in a coordinate are separated by a space.
*- Ordinate numbers are assumed formatted in US locale.
*- The coordinate having the highest dimension determines the dimension of this envelope.
** This constructor does not check the consistency of the provided WKT. For example it doesn't * check that every points in a {@code LINESTRING} have the same dimension. However this * constructor ensures that the parenthesis are balanced, in order to catch some malformed WKT. *
* The following examples can be parsed by this constructor in addition of the standard * {@code BOX} element. This constructor creates the bounding box of those geometries: *
*
*
* * @param wkt The {@code BOX}, {@code POLYGON} or other kind of element to parse. * @throws NumberFormatException If a number can not be parsed. * @throws IllegalArgumentException If the parenthesis are not balanced. * * @see Envelopes#parseWKT(String) * @see Envelopes#toWKT(Envelope) * * @since 3.09 */ public GeneralEnvelope(final String wkt) throws NumberFormatException, IllegalArgumentException { ensureNonNull("wkt", wkt); int levelParenth = 0; // Number of opening parenthesis: ( int levelBracket = 0; // Number of opening brackets: [ int dimLimit = 4; // The length of minimum and maximum arrays. int maxDimension = 0; // The number of valid entries in the minimum and maximum arrays. final int length = wkt.length(); double[] minimum = new double[dimLimit]; double[] maximum = new double[dimLimit]; int dimension = 0; scan: for (int i=0; i- {@code POINT(6 10)}
*- {@code MULTIPOLYGON(((1 1, 5 1, 1 5, 1 1),(2 2, 3 2, 3 3, 2 2)))}
*- {@code GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(3 8,7 10))}
*= length) break scan; while (Character.isJavaIdentifierPart(c = wkt.charAt(i))); } if (Character.isWhitespace(c)) { continue; } switch (c) { case ',': dimension=0; continue; case '(': ++levelParenth; dimension=0; continue; case '[': ++levelBracket; dimension=0; continue; case ')': if (--levelParenth<0) fail(wkt,'('); dimension=0; continue; case ']': if (--levelBracket<0) fail(wkt,'['); dimension=0; continue; } /* * At this point we have skipped the leading keyword (BOX, POLYGON, etc.), * the spaces and the parenthesis if any. We should be at the beginning of * a number. Search the first separator character (which determine the end * of the number) and parse the number. */ final int start = i; boolean flush = false; scanNumber: while (++i < length) { c = wkt.charAt(i); if (Character.isWhitespace(c)) { break; } switch (c) { case ',': flush=true; break scanNumber; case ')': if (--levelParenth<0) fail(wkt,'('); flush=true; break scanNumber; case ']': if (--levelBracket<0) fail(wkt,'['); flush=true; break scanNumber; } } final double value = Double.parseDouble(wkt.substring(start, i)); /* * Adjust the minimum and maximum value using the number that we parsed, * increasing the arrays size if necessary. Remember the maximum number * of dimensions we have found so far. */ if (dimension == maxDimension) { if (dimension == dimLimit) { dimLimit *= 2; minimum = Arrays.copyOf(minimum, dimLimit); maximum = Arrays.copyOf(maximum, dimLimit); } minimum[dimension] = maximum[dimension] = value; maxDimension = ++dimension; } else { if (value < minimum[dimension]) minimum[dimension] = value; if (value > maximum[dimension]) maximum[dimension] = value; dimension++; } if (flush) { dimension = 0; } } if (levelParenth != 0) fail(wkt, ')'); if (levelBracket != 0) fail(wkt, ']'); ordinates = XArrays.resize(minimum, maxDimension << 1); System.arraycopy(maximum, 0, ordinates, maxDimension, maxDimension); } /** * Throws an exception for unmatched parenthesis during WKT parsing. */ private static void fail(final String wkt, char missing) { throw new IllegalArgumentException(Errors.format( Errors.Keys.NON_EQUILIBRATED_PARENTHESIS_$2, wkt, missing)); } /** * Makes sure the specified dimensions are identical. */ private static void ensureSameDimension(final int dim1, final int dim2) throws MismatchedDimensionException { if (dim1 != dim2) { throw new MismatchedDimensionException(Errors.format( Errors.Keys.MISMATCHED_DIMENSION_$2, dim1, dim2)); } } /** * Checks if ordinate values in the minimum point are less than or * equal to the corresponding ordinate value in the maximum point. * * @throws IllegalArgumentException if an ordinate value in the minimum point is not less * than or equal to the corresponding ordinate value in the maximum point. */ private static void checkCoordinates(final double[] ordinates) throws IllegalArgumentException { final int dimension = ordinates.length >>> 1; for (int i=0; i max) { // We accept 'NaN' values. String message = Errors.format(Errors.Keys.ILLEGAL_ENVELOPE_ORDINATE_$1, i); message = message + ' ' + Errors.format(Errors.Keys.BAD_RANGE_$2, min, max); throw new IllegalArgumentException(message); } } } /** * Returns the given envelope as a {@code GeneralEnvelope} instance. If the given envelope * is already an instance of {@code GeneralEnvelope}, then it is returned unchanged. * Otherwise the coordinate values and the CRS of the given envelope are * {@linkplain #GeneralEnvelope(Envelope) copied} in a new {@code GeneralEnvelope}. * * @param envelope The envelope to cast, or {@code null}. * @return The values of the given envelope as a {@code GeneralEnvelope} instance. * * @since 3.19 */ public static GeneralEnvelope castOrCopy(final Envelope envelope) { if (envelope == null || envelope instanceof GeneralEnvelope) { return (GeneralEnvelope) envelope; } return new GeneralEnvelope(envelope); } /** * Returns the number of dimensions. */ @Override public final int getDimension() { return ordinates.length >>> 1; } /** * Returns the coordinate reference system in which the coordinates are given. * * @return The coordinate reference system, or {@code null}. */ @Override public final CoordinateReferenceSystem getCoordinateReferenceSystem() { assert crs == null || crs.getCoordinateSystem().getDimension() == getDimension(); return crs; } /** * Sets the coordinate reference system in which the coordinate are given. * This method does not reproject the envelope, and do not * check if the envelope is contained in the new domain of validity. The * later can be enforced by a call to {@link #reduceToDomain(boolean)}. * * 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}. * @throws MismatchedDimensionException if the specified CRS doesn't have the expected * number of dimensions. */ public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) throws MismatchedDimensionException { AbstractDirectPosition.checkCoordinateReferenceSystemDimension(crs, getDimension()); this.crs = crs; } /** * Restricts this envelope to the CS or CRS * {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain of validity}. * This method performs two steps: * *
*
* * Example: A longitude range of [185° … 190°] is equivalent to [-175° … -170°]. * But [175° … 185°] would be equivalent to [175° … -175°], which is likely to mislead *- *
Ensure that the envelope is contained in the {@linkplain CoordinateSystem * coordinate system} domain. If some ordinates are out of range, then there is a choice * depending on the {@linkplain CoordinateSystemAxis#getRangeMeaning range meaning}: *
*
*- If {@linkplain RangeMeaning#EXACT EXACT} (typically latitudes ordinates), * values greater than the {@linkplain CoordinateSystemAxis#getMaximumValue maximum value} * are replaced by the maximum, and values smaller than the * {@linkplain CoordinateSystemAxis#getMinimumValue minimum value} * are replaced by the minimum.
* *- If {@linkplain RangeMeaning#WRAPAROUND WRAPAROUND} (typically longitudes * ordinates), a multiple of the range (e.g. 360° for longitudes) is added or subtracted. * If a value stay out of range after this correction, then the ordinates are set to the * full [{@linkplain CoordinateSystemAxis#getMinimumValue minimum} … * {@linkplain CoordinateSystemAxis#getMaximumValue maximum}] range. * See the example below.
*- *
If {@code crsDomain} is {@code true}, then the envelope from the previous step * is intersected with the CRS {@linkplain CoordinateReferenceSystem#getDomainOfValidity * domain of validity}, if any. *
Envelope
users since the lower bounds is numerically greater than the upper bounds. * Reordering as [-175° … 175°] would interchange the meaning of what is "inside" and "outside" * the envelope. So this implementation conservatively expands the range to [-180° … 180°] * in order to ensure that the validated envelope fully contains the original envelope. * * @param useDomainOfCRS {@code true} if the envelope should be restricted to * the CRS domain of validity in addition to the CS domain. * @return {@code true} if this envelope has been modified, or {@code false} if no change * was done. * * @since 3.11 (derived from 2.5) */ public boolean reduceToDomain(final boolean useDomainOfCRS) { boolean changed = false; if (crs != null) { final int dimension = ordinates.length >>> 1; final CoordinateSystem cs = crs.getCoordinateSystem(); for (int i=0; imaximum) {ordinates[j] = maximum; changed = true;} } else if (RangeMeaning.WRAPAROUND.equals(rm)) { final double length = maximum - minimum; if (length > 0 && length < Double.POSITIVE_INFINITY) { final double offset = Math.floor((ordinates[i] - minimum) / length) * length; if (offset != 0) { ordinates[i] -= offset; ordinates[j] -= offset; changed = true; } if (ordinates[j] > maximum) { ordinates[i] = minimum; // See method Javadoc ordinates[j] = maximum; changed = true; } } } } if (useDomainOfCRS) { final Envelope domain = Envelopes.getDomainOfValidity(crs); if (domain != null) { final CoordinateReferenceSystem domainCRS = domain.getCoordinateReferenceSystem(); if (domainCRS == null) { intersect(domain); } else { /* * The domain may have fewer dimensions than this envelope (typically only * the ones relative to horizontal dimensions). We can rely on directions * for matching axis since CRS.getEnvelope(crs) should have transformed the * domain to this envelope CRS. */ final CoordinateSystem domainCS = domainCRS.getCoordinateSystem(); final int domainDimension = domainCS.getDimension(); for (int i=0; i maximum) ordinates[k] = maximum; } } } } } } } return changed; } /** * Fixes rounding errors up to a given tolerance level. For each value {@code ordinates[i]} * at dimension i, this method multiplies the ordinate value by the given factor, * then round the result only if the product is close to an integer value. The threshold is * defined by the {@code maxULP} argument in ULP units (Unit in the Last Place). * If and only if the product has been rounded, it is divided by the factor and stored in this * envelope in place of the original ordinate. * * The code below illustrates the work done on every ordinate values, omitting * (for simplicity) the fact that {@code ordinate[i]} is left unchanged if * {@code XMath.roundIfAlmostInteger} didn't rounded the product. * * {@preformat java * ordinates[i] = XMath.roundIfAlmostInteger(ordinates[i]*factor, maxULP) / factor; * } * * This method is useful after envelope calculations subject to rounding errors, like the * {@link #GeneralEnvelope(GridEnvelope, PixelInCell, MathTransform, CoordinateReferenceSystem)} * constructor. * * @param factor The factor by which to multiply ordinates before rounding * and divide after rounding. A recommended value is 360. * @param maxULP The maximal change allowed in ULPs (Unit in the Last Place). * * @see XMath#roundIfAlmostInteger(double, int) * * @since 3.11 */ public void roundIfAlmostInteger(final double factor, final int maxULP) { if (!(factor > 0)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "factor", factor)); } for (int i=0; i
>> 1; final GeneralDirectPosition position = new GeneralDirectPosition(dim); System.arraycopy(ordinates, 0, position.ordinates, 0, dim); position.setCoordinateReferenceSystem(crs); return position; } /** * A coordinate position consisting of all the {@linkplain #getMaximum maximal ordinates} * for each dimension for all points within the {@code Envelope}. * * @return The upper corner. */ @Override public DirectPosition getUpperCorner() { final int dim = ordinates.length >>> 1; final GeneralDirectPosition position = new GeneralDirectPosition(dim); System.arraycopy(ordinates, dim, position.ordinates, 0, dim); position.setCoordinateReferenceSystem(crs); return position; } /** * A coordinate position consisting of all the {@linkplain #getMedian(int) middle ordinates} * for each dimension for all points within the {@code Envelope}. * * @return The median coordinates. * * @since 2.5 */ public DirectPosition getMedian() { final GeneralDirectPosition position = new GeneralDirectPosition(ordinates.length >>> 1); for (int i=position.ordinates.length; --i>=0;) { position.ordinates[i] = getMedian(i); } position.setCoordinateReferenceSystem(crs); return position; } /** * 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. */ @Override public final double getMinimum(final int dimension) throws IndexOutOfBoundsException { ensureValidIndex(ordinates.length >>> 1, dimension); return ordinates[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. */ @Override public final double getMaximum(final int dimension) throws IndexOutOfBoundsException { ensureValidIndex(ordinates.length >>> 1, dimension); return ordinates[dimension + (ordinates.length >>> 1)]; } /** * Returns the median ordinate along the specified dimension. The result should be equals * (minus rounding error) to ({@linkplain #getMaximum getMaximum}(dimension) - * {@linkplain #getMinimum getMinimum}(dimension)) / 2
. * * @param dimension The dimension to query. * @return The mid ordinate value along the given dimension. * @throws IndexOutOfBoundsException If the given index is out of bounds. */ @Override public final double getMedian(final int dimension) throws IndexOutOfBoundsException { return 0.5*(ordinates[dimension] + ordinates[dimension + (ordinates.length >>> 1)]); } /** * Returns the envelope span (typically width or height) along the specified dimension. * The result should be equals (minus rounding error) to{@linkplain #getMaximum * getMaximum}(dimension) - {@linkplain #getMinimum getMinimum}(dimension)
. * * @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. */ @Override public final double getSpan(final int dimension) throws IndexOutOfBoundsException { return ordinates[dimension + (ordinates.length >>> 1)] - ordinates[dimension]; } /** * Returns the envelope span along the specified dimension, in terms of the given units. * * @param dimension The dimension to query. * @param unit The unit for the return value. * @return The span in terms of the given unit. * @throws IndexOutOfBoundsException If the given index is out of bounds. * @throws ConversionException if the length can't be converted to the specified units. * * @since 2.5 */ public double getSpan(final int dimension, final Unit> unit) throws IndexOutOfBoundsException, ConversionException { double value = getSpan(dimension); if (crs != null) { final Unit> source = crs.getCoordinateSystem().getAxis(dimension).getUnit(); if (source != null) { value = source.getConverterToAny(unit).convert(value); } } return value; } /** * 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. */ public void setRange(final int dimension, double minimum, double maximum) throws IndexOutOfBoundsException { if (minimum > maximum) { // Make an empty envelope (min == max) // while keeping it legal (min <= max). minimum = maximum = 0.5 * (minimum + maximum); } ensureValidIndex(ordinates.length >>> 1, dimension); ordinates[dimension + (ordinates.length >>> 1)] = maximum; ordinates[dimension] = minimum; } /** * Sets the envelope to the specified values, which must be the lower corner coordinates * followed by upper corner coordinates. The number of arguments provided shall be twice * this {@linkplain #getDimension envelope dimension}, and minimum shall not be greater * than maximum. ** Example: * (xmin, ymin, zmin, * xmax, ymax, zmax) * * @param ordinates The new ordinate values. * * @since 2.5 */ public void setEnvelope(final double... ordinates) { if ((ordinates.length & 1) != 0) { throw new IllegalArgumentException(Errors.format( Errors.Keys.ODD_ARRAY_LENGTH_$1, ordinates.length)); } final int dimension = ordinates.length >>> 1; final int check = this.ordinates.length >>> 1; if (dimension != check) { throw new MismatchedDimensionException(Errors.format( Errors.Keys.MISMATCHED_DIMENSION_$3, "ordinates", dimension, check)); } checkCoordinates(ordinates); System.arraycopy(ordinates, 0, this.ordinates, 0, ordinates.length); } /** * Sets this envelope to the same coordinate values than the specified envelope. * If the given envelope 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 envelope The envelope to copy coordinates from. * @throws MismatchedDimensionException if the specified envelope doesn't have the expected * number of dimensions. * * @since 2.2 */ public void setEnvelope(final Envelope envelope) throws MismatchedDimensionException { ensureNonNull("envelope", envelope); final int dimension = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dimension); if (envelope instanceof GeneralEnvelope) { System.arraycopy(((GeneralEnvelope) envelope).ordinates, 0, ordinates, 0, ordinates.length); } else { for (int i=0; i
lower + {@linkplain #getDimension()} * This method ignores the Coordinate Reference System of {@code this} and the given envelope. * * @param envelope The envelope to copy coordinates from. * @param offset Index of the first dimension to write in this envelope. * @throws IndexOutOfBoundsException If the given offset is negative, or is greater than *
getDimension() - envelope.getDimension()
. * * @since 3.16 */ public void setSubEnvelope(final Envelope envelope, int offset) throws IndexOutOfBoundsException { ensureNonNull("envelope", envelope); final int subDim = envelope.getDimension(); final int dimension = ordinates.length >>> 1; if (offset < 0 || offset + subDim > dimension) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "lower", offset)); } for (int i=0; i>> 1; final int newDim = upper-lower; if (lower<0 || lower>curDim) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "lower", lower)); } if (newDim<0 || upper>curDim) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "upper", upper)); } final GeneralEnvelope envelope = new GeneralEnvelope(newDim); System.arraycopy(ordinates, lower, envelope.ordinates, 0, newDim); System.arraycopy(ordinates, lower+curDim, envelope.ordinates, newDim, newDim); return envelope; } /** * Returns a new envelope with the same values than this envelope minus the * specified range of dimensions. * * @param lower The first dimension to omit, inclusive. * @param upper The last dimension to omit, exclusive. * @return The sub-envelope. * @throws IndexOutOfBoundsException if an index is out of bounds. */ public GeneralEnvelope getReducedEnvelope(final int lower, final int upper) throws IndexOutOfBoundsException { final int curDim = ordinates.length >>> 1; final int rmvDim = upper-lower; if (lower<0 || lower>curDim) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "lower", lower)); } if (rmvDim<0 || upper>curDim) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.ILLEGAL_ARGUMENT_$2, "upper", upper)); } final GeneralEnvelope envelope = new GeneralEnvelope(curDim - rmvDim); System.arraycopy(ordinates, 0, envelope.ordinates, 0, lower); System.arraycopy(ordinates, lower, envelope.ordinates, upper, curDim-upper); return envelope; } /** * Sets the lower corner to {@linkplain Double#NEGATIVE_INFINITY negative infinity} * and the upper corner to {@linkplain Double#POSITIVE_INFINITY positive infinity}. * The {@linkplain #getCoordinateReferenceSystem coordinate reference system} (if any) * stay unchanged. * * @since 2.2 */ public void setToInfinite() { final int mid = ordinates.length >>> 1; Arrays.fill(ordinates, 0, mid, Double.NEGATIVE_INFINITY); Arrays.fill(ordinates, mid, ordinates.length, Double.POSITIVE_INFINITY); assert isInfinite() : this; } /** * Returns {@code true} if at least one ordinate has an * {@linkplain Double#isInfinite infinite} value. * * @return {@code true} if this envelope has infinite value. * * @since 2.2 */ public boolean isInfinite() { for (int i=0; i * *
* * @return {@code true} if this envelope has NaN values. * * @since 2.2 */ public boolean isNull() { for (int i=0; i- If
*isNull() == true
, then{@linkplain #isEmpty()} == true
- If
*{@linkplain #isEmpty()} == false
, thenisNull() == false
- The converse of the above-cited rules are not always true.
*>> 1; if (dimension == 0) { return true; } for (int i=0; i =0;) { if (Double.isNaN(position.getOrdinate(i))) { return true; } } return false; } /** * Returns {@code true} if at least one ordinate in the given envelope * is {@link Double#NaN}. This is used for assertions only. */ private static boolean hasNaN(final Envelope envelope) { return hasNaN(envelope.getLowerCorner()) || hasNaN(envelope.getUpperCorner()); } /** * Adds a point to this envelope. The resulting envelope is the smallest envelope that * contains both the original envelope and the specified point. After adding a point, * a call to {@link #contains} with the added point as an argument will return {@code true}, * except if one of the point ordinates was {@link Double#NaN} (in which case the * corresponding ordinate have been ignored). * * {@note This method assumes that the specified point uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param position The point to add. * @throws MismatchedDimensionException if the specified point doesn't have * the expected dimension. */ public void add(final DirectPosition position) throws MismatchedDimensionException { ensureNonNull("position", position); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("position", position.getDimension(), dim); assert equalsIgnoreMetadata(crs, position.getCoordinateReferenceSystem()) : position; for (int i=0; i ordinates[i+dim]) ordinates[i+dim] = value; } assert isEmpty() || contains(position) || hasNaN(position) : position; } /** * Adds an envelope object to this envelope. The resulting envelope is the union of the * two {@code Envelope} objects. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param envelope the {@code Envelope} to add to this envelope. * @throws MismatchedDimensionException if the specified envelope doesn't * have the expected dimension. */ public void add(final Envelope envelope) throws MismatchedDimensionException { ensureNonNull("envelope", envelope); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim); assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope; for (int i=0; i ordinates[i+dim]) ordinates[i+dim] = max; } assert isEmpty() || contains(envelope, true) || hasNaN(envelope) : envelope; } /** * Tests if a specified coordinate is inside the boundary of this envelope. * If it least one ordinate value in the given point is {@link Double#NaN NaN}, * then this method returns {@code false}. * * {@note This method assumes that the specified point uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param position The point to text. * @return {@code true} if the specified coordinates are inside the boundary * of this envelope; {@code false} otherwise. * @throws MismatchedDimensionException if the specified point doesn't have * the expected dimension. */ public boolean contains(final DirectPosition position) throws MismatchedDimensionException { ensureNonNull("position", position); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("point", position.getDimension(), dim); assert equalsIgnoreMetadata(crs, position.getCoordinateReferenceSystem()) : position; for (int i=0; i = ordinates[i ])) return false; if (!(value <= ordinates[i+dim])) return false; // Use '!' in order to take 'NaN' in account. } return true; } /** * Returns {@code true} if this envelope completely encloses the specified envelope. * If one or more edges from the specified envelope coincide with an edge from this * envelope, then this method returns {@code true} only if {@code edgesInclusive} * is {@code true}. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param envelope The envelope to test for inclusion. * @param edgesInclusive {@code true} if this envelope edges are inclusive. * @return {@code true} if this envelope completely encloses the specified one. * @throws MismatchedDimensionException if the specified envelope doesn't have * the expected dimension. * * @see #intersects(Envelope, boolean) * @see #equals(Envelope, double, boolean) * * @since 2.2 */ public boolean contains(final Envelope envelope, final boolean edgesInclusive) throws MismatchedDimensionException { ensureNonNull("envelope", envelope); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim); assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope; for (int i=0; i = outer : inner > outer)) { // ! is for catching NaN. return false; } inner = envelope.getMaximum(i); outer = ordinates[i+dim]; if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN. return false; } } assert intersects(envelope, edgesInclusive) || hasNaN(envelope) : envelope; return true; } /** * Returns {@code true} if this envelope intersects the specified envelope. * If one or more edges from the specified envelope coincide with an edge from this * envelope, then this method returns {@code true} only if {@code edgesInclusive} * is {@code true}. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param envelope The envelope to test for intersection. * @param edgesInclusive {@code true} if this envelope edges are inclusive. * @return {@code true} if this envelope intersects the specified one. * @throws MismatchedDimensionException if the specified envelope doesn't have * the expected dimension. * * @see #contains(Envelope, boolean) * @see #equals(Envelope, double, boolean) * * @since 2.2 */ public boolean intersects(final Envelope envelope, final boolean edgesInclusive) throws MismatchedDimensionException { ensureNonNull("envelope", envelope); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim); assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope; for (int i=0; i = outer : inner > outer)) { // ! is for catching NaN. return false; } inner = envelope.getMinimum(i); outer = ordinates[i+dim]; if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN. return false; } } return true; } /** * Sets this envelope to the intersection if this envelope with the specified one. * * {@note This method assumes that the specified envelope uses the same CRS than this envelope. * For performance reason, it will no be verified unless Java assertions are enabled.} * * @param envelope the {@code Envelope} to intersect to this envelope. * @throws MismatchedDimensionException if the specified envelope doesn't * have the expected dimension. */ public void intersect(final Envelope envelope) throws MismatchedDimensionException { ensureNonNull("envelope", envelope); final int dim = ordinates.length >>> 1; AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim); assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope; for (int i=0; i max) { // Make an empty envelope (min==max) // while keeping it legal (min<=max). min = max = 0.5*(min+max); } ordinates[i ] = min; ordinates[i+dim] = max; } } /** * Returns a {@link Rectangle2D} with the same bounds as this {@code Envelope}. * This envelope must be two-dimensional before this method is invoked. * This is a convenience method for inter-operability with Java2D. * * @return This envelope as a two-dimensional rectangle. * @throws IllegalStateException if this envelope is not two-dimensional. */ public Rectangle2D toRectangle2D() throws IllegalStateException { /* * NOTE: if the type created below is changed to something else than XRectangle2D, then we * must perform a usage search because some client code cast the returned object to * XRectangle2D when this envelope is known to not be a subclass of GeneralEnvelope. */ if (ordinates.length == 4) { return XRectangle2D.createFromExtremums(ordinates[0], ordinates[1], ordinates[2], ordinates[3]); } else { throw new IllegalStateException(Errors.format( Errors.Keys.NOT_TWO_DIMENSIONAL_$1, getDimension())); } } /** * Returns a hash value for this envelope. */ @Override public int hashCode() { int code = Arrays.hashCode(ordinates); if (crs != null) { code += crs.hashCode(); } assert code == super.hashCode(); return code; } /** * Compares the specified object with this envelope for equality. */ @Override public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { final GeneralEnvelope that = (GeneralEnvelope) object; return Arrays.equals(this.ordinates, that.ordinates) && Utilities.equals(this.crs, that.crs); } return false; } /** * Returns a deep copy of this envelope. * * @return A clone of this envelope. */ @Override public GeneralEnvelope clone() { try { Field field = ordinatesField; if (field == null) { field = GeneralEnvelope.class.getDeclaredField("ordinates"); field.setAccessible(true); ordinatesField = field; } GeneralEnvelope e = (GeneralEnvelope) super.clone(); field.set(e, ordinates.clone()); return e; } catch (CloneNotSupportedException exception) { // Should not happen, since we are cloneable. throw new AssertionError(exception); } catch (NoSuchFieldException exception) { // Should not happen, since the field is known to exist. throw new AssertionError(exception); } catch (IllegalAccessException exception) { // Should not happen, since we made the field accessible. throw new AssertionError(exception); } // TODO: Use multi-catch with Java 7. } }