package com.revolsys.geometry.model; import java.io.IOException; import java.io.PushbackReader; import java.io.Serializable; import java.io.StringReader; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import javax.measure.Measurable; import javax.measure.Measure; import javax.measure.quantity.Length; import javax.measure.quantity.Quantity; import javax.measure.unit.SI; import javax.measure.unit.Unit; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.cs.CoordinateSystem; import com.revolsys.geometry.cs.ProjectedCoordinateSystem; import com.revolsys.geometry.cs.projection.CoordinatesOperation; import com.revolsys.geometry.model.coordinates.list.CoordinatesListUtil; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXY; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXYGeometryFactory; import com.revolsys.geometry.model.impl.LineStringDouble; import com.revolsys.geometry.model.impl.PointDoubleGf; import com.revolsys.geometry.util.BoundingBoxUtil; import com.revolsys.io.FileUtil; import com.revolsys.logging.Logs; import com.revolsys.record.Record; import com.revolsys.record.io.format.wkt.WktParser; import com.revolsys.util.Emptyable; import com.revolsys.util.Exceptions; import com.revolsys.util.Property; import com.revolsys.util.function.Consumer3; import com.revolsys.util.number.Doubles; public interface BoundingBox extends BoundingBoxProxy, Emptyable, GeometryFactoryProxy, Cloneable, Serializable { public static final int OUT_LEFT = 1; public static final int OUT_TOP = 2; public static final int OUT_RIGHT = 4; public static final int OUT_BOTTOM = 8; static BoundingBox empty() { return GeometryFactory.DEFAULT_3D.newBoundingBoxEmpty(); } static boolean isEmpty(final double minX, final double maxX) { if (Double.isNaN(minX)) { return true; } else if (Double.isNaN(maxX)) { return true; } else { return maxX < minX; } } static BoundingBox newBoundingBox(final Object value) { if (value == null) { return empty(); } else if (value instanceof BoundingBox) { return (BoundingBox)value; } else if (value instanceof Geometry) { final Geometry geometry = (Geometry)value; return geometry.getBoundingBox(); } else { final String string = DataTypes.toString(value); return BoundingBox.newBoundingBox(string); } } static BoundingBox newBoundingBox(final String wkt) { if (Property.hasValue(wkt)) { try { GeometryFactory geometryFactory = null; final PushbackReader reader = new PushbackReader(new StringReader(wkt), 20); WktParser.skipWhitespace(reader); if (WktParser.hasText(reader, "SRID=")) { final Integer srid = WktParser.parseInteger(reader); if (srid != null) { geometryFactory = GeometryFactory.floating(srid, 2); } WktParser.hasText(reader, ";"); } if (WktParser.hasText(reader, "BBOX(")) { final double x1 = WktParser.parseDouble(reader); if (WktParser.hasText(reader, ",")) { WktParser.skipWhitespace(reader); final double y1 = WktParser.parseDouble(reader); WktParser.skipWhitespace(reader); final double x2 = WktParser.parseDouble(reader); if (WktParser.hasText(reader, ",")) { WktParser.skipWhitespace(reader); final double y2 = WktParser.parseDouble(reader); return geometryFactory.newBoundingBox(x1, y1, x2, y2); } else { throw new IllegalArgumentException( "Expecting a ',' not " + FileUtil.getString(reader, 50)); } } else { throw new IllegalArgumentException("Expecting a ',' not " + FileUtil.getString(reader)); } } else if (WktParser.hasText(reader, "BBOX EMPTY")) { return geometryFactory.newBoundingBoxEmpty(); } } catch (final IOException e) { throw Exceptions.wrap("Error reading WKT:" + wkt, e); } } return empty(); } static String toString(final BoundingBox boundingBox) { final StringBuilder s = new StringBuilder(); final int srid = boundingBox.getCoordinateSystemId(); if (srid > 0) { s.append("SRID="); s.append(srid); s.append(";"); } if (boundingBox.isEmpty()) { s.append("BBOX EMPTY"); } else { s.append("BBOX"); final int axisCount = boundingBox.getAxisCount(); if (axisCount == 3) { s.append(" Z"); } else if (axisCount == 4) { s.append(" ZM"); } else if (axisCount != 2) { s.append(" "); s.append(axisCount); } s.append("("); for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { if (axisIndex > 0) { s.append(','); } s.append(Doubles.toString(boundingBox.getMin(axisIndex))); } s.append(' '); for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { if (axisIndex > 0) { s.append(','); } s.append(Doubles.toString(boundingBox.getMax(axisIndex))); } s.append(')'); } return s.toString(); } default BoundingBox clipToCoordinateSystem() { final GeometryFactory geometryFactory = getGeometryFactory(); final CoordinateSystem coordinateSystem = geometryFactory.getCoordinateSystem(); if (coordinateSystem == null) { return this; } else { final BoundingBox areaBoundingBox = coordinateSystem.getAreaBoundingBox(); return intersection(areaBoundingBox); } } default BoundingBox clone() { return this; } /** * Check that geom is not contained entirely in the rectangle boundary. * According to the somewhat odd spec of the SFS, if this * is the case the geometry is NOT contained. */ default boolean containsSFS(final Geometry geometry) { final BoundingBox boundingBox2 = geometry.getBoundingBox(); if (covers(boundingBox2)) { if (geometry.isContainedInBoundary(this)) { return false; } else { return true; } } else { return false; } } default BoundingBox convert(final GeometryFactory geometryFactory) { final GeometryFactory factory = getGeometryFactory(); if (isEmpty()) { return geometryFactory.newBoundingBoxEmpty(); } else if (geometryFactory == null) { return this; } else if (factory == geometryFactory) { return this; } else { if (factory == null || factory.getCoordinateSystem() == null) { final int axisCount = Math.min(geometryFactory.getAxisCount(), getAxisCount()); final double[] minMaxValues = getMinMaxValues(axisCount); return geometryFactory.newBoundingBox(axisCount, minMaxValues); } else if (isEmpty()) { return newBoundingBoxEmpty(); } else { final CoordinatesOperation operation = factory.getCoordinatesOperation(geometryFactory); if (operation != null) { double xStep = getWidth() / 10; double yStep = getHeight() / 10; final double scaleXY = geometryFactory.getScaleXY(); if (scaleXY > 0) { if (xStep < 1 / scaleXY) { xStep = 1 / scaleXY; } if (yStep < 1 / scaleXY) { yStep = 1 / scaleXY; } } final double minX = getMinX(); final double maxX = getMaxX(); final double minY = getMinY(); final double maxY = getMaxY(); final double[] bounds = getMinMaxValues(2); bounds[0] = Double.NaN; bounds[1] = Double.NaN; bounds[2] = Double.NaN; bounds[3] = Double.NaN; final double[] to = new double[2]; expand(geometryFactory, bounds, operation, to, minX, minY); expand(geometryFactory, bounds, operation, to, minX, maxY); expand(geometryFactory, bounds, operation, to, minX, minY); expand(geometryFactory, bounds, operation, to, maxX, minY); if (xStep != 0) { for (double x = minX + xStep; x < maxX; x += xStep) { expand(geometryFactory, bounds, operation, to, x, minY); expand(geometryFactory, bounds, operation, to, x, maxY); } } if (yStep != 0) { for (double y = minY + yStep; y < maxY; y += yStep) { expand(geometryFactory, bounds, operation, to, minX, y); expand(geometryFactory, bounds, operation, to, maxX, y); } } return geometryFactory.newBoundingBox(2, bounds); } else { return this; } } } } default BoundingBox convert(GeometryFactory geometryFactory, final int axisCount) { final GeometryFactory sourceGeometryFactory = getGeometryFactory(); if (geometryFactory == null || sourceGeometryFactory == null) { return this; } else { geometryFactory = geometryFactory.convertAxisCount(axisCount); boolean copy = false; if (geometryFactory != null && sourceGeometryFactory != geometryFactory) { final int srid = getCoordinateSystemId(); final int srid2 = geometryFactory.getCoordinateSystemId(); if (srid <= 0) { if (srid2 > 0) { copy = true; } } else if (srid != srid2) { copy = true; } if (!copy) { for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { final double scale = sourceGeometryFactory.getScale(axisIndex); final double scale1 = geometryFactory.getScale(axisIndex); if (!Doubles.equal(scale, scale1)) { copy = true; } } } } if (copy) { return convert(geometryFactory); } else { return this; } } } default boolean coveredBy(final double... bounds) { final double minX1 = bounds[0]; final double minY1 = bounds[1]; final double maxX1 = bounds[2]; final double maxY1 = bounds[3]; final double minX2 = getMinX(); final double minY2 = getMinY(); final double maxX2 = getMaxX(); final double maxY2 = getMaxY(); return BoundingBoxUtil.covers(minX1, minY1, maxX1, maxY1, minX2, minY2, maxX2, maxY2); } /** * Tests if the <code>BoundingBox other</code> * lies wholely inside this <code>BoundingBox</code> (inclusive of the boundary). * *@param other the <code>BoundingBox</code> to check *@return true if this <code>BoundingBox</code> covers the <code>other</code> */ default boolean covers(BoundingBox other) { if (other == null || isEmpty() || other.isEmpty()) { return false; } else { other = other.convert(getGeometryFactory()); final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); return other.coveredBy(minX, minY, maxX, maxY); } } /** * Tests if the given point lies in or on the envelope. * *@param x the x-coordinate of the point which this <code>BoundingBox</code> is * being checked for containing *@param y the y-coordinate of the point which this <code>BoundingBox</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>BoundingBox</code>. */ default boolean covers(final double x, final double y) { if (isEmpty()) { return false; } else { final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); return BoundingBoxUtil.covers(minX, minY, maxX, maxY, x, y, x, y); } } default boolean covers(final Geometry geometry) { if (geometry == null) { return false; } else { final BoundingBox boundingBox = geometry.getBoundingBox(); return covers(boundingBox); } } /** * Tests if the given point lies in or on the envelope. * *@param p the point which this <code>BoundingBox</code> is * being checked for containing *@return <code>true</code> if the point lies in the interior or * on the boundary of this <code>BoundingBox</code>. */ default boolean covers(final Point point) { if (point == null || point.isEmpty()) { return false; } else { final GeometryFactory geometryFactory = getGeometryFactory(); final Point projectedPoint = point.convertGeometry(geometryFactory); final double x = projectedPoint.getX(); final double y = projectedPoint.getY(); return covers(x, y); } } /** * Computes the distance between this and another * <code>BoundingBox</code>. * The distance between overlapping Envelopes is 0. Otherwise, the * distance is the Euclidean distance between the closest points. */ default double distance(BoundingBox boundingBox) { final GeometryFactory geometryFactory = getGeometryFactory(); boundingBox = boundingBox.convert(geometryFactory); final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); final double minX2 = boundingBox.getMinX(); final double minY2 = boundingBox.getMinY(); final double maxX2 = boundingBox.getMaxX(); final double maxY2 = boundingBox.getMaxY(); if (isEmpty(minX, maxX) || isEmpty(minX2, maxX2)) { // Empty return Double.MAX_VALUE; } else if (!(minX2 > maxX || maxX2 < minX || minY2 > maxY || maxY2 < minY)) { // Intersects return 0; } else { double dx; if (maxX < minX2) { dx = minX2 - maxX; } else { if (minX > maxX2) { dx = minX - maxX2; } else { dx = 0; } } double dy; if (maxY < minY2) { dy = minY2 - maxY; } else if (minY > maxY2) { dy = minY - maxY2; } else { dy = 0; } if (dx == 0.0) { return dy; } else if (dy == 0.0) { return dx; } else { return Math.sqrt(dx * dx + dy * dy); } } } default double distance(final Geometry geometry) { final BoundingBox boundingBox = geometry.getBoundingBox(); return distance(boundingBox); } /** * Computes the distance between this and another * <code>BoundingBox</code>. * The distance between overlapping Envelopes is 0. Otherwise, the * distance is the Euclidean distance between the closest points. */ default double distance(Point point) { point = point.convertGeometry(getGeometryFactory()); final double x = point.getX(); final double y = point.getY(); if (intersects(x, y)) { return 0; } else { final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); double dx = 0.0; if (maxX < x) { dx = x - maxX; } else if (minX > x) { dx = minX - x; } double dy = 0.0; if (maxY < y) { dy = y - maxY; } else if (minY > y) { dy = minY - y; } // if either is zero, the envelopes overlap either vertically or // horizontally if (dx == 0.0) { return dy; } if (dy == 0.0) { return dx; } return Math.sqrt(dx * dx + dy * dy); } } default boolean equals(final BoundingBox boundingBox) { if (isEmpty()) { return boundingBox.isEmpty(); } else if (boundingBox.isEmpty()) { return false; } else if (getCoordinateSystemId() == boundingBox.getCoordinateSystemId()) { if (getMaxX() == boundingBox.getMaxX()) { if (getMaxY() == boundingBox.getMaxY()) { if (getMinX() == boundingBox.getMinX()) { if (getMinY() == boundingBox.getMinY()) { return true; } } } } } return false; } /** * Return a new bounding box expanded by delta. * * @param delta * @return */ default BoundingBox expand(final double delta) { return expand(delta, delta); } /** * Return a new bounding box expanded by deltaX, deltaY. * * @param delta * @return */ default BoundingBox expand(final double deltaX, final double deltaY) { if (isEmpty() || deltaX == 0 && deltaY == 0) { return this; } else { final double x1 = getMinX() - deltaX; final double x2 = getMaxX() + deltaX; final double y1 = getMinY() - deltaY; final double y2 = getMaxY() + deltaY; if (x1 > x2 || y1 > y2) { return newBoundingBoxEmpty(); } else { return newBoundingBox(x1, y1, x2, y2); } } } default void expand(final GeometryFactory geometryFactory, final double[] bounds, final CoordinatesOperation operation, final double[] to, final double... from) { operation.perform(2, from, 2, to); BoundingBoxUtil.expand(geometryFactory, bounds, to); } default BoundingBox expand(final Point point) { if (isEmpty()) { return point.newBoundingBox(); } else { final double x = point.getX(); final double y = point.getY(); double minX = getMinX(); double maxX = getMaxX(); double minY = getMinY(); double maxY = getMaxY(); if (x < minX) { minX = x; } if (x > maxX) { maxX = x; } if (y < minY) { minY = y; } if (y > maxY) { maxY = y; } return newBoundingBox(minX, minY, maxX, maxY); } } default BoundingBox expandPercent(final double factor) { return expandPercent(factor, factor); } default BoundingBox expandPercent(final double factorX, final double factorY) { if (isEmpty()) { return this; } else { final double deltaX = getWidth() * factorX / 2; final double deltaY = getHeight() * factorY / 2; return expand(deltaX, deltaY); } } default BoundingBox expandToInclude(final BoundingBox other) { if (other == null || other.isEmpty()) { return this; } else { final GeometryFactory geometryFactory = getGeometryFactory(); final BoundingBox convertedOther = other.convert(geometryFactory); if (isEmpty()) { return convertedOther; } else if (covers(convertedOther)) { return this; } else { final double minX = Math.min(getMinX(), convertedOther.getMinX()); final double maxX = Math.max(getMaxX(), convertedOther.getMaxX()); final double minY = Math.min(getMinY(), convertedOther.getMinY()); final double maxY = Math.max(getMaxY(), convertedOther.getMaxY()); return newBoundingBox(minX, minY, maxX, maxY); } } } default BoundingBox expandToInclude(final double... coordinates) { if (coordinates == null || coordinates.length < 2) { return this; } else { final double[] bounds; final int axisCount = getAxisCount(); if (isEmpty()) { bounds = BoundingBoxUtil.newBounds(axisCount); } else { bounds = getMinMaxValues(); } BoundingBoxUtil.expand(bounds, axisCount, coordinates); return newBoundingBox(axisCount, bounds); } } default BoundingBox expandToInclude(final Geometry geometry) { if (geometry == null || geometry.isEmpty()) { return this; } else { final GeometryFactory geometryFactory = getGeometryFactory(); final Geometry convertedGeometry = geometry.convertGeometry(geometryFactory); final BoundingBox box = convertedGeometry.getBoundingBox(); return expandToInclude(box); } } default BoundingBox expandToInclude(final Point point) { return expandToInclude((Geometry)point); } default BoundingBox expandToInclude(final Record object) { if (object != null) { final Geometry geometry = object.getGeometry(); return expandToInclude(geometry); } return this; } /** * Gets the area of this envelope. * * @return the area of the envelope * @return 0.0 if the envelope is null */ default double getArea() { if (getAxisCount() < 2 || isEmpty()) { return 0; } else { final double width = getWidth(); final double height = getHeight(); return width * height; } } /** * Get the aspect ratio x:y. * * @return The aspect ratio. */ default double getAspectRatio() { final double width = getWidth(); final double height = getHeight(); final double aspectRatio = width / height; return aspectRatio; } default int getAxisCount() { return 2; } default Point getBottomLeftPoint() { return getGeometryFactory().point(getMinX(), getMinY()); } default Point getBottomRightPoint() { return getGeometryFactory().point(getMaxX(), getMinY()); } @Override default BoundingBox getBoundingBox() { return this; } default Point getCentre() { final GeometryFactory geometryFactory = getGeometryFactory(); if (isEmpty()) { return geometryFactory.point(); } else { final double centreX = getCentreX(); final double centreY = getCentreY(); return geometryFactory.point(centreX, centreY); } } default double getCentreX() { return getMinX() + getWidth() / 2; } default double getCentreY() { return getMinY() + getHeight() / 2; } /** * Get the geometry factory. * * @return The geometry factory. */ @Override default CoordinateSystem getCoordinateSystem() { final GeometryFactory geometryFactory = getGeometryFactory(); if (geometryFactory == null) { return null; } else { return geometryFactory.getCoordinateSystem(); } } /** * maxX,minY * minX,minY * minX,maxY * maxX,maxY */ default Point getCornerPoint(int index) { if (isEmpty()) { return null; } else { final double minX = getMinX(); final double maxX = getMaxX(); final double minY = getMinY(); final double maxY = getMaxY(); index = index % 4; switch (index) { case 0: return new PointDoubleGf(getGeometryFactory(), maxX, minY); case 1: return new PointDoubleGf(getGeometryFactory(), minX, minY); case 2: return new PointDoubleGf(getGeometryFactory(), minX, maxY); default: return new PointDoubleGf(getGeometryFactory(), maxX, maxY); } } } default LineString getCornerPoints() { final double minX = getMinX(); final double maxX = getMaxX(); final double minY = getMinY(); final double maxY = getMaxY(); return new LineStringDouble(2, maxX, minY, minX, minY, minX, maxY, maxX, maxY); } /** * Returns the difference between the maximum and minimum y values. * *@return max y - min y, or 0 if this is a null <code>BoundingBox</code> */ default double getHeight() { if (getAxisCount() < 2 || isEmpty()) { return 0; } else { return getMaxY() - getMinY(); } } default Measure<Length> getHeightLength() { final double height = getHeight(); final CoordinateSystem coordinateSystem = getCoordinateSystem(); if (coordinateSystem == null) { return Measure.valueOf(height, SI.METRE); } else { return Measure.valueOf(height, coordinateSystem.getLengthUnit()); } } default double getMax(final int i) { return Double.NaN; } default <Q extends Quantity> Measurable<Q> getMaximum(final int axisIndex) { final Unit<Q> unit = getUnit(); final double max = this.getMax(axisIndex); return Measure.valueOf(max, unit); } @SuppressWarnings({ "rawtypes", "unchecked" }) default <Q extends Quantity> double getMaximum(final int axisIndex, final Unit convertUnit) { final Measurable<Quantity> max = getMaximum(axisIndex); return max.doubleValue(convertUnit); } /** * Returns the <code>BoundingBox</code>s maximum x-value. min x > max x * indicates that this is a null <code>BoundingBox</code>. * *@return the maximum x-coordinate */ default double getMaxX() { return getMax(0); } /** * Returns the <code>BoundingBox</code>s maximum y-value. min y > max y * indicates that this is a null <code>BoundingBox</code>. * *@return the maximum y-coordinate */ default double getMaxY() { return getMax(1); } default double getMaxZ() { return getMax(2); } default double getMin(final int i) { return Double.NaN; } default <Q extends Quantity> Measurable<Q> getMinimum(final int axisIndex) { final Unit<Q> unit = getUnit(); final double min = this.getMin(axisIndex); return Measure.valueOf(min, unit); } @SuppressWarnings({ "rawtypes", "unchecked" }) default <Q extends Quantity> double getMinimum(final int axisIndex, final Unit convertUnit) { final Measurable<Quantity> min = getMinimum(axisIndex); return min.doubleValue(convertUnit); } default double[] getMinMaxValues() { if (isEmpty()) { return null; } else { final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); return new double[] { minX, minY, maxX, maxY }; } } default double[] getMinMaxValues(final int axisCount) { if (isEmpty()) { return null; } else { final double[] bounds = new double[2 * axisCount]; for (int i = 0; i < axisCount; i++) { bounds[i] = getMin(i); bounds[i + axisCount] = getMax(i); } return bounds; } } /** * Returns the <code>BoundingBox</code>s minimum x-value. min x > max x * indicates that this is a null <code>BoundingBox</code>. * *@return the minimum x-coordinate */ default double getMinX() { return getMin(0); } /** * Returns the <code>BoundingBox</code>s minimum y-value. min y > max y * indicates that this is a null <code>BoundingBox</code>. * *@return the minimum y-coordinate */ default double getMinY() { return getMin(1); } default double getMinZ() { return getMin(2); } default int getOutcode(final double x, final double y) { int out; final double minX = getMinX(); final double maxX = getMaxX(); if (x < minX) { out = OUT_LEFT; } else if (x > maxX) { out = OUT_RIGHT; } else { out = 0; } final double minY = getMinY(); final double maxY = getMaxY(); if (y < minY) { out |= OUT_TOP; } else if (y > maxY) { out |= OUT_BOTTOM; } return out; } default Point getRandomPointWithin() { final GeometryFactory geometryFactory = getGeometryFactory(); final double x = getMinX() + getWidth() * Math.random(); final double y = getMinY() + getHeight() * Math.random(); return geometryFactory.point(x, y); } default Point getTopLeftPoint() { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.point(getMinX(), getMaxY()); } default Point getTopRightPoint() { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.point(getMaxX(), getMaxY()); } @SuppressWarnings("unchecked") default <Q extends Quantity> Unit<Q> getUnit() { final CoordinateSystem coordinateSystem = getCoordinateSystem(); if (coordinateSystem == null) { return (Unit<Q>)SI.METRE; } else { return coordinateSystem.<Q> getUnit(); } } /** * Returns the difference between the maximum and minimum x values. * *@return max x - min x, or 0 if this is a null <code>BoundingBox</code> */ default double getWidth() { if (getAxisCount() < 2 || isEmpty()) { return 0; } else { final double minX = getMinX(); final double maxX = getMaxX(); return maxX - minX; } } default Measure<Length> getWidthLength() { final double width = getWidth(); final CoordinateSystem coordinateSystem = getCoordinateSystem(); if (coordinateSystem == null) { return Measure.valueOf(width, SI.METRE); } else { return Measure.valueOf(width, coordinateSystem.getLengthUnit()); } } /** * Computes the intersection of two {@link BoundingBox}s. * * @param env the envelope to intersect with * @return a new BoundingBox representing the intersection of the envelopes (this will be * the null envelope if either argument is null, or they do not intersect */ default BoundingBox intersection(final BoundingBox boundingBox) { final GeometryFactory geometryFactory = getGeometryFactory(); final BoundingBox convertedBoundingBox = boundingBox.convert(geometryFactory); if (isEmpty() || convertedBoundingBox.isEmpty() || !intersects(convertedBoundingBox)) { return newBoundingBoxEmpty(); } else { final double intMinX = Math.max(getMinX(), convertedBoundingBox.getMinX()); final double intMinY = Math.max(getMinY(), convertedBoundingBox.getMinY()); final double intMaxX = Math.min(getMaxX(), convertedBoundingBox.getMaxX()); final double intMaxY = Math.min(getMaxY(), convertedBoundingBox.getMaxY()); return newBoundingBox(intMinX, intMinY, intMaxX, intMaxY); } } /** * Check if the region defined by <code>other</code> * overlaps (intersects) the region of this <code>BoundingBox</code>. * *@param other the <code>BoundingBox</code> which this <code>BoundingBox</code> is * being checked for overlapping *@return <code>true</code> if the <code>BoundingBox</code>s overlap */ default boolean intersects(final BoundingBox other) { if (isEmpty() || other.isEmpty()) { return false; } else { final GeometryFactory geometryFactory = getGeometryFactory(); final BoundingBox convertedBoundingBox = other.convert(geometryFactory, 2); return intersectsFast(convertedBoundingBox); } } /** * Check if the point <code>(x, y)</code> * overlaps (lies inside) the region of this <code>BoundingBox</code>. * *@param x the x-ordinate of the point *@param y the y-ordinate of the point *@return <code>true</code> if the point overlaps this <code>BoundingBox</code> */ default boolean intersects(final double x, final double y) { if (isEmpty()) { return false; } else { final double minX1 = getMinX(); final double minY1 = getMinY(); final double maxX1 = getMaxX(); final double maxY1 = getMaxY(); return !(x > maxX1 || x < minX1 || y > maxY1 || y < minY1); } } default boolean intersects(double x1, double y1, double x2, double y2) { final double minX1 = getMinX(); final double minY1 = getMinY(); final double maxX1 = getMaxX(); final double maxY1 = getMaxY(); if (x1 > x2) { final double t = x1; x1 = x2; x2 = t; } if (y1 > y2) { final double t = y1; y1 = y2; y2 = t; } return !(x1 > maxX1 || x2 < minX1 || y1 > maxY1 || y2 < minY1); } /** * Fast version of intersects that assumes it's in the same coordinate system. * * @param boundingBox * @return */ default boolean intersectsFast(final BoundingBox boundingBox) { final double minX2 = boundingBox.getMinX(); final double minY2 = boundingBox.getMinY(); final double maxX2 = boundingBox.getMaxX(); final double maxY2 = boundingBox.getMaxY(); return intersects(minX2, minY2, maxX2, maxY2); } @Override default boolean isEmpty() { final double minX = getMinX(); final double maxX = getMaxX(); if (Double.isNaN(minX)) { return true; } else if (Double.isNaN(maxX)) { return true; } else { return maxX < minX; } } default boolean isWithinDistance(final BoundingBox boundingBox, final double maxDistance) { final double distance = boundingBox.distance(boundingBox); if (distance < maxDistance) { return true; } else { return false; } } default boolean isWithinDistance(final Geometry geometry, final double maxDistance) { final BoundingBox boundingBox = geometry.getBoundingBox(); return isWithinDistance(boundingBox, maxDistance); } /** * <p>Construct a new new BoundingBox by moving the min/max x coordinates by xDisplacement and * the min/max y coordinates by yDisplacement. If the bounding box is null or the xDisplacement * and yDisplacement are 0 then this bounding box will be returned.</p> * * @param xDisplacement The distance to move the min/max x coordinates. * @param yDisplacement The distance to move the min/max y coordinates. * @return The moved bounding box. */ default BoundingBox move(final double xDisplacement, final double yDisplacement) { if (isEmpty() || xDisplacement == 0 && yDisplacement == 0) { return this; } else { final double x1 = getMinX() + xDisplacement; final double x2 = getMaxX() + xDisplacement; final double y1 = getMinY() + yDisplacement; final double y2 = getMaxY() + yDisplacement; return newBoundingBox(x1, y1, x2, y2); } } default BoundingBox newBoundingBox(final double x, final double y) { return new BoundingBoxDoubleXY(x, y); } default BoundingBox newBoundingBox(final double x1, final double y1, final double x2, final double y2) { final GeometryFactory geometryFactory = getGeometryFactory(); if (geometryFactory == GeometryFactory.DEFAULT_3D) { return new BoundingBoxDoubleXY(x1, y1, x2, y2); } else { return new BoundingBoxDoubleXYGeometryFactory(geometryFactory, x1, y1, x2, y2); } } default BoundingBox newBoundingBox(final int axisCount, final double... bounds) { final double minX = bounds[0]; final double minY = bounds[1]; final double maxX = bounds[axisCount]; final double maxY = bounds[axisCount + 1]; return newBoundingBox(minX, minY, maxX, maxY); } default BoundingBox newBoundingBox(final Point point) { final double x = point.getX(); final double y = point.getY(); return newBoundingBox(x, y); } default BoundingBox newBoundingBoxEmpty() { return BoundingBoxDoubleXY.EMPTY; } /** * Creates a {@link Geometry} with the same extent as the given envelope. * The Geometry returned is guaranteed to be valid. * To provide this behaviour, the following cases occur: * <p> * If the <code>BoundingBox</code> is: * <ul> * <li>null : returns an empty {@link Point} * <li>a point : returns a non-empty {@link Point} * <li>a line : returns a two-point {@link LineString} * <li>a rectangle : returns a {@link Polygon}> whose points are (minx, miny), * (minx, maxy), (maxx, maxy), (maxx, miny), (minx, miny). * </ul> * *@param envelope the <code>BoundingBox</code> to convert *@return an empty <code>Point</code> (for null <code>BoundingBox</code>s), * a <code>Point</code> (when min x = max x and min y = max y) or a * <code>Polygon</code> (in all other cases) */ default Geometry toGeometry() { GeometryFactory geometryFactory = getGeometryFactory(); if (geometryFactory == null) { geometryFactory = GeometryFactory.floating(0, 2); } if (isEmpty()) { return geometryFactory.point(); } else { final double minX = getMinX(); final double minY = getMinY(); final double maxX = getMaxX(); final double maxY = getMaxY(); final double width = getWidth(); final double height = getHeight(); if (width == 0 && height == 0) { return geometryFactory.point(minX, minY); } else if (width == 0 || height == 0) { return geometryFactory.lineString(2, minX, minY, maxX, maxY); } else { return geometryFactory.polygon(geometryFactory.linearRing(2, minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY)); } } } default Polygon toPolygon() { return toPolygon(100, 100); } default Polygon toPolygon(final GeometryFactory factory) { return toPolygon(factory, 100, 100); } default Polygon toPolygon(final GeometryFactory factory, final int numSegments) { return toPolygon(factory, numSegments, numSegments); } default Polygon toPolygon(GeometryFactory geometryFactory, int numX, int numY) { if (isEmpty()) { return geometryFactory.polygon(); } else { final GeometryFactory factory = getGeometryFactory(); if (geometryFactory == null) { if (factory == null) { geometryFactory = GeometryFactory.floating(0, 2); } else { geometryFactory = factory; } } try { double minStep = 0.00001; final CoordinateSystem coordinateSystem = factory.getCoordinateSystem(); if (coordinateSystem instanceof ProjectedCoordinateSystem) { minStep = 1; } else { minStep = 0.00001; } double xStep; final double width = getWidth(); if (numX <= 1) { numX = 1; xStep = width; } else { xStep = width / numX; if (xStep < minStep) { xStep = minStep; } numX = Math.max(1, (int)Math.ceil(width / xStep)); } double yStep; if (numY <= 1) { numY = 1; yStep = getHeight(); } else { yStep = getHeight() / numY; if (yStep < minStep) { yStep = minStep; } numY = Math.max(1, (int)Math.ceil(getHeight() / yStep)); } final double minX = getMinX(); final double maxX = getMaxX(); final double minY = getMinY(); final double maxY = getMaxY(); final int numCoordinates = 1 + 2 * (numX + numY); final double[] coordinates = new double[numCoordinates * 2]; int i = 0; CoordinatesListUtil.setCoordinates(coordinates, 2, i, maxX, minY); i++; for (int j = 0; j < numX - 1; j++) { CoordinatesListUtil.setCoordinates(coordinates, 2, i, maxX - j * xStep, minY); i++; } CoordinatesListUtil.setCoordinates(coordinates, 2, i, minX, minY); i++; for (int j = 0; j < numY - 1; j++) { CoordinatesListUtil.setCoordinates(coordinates, 2, i, minX, minY + j * yStep); i++; } CoordinatesListUtil.setCoordinates(coordinates, 2, i, minX, maxY); i++; for (int j = 0; j < numX - 1; j++) { CoordinatesListUtil.setCoordinates(coordinates, 2, i, minX + j * xStep, maxY); i++; } CoordinatesListUtil.setCoordinates(coordinates, 2, i, maxX, maxY); i++; for (int j = 0; j < numY - 1; j++) { CoordinatesListUtil.setCoordinates(coordinates, 2, i, maxX, minY + (numY - j) * yStep); i++; } CoordinatesListUtil.setCoordinates(coordinates, 2, i, maxX, minY); final LinearRing ring = factory.linearRing(2, coordinates); final Polygon polygon = factory.polygon(ring); if (geometryFactory == null) { return polygon; } else { return (Polygon)polygon.convertGeometry(geometryFactory); } } catch (final IllegalArgumentException e) { Logs.error(this, "Unable to convert to polygon: " + this, e); return geometryFactory.polygon(); } } } default Polygon toPolygon(final int numSegments) { return toPolygon(numSegments, numSegments); } default Polygon toPolygon(final int numX, final int numY) { final GeometryFactory geometryFactory = getGeometryFactory(); return toPolygon(geometryFactory, numX, numY); } static <V> List<V> newArray( final BiConsumer<BoundingBoxProxy, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy) { final List<V> values = new ArrayList<>(); if (boundingBoxProxy != null) { final BoundingBox boundingBox = boundingBoxProxy.getBoundingBox(); forEachFunction.accept(boundingBox, values::add); } return values; } static <V> List<V> newArray( final Consumer3<BoundingBoxProxy, Predicate<? super V>, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy, final Predicate<? super V> filter) { final List<V> values = new ArrayList<>(); if (boundingBoxProxy != null) { final BoundingBox boundingBox = boundingBoxProxy.getBoundingBox(); forEachFunction.accept(boundingBox, filter, values::add); } return values; } static <V> List<V> newArraySorted( final BiConsumer<BoundingBoxProxy, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy) { return newArraySorted(forEachFunction, boundingBoxProxy, null); } static <V> List<V> newArraySorted( final BiConsumer<BoundingBoxProxy, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy, final Comparator<V> comparator) { final List<V> values = newArray(forEachFunction, boundingBoxProxy); values.sort(comparator); return values; } static <V> List<V> newArraySorted( final Consumer3<BoundingBoxProxy, Predicate<? super V>, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy, final Predicate<? super V> filter) { return newArraySorted(forEachFunction, boundingBoxProxy, filter, null); } static <V> List<V> newArraySorted( final Consumer3<BoundingBoxProxy, Predicate<? super V>, Consumer<V>> forEachFunction, final BoundingBoxProxy boundingBoxProxy, final Predicate<? super V> filter, final Comparator<V> comparator) { final List<V> values = newArray(forEachFunction, boundingBoxProxy, filter); values.sort(comparator); return values; } }