/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.model;
import com.revolsys.geometry.model.impl.PointDoubleXY;
/**
* A Bounding Container which is in the shape of an octagon.
* The OctagonalEnvelope of a geometric object
* is tight along the four extremal rectilineal parallels
* and along the four extremal diagonal parallels.
* Depending on the shape of the contained
* geometry, the octagon may be degenerate to any extreme
* (e.g. it may be a rectangle, a line, or a point).
*/
public class OctagonalEnvelope {
private static double SQRT2 = Math.sqrt(2.0);
private static double computeA(final double x, final double y) {
return x + y;
}
private static double computeB(final double x, final double y) {
return x - y;
}
private double maxA;
private double maxB;
private double maxX;
private double maxY;
private double minA;
private double minB;
// initialize in the null state
private double minX = Double.NaN;
private double minY;
/**
* Creates a new null bounding octagon
*/
public OctagonalEnvelope() {
}
/**
* Creates a new null bounding octagon bounding an {@link BoundingBox}
*/
public OctagonalEnvelope(final BoundingBox env) {
expandToInclude(env);
}
/**
* Creates a new null bounding octagon bounding a {@link Geometry}
*/
public OctagonalEnvelope(final Geometry geom) {
expandToInclude(geom);
}
/**
* Creates a new null bounding octagon bounding an {@link OctagonalEnvelope}
* (the copy constructor).
*/
public OctagonalEnvelope(final OctagonalEnvelope oct) {
expandToInclude(oct);
}
/**
* Creates a new null bounding octagon bounding a {@link Coordinates}
*/
public OctagonalEnvelope(final Point p) {
expandToInclude(p);
}
/**
* Creates a new null bounding octagon bounding a pair of {@link Coordinates}s
*/
public OctagonalEnvelope(final Point p0, final Point p1) {
expandToInclude(p0);
expandToInclude(p1);
}
public boolean contains(final OctagonalEnvelope other) {
if (isNull() || other.isNull()) {
return false;
}
return other.minX >= this.minX && other.maxX <= this.maxX && other.minY >= this.minY
&& other.maxY <= this.maxY && other.minA >= this.minA && other.maxA <= this.maxA
&& other.minB >= this.minB && other.maxB <= this.maxB;
}
public void expandBy(final double distance) {
if (isNull()) {
return;
}
final double diagonalDistance = SQRT2 * distance;
this.minX -= distance;
this.maxX += distance;
this.minY -= distance;
this.maxY += distance;
this.minA -= diagonalDistance;
this.maxA += diagonalDistance;
this.minB -= diagonalDistance;
this.maxB += diagonalDistance;
if (!isValid()) {
setToNull();
}
}
public OctagonalEnvelope expandToInclude(final BoundingBox env) {
expandToInclude(env.getMinX(), env.getMinY());
expandToInclude(env.getMinX(), env.getMaxY());
expandToInclude(env.getMaxX(), env.getMinY());
expandToInclude(env.getMaxX(), env.getMaxY());
return this;
}
public OctagonalEnvelope expandToInclude(final double x, final double y) {
final double A = computeA(x, y);
final double B = computeB(x, y);
if (isNull()) {
this.minX = x;
this.maxX = x;
this.minY = y;
this.maxY = y;
this.minA = A;
this.maxA = A;
this.minB = B;
this.maxB = B;
} else {
if (x < this.minX) {
this.minX = x;
}
if (x > this.maxX) {
this.maxX = x;
}
if (y < this.minY) {
this.minY = y;
}
if (y > this.maxY) {
this.maxY = y;
}
if (A < this.minA) {
this.minA = A;
}
if (A > this.maxA) {
this.maxA = A;
}
if (B < this.minB) {
this.minB = B;
}
if (B > this.maxB) {
this.maxB = B;
}
}
return this;
}
public void expandToInclude(final Geometry geometry) {
for (final Point point : geometry.getGeometries(Point.class)) {
expandToInclude(point);
}
for (final LineString line : geometry.getGeometryComponents(LineString.class)) {
expandToInclude(line);
}
}
public OctagonalEnvelope expandToInclude(final LineString seq) {
for (int i = 0; i < seq.getVertexCount(); i++) {
final double x = seq.getX(i);
final double y = seq.getY(i);
expandToInclude(x, y);
}
return this;
}
public OctagonalEnvelope expandToInclude(final OctagonalEnvelope oct) {
if (oct.isNull()) {
return this;
}
if (isNull()) {
this.minX = oct.minX;
this.maxX = oct.maxX;
this.minY = oct.minY;
this.maxY = oct.maxY;
this.minA = oct.minA;
this.maxA = oct.maxA;
this.minB = oct.minB;
this.maxB = oct.maxB;
return this;
}
if (oct.minX < this.minX) {
this.minX = oct.minX;
}
if (oct.maxX > this.maxX) {
this.maxX = oct.maxX;
}
if (oct.minY < this.minY) {
this.minY = oct.minY;
}
if (oct.maxY > this.maxY) {
this.maxY = oct.maxY;
}
if (oct.minA < this.minA) {
this.minA = oct.minA;
}
if (oct.maxA > this.maxA) {
this.maxA = oct.maxA;
}
if (oct.minB < this.minB) {
this.minB = oct.minB;
}
if (oct.maxB > this.maxB) {
this.maxB = oct.maxB;
}
return this;
}
public OctagonalEnvelope expandToInclude(final Point p) {
expandToInclude(p.getX(), p.getY());
return this;
}
public double getMaxA() {
return this.maxA;
}
public double getMaxB() {
return this.maxB;
}
public double getMaxX() {
return this.maxX;
}
public double getMaxY() {
return this.maxY;
}
public double getMinA() {
return this.minA;
}
public double getMinB() {
return this.minB;
}
public double getMinX() {
return this.minX;
}
public double getMinY() {
return this.minY;
}
public boolean intersects(final OctagonalEnvelope other) {
if (isNull() || other.isNull()) {
return false;
}
if (this.minX > other.maxX) {
return false;
}
if (this.maxX < other.minX) {
return false;
}
if (this.minY > other.maxY) {
return false;
}
if (this.maxY < other.minY) {
return false;
}
if (this.minA > other.maxA) {
return false;
}
if (this.maxA < other.minA) {
return false;
}
if (this.minB > other.maxB) {
return false;
}
if (this.maxB < other.minB) {
return false;
}
return true;
}
public boolean intersects(final Point p) {
if (this.minX > p.getX()) {
return false;
}
if (this.maxX < p.getX()) {
return false;
}
if (this.minY > p.getY()) {
return false;
}
if (this.maxY < p.getY()) {
return false;
}
final double A = computeA(p.getX(), p.getY());
final double B = computeB(p.getX(), p.getY());
if (this.minA > A) {
return false;
}
if (this.maxA < A) {
return false;
}
if (this.minB > B) {
return false;
}
if (this.maxB < B) {
return false;
}
return true;
}
public boolean isNull() {
return Double.isNaN(this.minX);
}
/**
* Tests if the extremal values for this octagon are valid.
*
* @return <code>true</code> if this object has valid values
*/
private boolean isValid() {
if (isNull()) {
return true;
}
return this.minX <= this.maxX && this.minY <= this.maxY && this.minA <= this.maxA
&& this.minB <= this.maxB;
}
/**
* Sets the value of this object to the null value
*/
public void setToNull() {
this.minX = Double.NaN;
}
public Geometry toGeometry(final GeometryFactory geometryFactory) {
if (isNull()) {
return geometryFactory.point();
}
final Point px00 = new PointDoubleXY(geometryFactory.makePrecise(0, this.minX),
geometryFactory.makePrecise(1, this.minA - this.minX));
final Point px01 = new PointDoubleXY(geometryFactory.makePrecise(0, this.minX),
geometryFactory.makePrecise(1, this.minX - this.minB));
final Point px10 = new PointDoubleXY(geometryFactory.makePrecise(0, this.maxX),
geometryFactory.makePrecise(1, this.maxX - this.maxB));
final Point px11 = new PointDoubleXY(geometryFactory.makePrecise(0, this.maxX),
geometryFactory.makePrecise(1, this.maxA - this.maxX));
final Point py00 = new PointDoubleXY(geometryFactory.makePrecise(0, this.minA - this.minY),
geometryFactory.makePrecise(1, this.minY));
final Point py01 = new PointDoubleXY(geometryFactory.makePrecise(0, this.minY + this.maxB),
geometryFactory.makePrecise(1, this.minY));
final Point py10 = new PointDoubleXY(geometryFactory.makePrecise(0, this.maxY + this.minB),
geometryFactory.makePrecise(1, this.maxY));
final Point py11 = new PointDoubleXY(geometryFactory.makePrecise(0, this.maxA - this.maxY),
geometryFactory.makePrecise(1, this.maxY));
final PointList coordList = new PointList();
coordList.add(px00, false);
coordList.add(px01, false);
coordList.add(py10, false);
coordList.add(py11, false);
coordList.add(px11, false);
coordList.add(px10, false);
coordList.add(py01, false);
coordList.add(py00, false);
if (coordList.size() == 1) {
return geometryFactory.point(px00);
}
if (coordList.size() == 2) {
return geometryFactory.lineString(coordList);
}
// must be a polygon, so add closing point
coordList.add(px00, false);
return geometryFactory.polygon(geometryFactory.linearRing(coordList));
}
}