/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2009, 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.jts;
import java.awt.geom.Rectangle2D;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import java.util.Objects;
import org.opengis.geometry.BoundingBox;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.geometry.MismatchedReferenceSystemException;
import org.opengis.util.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 org.apache.sis.geometry.DirectPosition2D;
import org.apache.sis.geometry.GeneralEnvelope;
import org.geotoolkit.referencing.CRS;
import org.apache.sis.util.Classes;
import org.geotoolkit.resources.Errors;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.util.Utilities;
/**
* A JTS envelope associated with a
* {@linkplain CoordinateReferenceSystem coordinate reference system}. In
* addition, this JTS envelope also implements the Types
* {@linkplain org.opengis.geometry.coordinate.Envelope envelope} interface
* for interoperability with Types.
*
* @module
* @since 2.2
* @version $Id$
* @author Jody Garnett
* @author Martin Desruisseaux
* @author Simone Giannecchini
* @author Johann Sorel
*
* @see org.apache.sis.geometry.Envelope2D
* @see org.apache.sis.geometry.GeneralEnvelope
* @see org.opengis.metadata.extent.GeographicBoundingBox
*/
public class JTSEnvelope2D extends Envelope implements org.opengis.geometry.Envelope,BoundingBox {
/** A ReferencedEnvelope containing "everything" */
public static final JTSEnvelope2D EVERYTHING = new JTSEnvelope2D(
Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, null) {
private static final long serialVersionUID = -3188702602373537164L;
@Override
public boolean contains(BoundingBox bbox) {
return true;
}
@Override
public boolean contains(Coordinate p) {
return true;
}
@Override
public boolean contains(DirectPosition pos) {
return true;
}
@Override
public boolean contains(double x, double y) {
return true;
}
@Override
public boolean contains(Envelope other) {
return true;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean isNull() {
return true;
}
};
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = -3188702602373537163L;
/**
* The coordinate reference system, or {@code null}.
*/
private CoordinateReferenceSystem crs;
/**
* Creates a null envelope with a null coordinate reference system.
*/
public JTSEnvelope2D() {
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 JTSEnvelope2D(final 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 crs The coordinate reference system.
*
* @throws MismatchedDimensionException if the CRS dimension is not valid.
*/
public JTSEnvelope2D(final double x1, final double x2, final double y1, final double y2,
final CoordinateReferenceSystem crs) throws MismatchedDimensionException {
super(x1, x2, y1, y2);
this.crs = crs;
checkCoordinateReferenceSystemDimension();
}
/**
* Creates an envelope for a Java2D rectangle.
*
* @param rectangle The rectangle.
* @param crs The coordinate reference system.
*
* @throws MismatchedDimensionException if the CRS dimension is not valid.
*
* @since 2.4
*/
public JTSEnvelope2D(final Rectangle2D rectangle, final CoordinateReferenceSystem crs)
throws MismatchedDimensionException {
this(rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY(), crs);
}
/**
* Creates a new envelope from an existing envelope.
*
* @param envelope The envelope to initialize from
* @throws MismatchedDimensionException if the CRS dimension is not valid.
*
* @since 2.3
*/
public JTSEnvelope2D(final JTSEnvelope2D envelope)
throws MismatchedDimensionException {
super(envelope);
crs = envelope.getCoordinateReferenceSystem();
checkCoordinateReferenceSystemDimension();
}
/**
* Creates a new envelope from an existing bounding box.
*
* @param bbox The bounding box to initialize from.
* @throws MismatchedDimensionException if the CRS dimension is not valid.
*
* @since 2.4
*/
public JTSEnvelope2D(final BoundingBox bbox) throws MismatchedDimensionException {
this(bbox.getMinX(), bbox.getMaxX(), bbox.getMinY(), bbox.getMaxY(),
bbox.getCoordinateReferenceSystem());
}
/**
* Creates a new envelope from an existing OGC envelope.
*
* @param envelope The envelope to initialize from.
* @throws MismatchedDimensionException if the CRS dimension is not valid.
*
* @since 2.4
*/
public JTSEnvelope2D(final org.opengis.geometry.Envelope envelope)
throws MismatchedDimensionException {
super(envelope.getMinimum(0), envelope.getMaximum(0), envelope.getMinimum(1),
envelope.getMaximum(1));
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 JTSEnvelope2D(final Envelope envelope, final CoordinateReferenceSystem crs)
throws MismatchedDimensionException {
super(envelope);
this.crs = crs;
checkCoordinateReferenceSystemDimension();
}
/**
* Sets this envelope to the specified bounding box.
*/
public void init(final BoundingBox bounds) {
super.init(bounds.getMinimum(0), bounds.getMaximum(0), bounds.getMinimum(1),
bounds.getMaximum(1));
this.crs = bounds.getCoordinateReferenceSystem();
}
/**
* Returns the specified bounding box as a JTS envelope.
*/
private static Envelope getJTSEnvelope(final BoundingBox bbox) {
if (bbox instanceof Envelope) {
return (Envelope) bbox;
}
return new JTSEnvelope2D(bbox);
}
/**
* Convenience method for checking coordinate reference system validity.
*
* @throws IllegalArgumentException if the CRS dimension is not valid.
*/
private void checkCoordinateReferenceSystemDimension()
throws MismatchedDimensionException {
if (crs != null) {
final int expected = getDimension();
final int dimension = crs.getCoordinateSystem().getDimension();
if (dimension != expected) {
throw new MismatchedDimensionException(Errors.format(
Errors.Keys.MismatchedDimension_3, crs.getName().getCode(),
new Integer(dimension), new Integer(expected)));
}
}
}
/**
* Make sure that the specified bounding box uses the same CRS than this one.
*
* @param bbox The other bounding box to test for compatibility.
* @throws MismatchedReferenceSystemException if the CRS are incompatibles.
*/
private void ensureCompatibleReferenceSystem(final BoundingBox bbox)
throws MismatchedReferenceSystemException {
if (crs != null) {
final CoordinateReferenceSystem other = bbox.getCoordinateReferenceSystem();
if (other != null) {
if (!Utilities.equalsIgnoreMetadata(crs, other)) {
throw new MismatchedReferenceSystemException(Errors.format(
Errors.Keys.MismatchedCoordinateReferenceSystem));
}
}
}
}
/**
* Returns the coordinate reference system associated with this envelope.
*/
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return crs;
}
/**
* Returns the number of dimensions.
*/
@Override
public int getDimension() {
return 2;
}
/**
* Returns the minimal ordinate along the specified dimension.
*/
@Override
public double getMinimum(final int dimension) {
switch (dimension) {
case 0: return getMinX();
case 1: return getMinY();
default: throw new IndexOutOfBoundsException(String.valueOf(dimension));
}
}
/**
* Returns the maximal ordinate along the specified dimension.
*/
@Override
public double getMaximum(final int dimension) {
switch (dimension) {
case 0: return getMaxX();
case 1: return getMaxY();
default: throw new IndexOutOfBoundsException(String.valueOf(dimension));
}
}
/**
* Returns the center ordinate along the specified dimension.
*/
@Override
public double getMedian(final int dimension) {
switch (dimension) {
case 0: return 0.5 * (getMinX() + getMaxX());
case 1: return 0.5 * (getMinY() + getMaxY());
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.
*/
@Override
public double getSpan(final int dimension) {
switch (dimension) {
case 0: return getWidth();
case 1: return getHeight();
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}.
*/
@Override
public DirectPosition getLowerCorner() {
return new DirectPosition2D(crs, getMinX(), getMinY());
}
/**
* A coordinate position consisting of all the maximal ordinates for each
* dimension for all points within the {@code Envelope}.
*/
@Override
public DirectPosition getUpperCorner() {
return new DirectPosition2D(crs, getMaxX(), getMaxY());
}
/**
* Returns {@code true} if lengths along all dimension are zero.
*
* @since 2.4
*/
@Override
public boolean isEmpty() {
return super.isNull();
}
/**
* Returns {@code true} if the provided location is contained by this bounding box.
*
* @since 2.4
*/
@Override
public boolean contains(final DirectPosition pos) {
return super.contains(pos.getOrdinate(0), pos.getOrdinate(1));
}
/**
* Returns {@code true} if the provided bounds are contained by this bounding box.
*
* @since 2.4
*/
@Override
public boolean contains(final BoundingBox bbox) {
ensureCompatibleReferenceSystem(bbox);
return super.contains(getJTSEnvelope(bbox));
}
/**
* Check if this bounding box intersects the provided bounds.
*
* @since 2.4
*/
@Override
public boolean intersects(final BoundingBox bbox) {
ensureCompatibleReferenceSystem(bbox);
return super.intersects(getJTSEnvelope(bbox));
}
/**
* Include the provided bounding box, expanding as necessary.
*
* @since 2.4
*/
@Override
public void include(final BoundingBox bbox) {
ensureCompatibleReferenceSystem(bbox);
super.expandToInclude(getJTSEnvelope(bbox));
}
/**
* Include the provided coordinates, expanding as necessary.
*
* @since 2.4
*/
@Override
public void include(final double x, final double y) {
super.expandToInclude(x, y);
}
/**
* Initialize the bounding box with another bounding box.
*
* @since 2.4
*/
@Override
public void setBounds(final BoundingBox bbox) {
ensureCompatibleReferenceSystem(bbox);
super.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.
*
* @since 2.4
*/
@Override
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.
*/
public JTSEnvelope2D transform(final CoordinateReferenceSystem targetCRS, final 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.
*
* @since 2.3
*/
public JTSEnvelope2D transform(final CoordinateReferenceSystem targetCRS,
final boolean lenient, final int numPointsForTransformation)
throws TransformException, FactoryException {
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 JTSEnvelope2D(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.");
}
}
/*
* 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 = Envelopes.transform(operation, this);
transformed.setCoordinateReferenceSystem(targetCRS);
/*
* Now expands the box using the usual utility methods.
*/
final JTSEnvelope2D target = new JTSEnvelope2D(transformed);
final MathTransform transform = operation.getMathTransform();
JTS.transform(this, target, transform, numPointsForTransformation);
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() {
int code = super.hashCode() ^ (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 object) {
if (super.equals(object)) {
final CoordinateReferenceSystem otherCRS = (object instanceof JTSEnvelope2D)
? ((JTSEnvelope2D) object).crs : null;
return Objects.equals(crs, otherCRS);
}
return false;
}
/**
* Returns a string representation of this envelope. The default implementation
* is okay for occasional formatting (for example for debugging purpose).
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(this)).append('[');
final int dimension = getDimension();
for (int i = 0; i < dimension; i++) {
if (i != 0) {
buffer.append(", ");
}
buffer.append(getMinimum(i)).append(" : ").append(getMaximum(i));
}
return buffer.append(']').toString();
}
/**
* Utility method to ensure that an Envelope if a JTSEnvelope2D.
* <p>
* This method first checks if <tt>e</tt> is an instanceof {@link JTSEnvelope2D},
* if it is, itself is returned. If not <code>new JTSEnvelope2D(e,null)</code>
* is returned.
* </p>
* <p>
* If e is null, null is returned.
* </p>
* @param e The envelope. Can be null.
* @return A JTSEnvelope2D using the specified envelope, or null if the envelope was null.
*/
public static JTSEnvelope2D reference(final Envelope e) {
if (e == null) {
return null;
} else {
if (e instanceof JTSEnvelope2D) {
return (JTSEnvelope2D) e;
}
return new JTSEnvelope2D(e, null);
}
}
/**
* Utility method to ensure that an BoundingBox in a JTSEnvelope2D.
* <p>
* This method first checks if <tt>e</tt> is an instanceof {@link JTSEnvelope2D},
* if it is, itself is returned. If not <code>new JTSEnvelope2D(e)</code>
* is returned.
* </p>
* @param e The envelope.
* @return JTSEnvelope2D
*/
public static JTSEnvelope2D reference(final BoundingBox e) {
if (e == null) {
return null;
}
if (e instanceof JTSEnvelope2D) {
return (JTSEnvelope2D) e;
}
return new JTSEnvelope2D(e);
}
}