/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2016, 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.jts; import org.geotools.geometry.DirectPosition3D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.referencing.CRS; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.opengis.geometry.BoundingBox; import org.opengis.geometry.BoundingBox3D; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; /** * A 3D envelope associated with a * {@linkplain CoordinateReferenceSystem coordinate reference system}. In * addition, this JTS envelope also implements the GeoAPI * {@linkplain org.opengis.geometry.Envelope envelope} interface * for interoperability with GeoAPI. * * * @source $URL$ * @version $Id$ * @author Niels Charlier * */ public class ReferencedEnvelope3D extends ReferencedEnvelope implements BoundingBox3D { /** * Serial number for compatibility with different versions. */ private static final long serialVersionUID = -3188702602373537163L; /** * Test the point q to see whether it intersects the Envelope defined by * p1-p2 * * @param p1 * one extremal point of the envelope * @param p2 * another extremal point of the envelope * @param q * the point to test for intersection * @return <code>true</code> if q intersects the envelope p1-p2 */ public static boolean intersects(Coordinate p1, Coordinate p2, Coordinate q) { // OptimizeIt shows that Math#min and Math#max here are a bottleneck. // Replace with direct comparisons. [Jon Aquino] if (((q.x >= (p1.x < p2.x ? p1.x : p2.x)) && (q.x <= (p1.x > p2.x ? p1.x : p2.x))) && ((q.y >= (p1.y < p2.y ? p1.y : p2.y)) && (q.y <= (p1.y > p2.y ? p1.y : p2.y)))) { return true; } return false; } /** * Test the envelope defined by p1-p2 for intersection with the envelope * defined by q1-q2 * * @param p1 * one extremal point of the envelope P * @param p2 * another extremal point of the envelope P * @param q1 * one extremal point of the envelope Q * @param q2 * another extremal point of the envelope Q * @return <code>true</code> if Q intersects P */ public static boolean intersects(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) { double minq = Math.min(q1.x, q2.x); double maxq = Math.max(q1.x, q2.x); double minp = Math.min(p1.x, p2.x); double maxp = Math.max(p1.x, p2.x); if (minp > maxq) return false; if (maxp < minq) return false; minq = Math.min(q1.y, q2.y); maxq = Math.max(q1.y, q2.y); minp = Math.min(p1.y, p2.y); maxp = Math.max(p1.y, p2.y); if (minp > maxq) return false; if (maxp < minq) return false; return true; } /** * the minimum z-coordinate */ private double minz; /** * the maximum z-coordinate */ private double maxz; /** * Initialize to a null <code>Envelope</code>. */ public void init() { setToNull(); } /** * Initialize an <code>Envelope</code> for a region defined by maximum and * minimum values. * * @param x1 * the first x-value * @param x2 * the second x-value * @param y1 * the first y-value * @param y2 * the second y-value * @param z1 * the first z-value * @param z2 * the second z-value */ public void init(double x1, double x2, double y1, double y2, double z1, double z2) { init(x1, x2, y1, y2); if (z1 < z2) { minz = z1; maxz = z2; } else { minz = z2; maxz = z1; } } /** * Initialize an <code>Envelope</code> to a region defined by two * Coordinates. * * @param p1 * the first Coordinate * @param p2 * the second Coordinate */ public void init(Coordinate p1, Coordinate p2) { init(p1.x, p2.x, p1.y, p2.y, p1.z, p2.z); } /** * Initialize an <code>Envelope</code> to a region defined by a single * Coordinate. * * @param p * the coordinate */ public void init(Coordinate p) { init(p.x, p.x, p.y, p.y, p.z, p.z); } @Override public void init(Envelope env) { super.init(env); if( env instanceof BoundingBox3D ){ this.minz = ((BoundingBox3D)env).getMinZ(); this.maxz = ((BoundingBox3D)env).getMaxZ(); } } /** * Initialize an <code>Envelope</code> from an existing 3D Envelope. * * @param env * the 3D Envelope to initialize from */ public void init(ReferencedEnvelope3D env) { super.init( (Envelope) env); this.minz = env.minz; this.maxz = env.maxz; } /** * Makes this <code>Envelope</code> a "null" envelope, that is, the envelope * of the empty geometry. */ public void setToNull() { super.setToNull(); minz = 0; maxz = -1; } /** * Returns the difference between the maximum and minimum z values. * * @return max z - min z, or 0 if this is a null <code>Envelope</code> */ public double getDepth() { if (isNull()) { return 0; } return maxz - minz; } /** * Returns the <code>Envelope</code>s minimum z-value. min z > max z * indicates that this is a null <code>Envelope</code>. * * @return the minimum z-coordinate */ public double getMinZ() { return minz; } /** * Returns the <code>Envelope</code>s maximum z-value. min z > max z * indicates that this is a null <code>Envelope</code>. * * @return the maximum z-coordinate */ public double getMaxZ() { return maxz; } /** * Gets the volume of this envelope. * * @return the volume of the envelope * @return 0.0 if the envelope is null */ public double getVolume() { return getWidth() * getHeight() * getDepth(); } /** * Gets the minimum extent of this envelope across all three dimensions. * * @return the minimum extent of this envelope */ public double minExtent() { if (isNull()) return 0.0; return Math.min(getWidth(), Math.min(getHeight(), getDepth())); } /** * Gets the maximum extent of this envelope across both dimensions. * * @return the maximum extent of this envelope */ public double maxExtent() { if (isNull()) return 0.0; return Math.max(getWidth(), Math.max(getHeight(), getDepth())); } /** * Enlarges this <code>Envelope</code> so that it contains the given * {@link Coordinate}. Has no effect if the point is already on or within * the envelope. * * @param p * the Coordinate to expand to include */ public void expandToInclude(Coordinate p) { expandToInclude(p.x, p.y, p.z); } /** * Expands this envelope by a given distance in all directions. Both * positive and negative distances are supported. * * @param distance * the distance to expand the envelope */ public void expandBy(double distance) { expandBy(distance, distance, distance); } /** * Expands this envelope by a given distance in all directions. Both * positive and negative distances are supported. * * @param deltaX * the distance to expand the envelope along the the X axis * @param deltaY * the distance to expand the envelope along the the Y axis */ public void expandBy(double deltaX, double deltaY, double deltaZ) { if (isNull()) return; minz -= deltaZ; maxz += deltaZ; expandBy(deltaX, deltaY); // check for envelope disappearing if (minz > maxz) setToNull(); } /** * Enlarges this <code>Envelope</code> so that it contains the given point. * Has no effect if the point is already on or within the envelope. * * @param x * the value to lower the minimum x to or to raise the maximum x * to * @param y * the value to lower the minimum y to or to raise the maximum y * to * @param z * the value to lower the minimum z to or to raise the maximum z * to */ public void expandToInclude(double x, double y, double z) { if (isNull()) { expandToInclude(x,y); minz = z; maxz = z; } else { expandToInclude(x,y); if (z < minz) { minz = z; } if (z > maxz) { maxz = z; } } } @Override public void expandToInclude(DirectPosition pt ){ double x = pt.getOrdinate(0); double y = pt.getOrdinate(1); double z = pt.getDimension()>=3 ? pt.getOrdinate(2) : Double.NaN; expandToInclude(x,y,z); } /** * Translates this envelope by given amounts in the X and Y direction. * * @param transX * the amount to translate along the X axis * @param transY * the amount to translate along the Y axis * @param transZ * the amount to translate along the Z axis */ public void translate(double transX, double transY, double transZ) { if (isNull()) { return; } init(getMinX() + transX, getMaxX() + transX, getMinY() + transY, getMaxY() + transY, getMinZ() + transZ, getMaxZ() + transZ); } /** * Computes the coordinate of the centre of this envelope (as long as it is * non-null * * @return the centre coordinate of this envelope <code>null</code> if the * envelope is null */ public Coordinate centre() { if (isNull()) return null; return new Coordinate((getMinX() + getMaxX()) / 2.0, (getMinY() + getMaxY()) / 2.0, (getMinZ() + getMaxZ()) / 2.0); } /** * Check if the region defined by <code>other</code> overlaps (intersects) * the region of this <code>Envelope</code>. * * @param other * the <code>Envelope</code> which this <code>Envelope</code> is * being checked for overlapping * @return <code>true</code> if the <code>Envelope</code>s overlap */ public boolean intersects(ReferencedEnvelope3D other) { if (isNull() || other.isNull()) { return false; } return super.intersects((Envelope) other) && !(other.minz > maxz || other.maxz < minz); } /** * @deprecated Use #intersects instead. In the future, #overlaps may be * changed to be a true overlap check; that is, whether the * intersection is two-dimensional. */ public boolean overlaps(ReferencedEnvelope3D other) { return intersects(other); } /** * Check if the point <code>p</code> overlaps (lies inside) the region of * this <code>Envelope</code>. * * @param p * the <code>Coordinate</code> to be tested * @return <code>true</code> if the point overlaps this * <code>Envelope</code> */ public boolean intersects(Coordinate p) { return intersects(p.x, p.y, p.z); } /** * @deprecated Use #intersects instead. */ public boolean overlaps(Coordinate p) { return intersects(p); } /** * Check if the point <code>(x, y)</code> overlaps (lies inside) the region * of this <code>Envelope</code>. * * @param x * the x-ordinate of the point * @param y * the y-ordinate of the point * @param z * the z-ordinate of the point * @return <code>true</code> if the point overlaps this * <code>Envelope</code> */ public boolean intersects(double x, double y, double z) { if (isNull()) return false; return intersects(x,y) && !(z > maxz || z > maxz); } /** * @deprecated Use #intersects instead. */ public boolean overlaps(double x, double y, double z) { return intersects(x, y, z); } /** * Tests if the given point lies in or on the envelope. * <p> * Note that this is <b>not</b> the same definition as the SFS * <tt>contains</tt>, which would exclude the envelope boundary. * * @param p * the point which this <code>Envelope</code> is being checked * for containing * @return <code>true</code> if the point lies in the interior or on the * boundary of this <code>Envelope</code>. * * @see #covers(Coordinate) */ public boolean contains(Coordinate p) { return covers(p); } /** * Tests if the given point lies in or on the envelope. * <p> * Note that this is <b>not</b> the same definition as the SFS * <tt>contains</tt>, which would exclude the envelope boundary. * * @param x * the x-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @param y * the y-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @return <code>true</code> if <code>(x, y)</code> lies in the interior or * on the boundary of this <code>Envelope</code>. * * @see #covers(double, double) */ public boolean contains(double x, double y, double z) { return covers(x, y, z); } /** * Tests if the given point lies in or on the envelope. * * @param x * the x-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @param y * the y-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @return <code>true</code> if <code>(x, y)</code> lies in the interior or * on the boundary of this <code>Envelope</code>. */ public boolean covers(double x, double y, double z) { if (isNull()) return false; return covers(x,y) && z >= minz && z <= maxz; } /** * Tests if the given point lies in or on the envelope. * * @param p * the point which this <code>Envelope</code> is being checked * for containing * @return <code>true</code> if the point lies in the interior or on the * boundary of this <code>Envelope</code>. */ public boolean covers(Coordinate p) { return covers(p.x, p.y, p.z); } /** * Tests if the <code>Envelope other</code> lies wholely inside this * <code>Envelope</code> (inclusive of the boundary). * * @param other * the <code>Envelope</code> to check * @return true if this <code>Envelope</code> covers the <code>other</code> */ public boolean covers(ReferencedEnvelope3D other) { if (isNull() || other.isNull()) { return false; } return super.covers(other) && other.getMinZ() >= minz && other.getMaxZ() <= maxz; } /** * Computes the distance between this and another <code>Envelope</code>. The * distance between overlapping Envelopes is 0. Otherwise, the distance is * the Euclidean distance between the closest points. */ public double distance(ReferencedEnvelope3D env) { if (intersects(env)) return 0; double dx = 0.0; if (getMaxX() < env.getMinX()) dx = env.getMinX() - getMaxX(); else if (getMinX() > env.getMaxX()) dx = getMinX() - env.getMaxX(); double dy = 0.0; if (getMaxY() < env.getMinY()) dy = env.getMinY() - getMaxY(); else if (getMinY() > env.getMaxY()) dy = getMinY() - env.getMaxY(); double dz = 0.0; if (maxz < env.minz) dz = env.minz - maxz; else if (minz > env.maxz) dz = minz - env.maxz; // if either is zero, the envelopes overlap either vertically or // horizontally if (dx == 0.0 && dz == 0) return dy; if (dy == 0.0 && dz ==0) return dx; if (dx == 0 && dy == 0) return dz; return Math.sqrt(dx * dx + dy * dy + dz * dz); } //--------------------------------------------------------------------------------------------------------------- /** A ReferencedEnvelope containing "everything" */ public static ReferencedEnvelope3D EVERYTHING = new ReferencedEnvelope3D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null) { private static final long serialVersionUID = -3188702602373537164L; public boolean contains(BoundingBox bbox) { return true; } public boolean contains(Coordinate p) { return true; } public boolean contains(DirectPosition pos) { return true; } public boolean contains(double x, double y, double z) { return true; } public boolean isEmpty() { return false; } public boolean isNull() { return true; } public double getArea() { //return super.getArea(); return Double.POSITIVE_INFINITY; } public double getVolume() { //return super.getArea(); return Double.POSITIVE_INFINITY; } public void setBounds(BoundingBox3D arg0) { throw new IllegalStateException("Cannot modify ReferencedEnvelope.EVERYTHING"); } public Coordinate centre() { return new Coordinate(); } public void setToNull() { // um ignore this as we are already "null" } public boolean equals(Object obj) { if( obj == EVERYTHING ){ return true; } if( obj instanceof ReferencedEnvelope3D ){ ReferencedEnvelope3D other = (ReferencedEnvelope3D) obj; if( other.crs != EVERYTHING.crs ) return false; if( other.getMinX() != EVERYTHING.getMinX() ) return false; if( other.getMinY() != EVERYTHING.getMinY() ) return false; if( other.getMinZ() != EVERYTHING.getMinZ() ) return false; if( other.getMaxX() != EVERYTHING.getMaxX() ) return false; if( other.getMaxY() != EVERYTHING.getMaxY() ) return false; if( other.getMaxZ() != EVERYTHING.getMaxZ() ) return false; return true; } return super.equals(obj); } public String toString() { return "ReferencedEnvelope.EVERYTHING"; } }; /** * Creates a null envelope with a null coordinate reference system. */ public ReferencedEnvelope3D() { this((CoordinateReferenceSystem) null); } /** * Creates a null envelope with the specified coordinate reference system. * * @param crs The coordinate reference system. * @throws MismatchedDimensionException if the CRS dimension is not valid. */ public ReferencedEnvelope3D(CoordinateReferenceSystem crs) throws MismatchedDimensionException { this.crs = crs; checkCoordinateReferenceSystemDimension(); } /** * Creates an envelope for a region defined by maximum and minimum values. * * @param x1 The first x-value. * @param x2 The second x-value. * @param y1 The first y-value. * @param y2 The second y-value. * @param z1 The first y-value. * @param z2 The second y-value. * @param crs The coordinate reference system. * * @throws MismatchedDimensionException if the CRS dimension is not valid. */ public ReferencedEnvelope3D(final double x1, final double x2, final double y1, final double y2, final double z1, final double z2, final CoordinateReferenceSystem crs) throws MismatchedDimensionException { init(x1, x2, y1, y2, z1, z2); this.crs = crs; checkCoordinateReferenceSystemDimension(); } /** * Creates a new envelope from an existing envelope. * * @param envelope The envelope to initialize from * @throws MismatchedDimensionException if the CRS dimension is not valid. * */ public ReferencedEnvelope3D(final ReferencedEnvelope3D envelope) throws MismatchedDimensionException { init(envelope); crs = envelope.getCoordinateReferenceSystem(); checkCoordinateReferenceSystemDimension(); } /** * Creates a new envelope from an existing bounding box. * * <p> NOTE: if the bounding box is empty, the resulting ReferencedEnvelope will not be. In * case this is needed use {@link #create(org.opengis.geometry.Envelope, * CoordinateReferenceSystem) ReferencedEnvelope.create(bbox, * bbox.getCoordinateReferenceSystem())} * * @param bbox The bounding box to initialize from. * @throws MismatchedDimensionException if the CRS dimension is not valid. * */ public ReferencedEnvelope3D(final BoundingBox3D bbox) throws MismatchedDimensionException { this(bbox.getMinX(), bbox.getMaxX(), bbox.getMinY(), bbox.getMaxY(), bbox.getMinZ(), bbox.getMaxZ(), bbox.getCoordinateReferenceSystem()); } /** * Creates a new envelope from an existing JTS envelope. * * @param envelope The envelope to initialize from. * @param crs The coordinate reference system. * @throws MismatchedDimensionExceptionif the CRS dimension is not valid. */ public ReferencedEnvelope3D(final Envelope envelope, final CoordinateReferenceSystem crs) throws MismatchedDimensionException { super(envelope, crs ); if( envelope instanceof ReferencedEnvelope3D ){ this.minz = ((ReferencedEnvelope3D)envelope).getMinZ(); this.maxz = ((ReferencedEnvelope3D)envelope).getMaxZ(); } } /** * Creates a new envelope from an existing OGC envelope. * * <p> NOTE: if the envelope is empty, the resulting ReferencedEnvelope will not be. In * case this is needed use {@link #create(org.opengis.geometry.Envelope, * CoordinateReferenceSystem) ReferencedEnvelope.create(envelope, * envelope.getCoordinateReferenceSystem())} * * @param envelope The envelope to initialize from. * @throws MismatchedDimensionException if the CRS dimension is not valid. * */ public ReferencedEnvelope3D(final org.opengis.geometry.Envelope envelope) throws MismatchedDimensionException { init(envelope.getMinimum(0), envelope.getMaximum(0), envelope.getMinimum(1), envelope.getMaximum(1), envelope.getMinimum(2), envelope.getMaximum(2)); this.crs = envelope.getCoordinateReferenceSystem(); checkCoordinateReferenceSystemDimension(); } /** * Creates a new envelope from an existing JTS envelope. * * @param envelope The envelope to initialize from. * @param crs The coordinate reference system. * @throws MismatchedDimensionExceptionif the CRS dimension is not valid. */ public ReferencedEnvelope3D(final ReferencedEnvelope3D envelope, final CoordinateReferenceSystem crs) throws MismatchedDimensionException { init(envelope); this.crs = crs; checkCoordinateReferenceSystemDimension(); } /** * Sets this envelope to the specified bounding box. */ public void init(BoundingBox bounds) { init(bounds.getMinimum(0), bounds.getMaximum(0), bounds.getMinimum(1), bounds.getMaximum(1), bounds.getMinimum(2), bounds.getMaximum(2)); this.crs = bounds.getCoordinateReferenceSystem(); } /** * Returns the specified bounding box as a JTS envelope. */ private static ReferencedEnvelope3D getJTSEnvelope(final BoundingBox3D bbox) { if( bbox == null ){ throw new NullPointerException("Provided bbox envelope was null"); } if (bbox instanceof ReferencedEnvelope3D) { return (ReferencedEnvelope3D) bbox; } return new ReferencedEnvelope3D(bbox); } /** * Returns the number of dimensions. */ public int getDimension() { return 3; } /** * Returns the minimal ordinate along the specified dimension. */ public double getMinimum(final int dimension) { switch (dimension) { case 0: return getMinX(); case 1: return getMinY(); case 2: return getMinZ(); default: throw new IndexOutOfBoundsException(String.valueOf(dimension)); } } /** * Returns the maximal ordinate along the specified dimension. */ public double getMaximum(final int dimension) { switch (dimension) { case 0: return getMaxX(); case 1: return getMaxY(); case 2: return getMaxZ(); default: throw new IndexOutOfBoundsException(String.valueOf(dimension)); } } /** * Returns the center ordinate along the specified dimension. */ public double getMedian(final int dimension) { switch (dimension) { case 0: return 0.5 * (getMinX() + getMaxX()); case 1: return 0.5 * (getMinY() + getMaxY()); case 2: return 0.5 * (getMinZ() + getMaxZ()); default: throw new IndexOutOfBoundsException(String.valueOf(dimension)); } } /** * Returns the envelope length along the specified dimension. This length is * equals to the maximum ordinate minus the minimal ordinate. */ public double getSpan(final int dimension) { switch (dimension) { case 0: return getWidth(); case 1: return getHeight(); case 2: return getDepth(); default: throw new IndexOutOfBoundsException(String.valueOf(dimension)); } } /** * A coordinate position consisting of all the minimal ordinates for each * dimension for all points within the {@code Envelope}. */ public DirectPosition getLowerCorner() { return new DirectPosition3D(crs, getMinX(), getMinY(), getMinZ()); } /** * A coordinate position consisting of all the maximal ordinates for each * dimension for all points within the {@code Envelope}. */ public DirectPosition getUpperCorner() { return new DirectPosition3D(crs, getMaxX(), getMaxY(), getMinZ() ); } /** * Returns {@code true} if lengths along all dimension are zero. * */ public boolean isEmpty() { return super.isNull(); } /** * Returns {@code true} if the provided location is contained by this bounding box. * */ public boolean contains(DirectPosition pos) { ensureCompatibleReferenceSystem( pos ); return contains(pos.getOrdinate(0), pos.getOrdinate(1), pos.getOrdinate(2)); } /** * Returns {@code true} if the provided bounds are contained by this bounding box. * */ public boolean contains(final BoundingBox3D bbox) { ensureCompatibleReferenceSystem(bbox); return covers(getJTSEnvelope(bbox)); } /** * Check if this bounding box intersects the provided bounds. * */ public boolean intersects(final BoundingBox3D bbox) { ensureCompatibleReferenceSystem(bbox); return intersects(getJTSEnvelope(bbox)); } /** * Computes the intersection of two {@link Envelope}s. * * @param env * the envelope to intersect with * @return a new Envelope representing the intersection of the envelopes * (this will be the null envelope if either argument is null, or * they do not intersect */ public ReferencedEnvelope3D intersection(ReferencedEnvelope3D env) { ensureCompatibleReferenceSystem( env ); if (isNull() || env.isNull() || !intersects( env)) return new ReferencedEnvelope3D(); double intMinX = getMinX() > env.getMinX() ? getMinX() : env.getMinX(); double intMinY = getMinY() > env.getMinY() ? getMinY() : env.getMinY(); double intMinZ = minz > env.minz ? minz : env.minz; double intMaxX = getMaxX() < env.getMaxX() ? getMaxX() : env.getMaxX(); double intMaxY = getMaxY() < env.getMaxY() ? getMaxY() : env.getMaxY(); double intMaxZ = maxz < env.maxz ? maxz : env.maxz; return new ReferencedEnvelope3D(intMinX, intMaxX, intMinY, intMaxY, intMinZ, intMaxZ, env.getCoordinateReferenceSystem()); } /** * Include the provided bounding box, expanding as necessary. * */ public void include(final BoundingBox3D bbox) { if( crs == null ){ this.crs = bbox.getCoordinateReferenceSystem(); } expandToInclude(getJTSEnvelope(bbox)); } /** * Enlarges this <code>Envelope</code> so that it contains the * <code>other</code> Envelope. Has no effect if <code>other</code> is * wholly on or within the envelope. * * @param other * the <code>Envelope</code> to expand to include */ public void expandToInclude(ReferencedEnvelope3D other) { ensureCompatibleReferenceSystem( other ); if (other.isNull()) { return; } if (isNull()) { super.expandToInclude(other); minz = other.getMinZ(); maxz = other.getMaxZ(); } else { super.expandToInclude(other); if (other.minz < minz) { minz = other.minz; } if (other.maxz > maxz) { maxz = other.maxz; } } } /** * Include the provided coordinates, expanding as necessary. * */ public void include(double x, double y, double z) { expandToInclude(x, y, z); } /** * Initialize the bounding box with another bounding box. * * @since 2.4 */ public void setBounds(final BoundingBox3D bbox) { ensureCompatibleReferenceSystem(bbox); init(getJTSEnvelope(bbox)); } /** * Returns a new bounding box which contains the transformed shape of this bounding box. * This is a convenience method that delegate its work to the {@link #transform transform} * method. * */ public BoundingBox toBounds(final CoordinateReferenceSystem targetCRS) throws TransformException { try { return transform(targetCRS, true); } catch (FactoryException e) { throw new TransformException(e.getLocalizedMessage(), e); } } /** * Transforms the referenced envelope to the specified coordinate reference system. * <p> * This method can handle the case where the envelope contains the North or South pole, * or when it cross the ±180� longitude. * * @param targetCRS The target coordinate reference system. * @param lenient {@code true} if datum shift should be applied even if there is * insuffisient information. Otherwise (if {@code false}), an * exception is thrown in such case. * @return The transformed envelope. * @throws FactoryException if the math transform can't be determined. * @throws TransformException if at least one coordinate can't be transformed. * * @see CRS#transform(CoordinateOperation, org.opengis.geometry.Envelope) */ public ReferencedEnvelope transform(CoordinateReferenceSystem targetCRS, boolean lenient) throws TransformException, FactoryException { return transform(targetCRS, lenient, 5); } /** * Transforms the referenced envelope to the specified coordinate reference system * using the specified amount of points. * <p> * This method can handle the case where the envelope contains the North or South pole, * or when it cross the ±180� longitude. * * @param targetCRS The target coordinate reference system. * @param lenient {@code true} if datum shift should be applied even if there is * insuffisient information. Otherwise (if {@code false}), an * exception is thrown in such case. * @param numPointsForTransformation The number of points to use for sampling the envelope. * @return The transformed envelope. * @throws FactoryException if the math transform can't be determined. * @throws TransformException if at least one coordinate can't be transformed. * * @see CRS#transform(CoordinateOperation, org.opengis.geometry.Envelope) * */ public ReferencedEnvelope transform(final CoordinateReferenceSystem targetCRS, final boolean lenient, final int numPointsForTransformation) throws TransformException, FactoryException { //TODO: implement 3D behaviour for this method //falls back on 2D behaviour (3rd coordinate is preserved!) if( crs == null ){ if( isEmpty() ){ // We don't have a CRS yet because we are still empty, being empty is // something we can represent in the targetCRS return new ReferencedEnvelope3D(targetCRS); } else { // really this is a the code that created this ReferencedEnvelope throw new NullPointerException("Unable to transform referenced envelope, crs has not yet been provided."); } } if( getDimension() != targetCRS.getCoordinateSystem().getDimension()){ if( lenient ){ return JTS.transformTo2D(this, targetCRS, lenient, numPointsForTransformation ); } else { throw new MismatchedDimensionException(Errors.format( ErrorKeys.MISMATCHED_DIMENSION_$3, crs.getName().getCode(), new Integer(getDimension()), new Integer(targetCRS.getCoordinateSystem().getDimension()))); } } // Gets a first estimation using an algorithm capable to take singularity in account // (North pole, South pole, 180� longitude). We will expand this initial box later. CoordinateOperationFactory coordinateOperationFactory = CRS.getCoordinateOperationFactory(lenient); final CoordinateOperation operation = coordinateOperationFactory.createOperation(crs, targetCRS); final GeneralEnvelope transformed = CRS.transform(operation, this); transformed.setCoordinateReferenceSystem(targetCRS); // Now expands the box using the usual utility methods. final ReferencedEnvelope3D target = new ReferencedEnvelope3D(transformed); final MathTransform transform = operation.getMathTransform(); JTS.transform(this, target, transform, numPointsForTransformation); //smuggle back third coordinate target.expandToInclude(0,0,this.minz); target.expandToInclude(0,0,this.maxz); return target; } /** * Returns a hash value for this envelope. This value need not remain * consistent between different implementations of the same class. */ @Override public int hashCode() { // Algorithm from Effective Java by Joshua Bloch [Jon Aquino] int result = super.hashCode(); result = 37 * result + Coordinate.hashCode(minz); result = 37 * result + Coordinate.hashCode(maxz); int code = result ^ (int) serialVersionUID; if (crs != null) { code ^= crs.hashCode(); } return code; } /** * Compares the specified object with this envelope for equality. */ @Override public boolean equals(final Object other) { if (!(other instanceof ReferencedEnvelope3D)) { return false; } ReferencedEnvelope3D otherEnvelope = (ReferencedEnvelope3D) other; if (isNull()) { return otherEnvelope.isNull(); } if (super.equals(other) && minz == otherEnvelope.getMinZ() && minz == otherEnvelope.getMinZ()) { final CoordinateReferenceSystem otherCRS = (other instanceof ReferencedEnvelope3D) ? ((ReferencedEnvelope3D) other).crs : null; return CRS.equalsIgnoreMetadata(crs, otherCRS); } return false; } /** * Compare the bounds of this envelope with those of another. * <p> * Note: in this test: * <ul> * <li> the coordinate reference systems of the envelopes are not examined * <li> only the first three dimensions of the envelopes are compared * <li> it is assumed that each dimension equates to the same axis for both envelopes * </ul> * * @param other other envelope * @param eps a small tolerance factor (e.g. 1.0e-6d) which will be scaled * relative to this envlope's width and height * * @return true if all bounding coordinates are equal within the set tolerance; * false otherwise */ public boolean boundsEquals3D(final org.opengis.geometry.Envelope other, double eps) { eps *= 0.5*(getWidth() + getHeight()); double[] delta = new double[4]; delta[0] = getMinimum(0) - other.getMinimum(0); delta[1] = getMaximum(0) - other.getMaximum(0); delta[2] = getMinimum(1) - other.getMinimum(1); delta[3] = getMaximum(1) - other.getMaximum(1); delta[4] = getMinimum(2) - other.getMinimum(2); delta[5] = getMaximum(2) - other.getMaximum(2); for (int i = 0; i < delta.length; i++) { /* * As per Envelope2D#boundsEquals we use ! here to * catch any NaN values */ if (!(Math.abs(delta[i]) <= eps)) { return false; } } return true; } @Override public void include(double x, double y) { super.expandToInclude(x, y); } }