/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.geom;
/**
* A bounding container for a {@link Geometry} which is in the shape of a general octagon.
* The OctagonalEnvelope of a geometric object
* is a geometry which is a tight bound
* along the (up to) four extremal rectilinear parallels
* and along the (up to) 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
{
/**
* Gets the octagonal envelope of a geometry
* @param geom the geometry
* @return the octagonal envelope of the geometry
*/
public static Geometry octagonalEnvelope(Geometry geom) {
return (new OctagonalEnvelope(geom)).toGeometry(geom.getFactory());
}
private static double computeA(double x, double y)
{
return x + y;
}
private static double computeB(double x, double y)
{
return x - y;
}
private static double SQRT2 = Math.sqrt(2.0);
// initialize in the null state
private double minX = Double.NaN;
private double maxX;
private double minY;
private double maxY;
private double minA;
private double maxA;
private double minB;
private double maxB;
/**
* Creates a new null bounding octagon
*/
public OctagonalEnvelope()
{
}
/**
* Creates a new null bounding octagon bounding a {@link Coordinate}
*
* @param p the coordinate to bound
*/
public OctagonalEnvelope(Coordinate p)
{
expandToInclude(p);
}
/**
* Creates a new null bounding octagon bounding a pair of {@link Coordinate}s
*
* @param p0 a coordinate to bound
* @param p1 a coordinate to bound
*/
public OctagonalEnvelope(Coordinate p0, Coordinate p1)
{
expandToInclude(p0);
expandToInclude(p1);
}
/**
* Creates a new null bounding octagon bounding an {@link Envelope}
*/
public OctagonalEnvelope(Envelope env)
{
expandToInclude(env);
}
/**
* Creates a new null bounding octagon bounding an {@link OctagonalEnvelope}
* (the copy constructor).
*/
public OctagonalEnvelope(OctagonalEnvelope oct)
{
expandToInclude(oct);
}
/**
* Creates a new null bounding octagon bounding a {@link Geometry}
*/
public OctagonalEnvelope(Geometry geom)
{
expandToInclude(geom);
}
public double getMinX() { return minX; }
public double getMaxX() { return maxX; }
public double getMinY() { return minY; }
public double getMaxY() { return maxY; }
public double getMinA() { return minA; }
public double getMaxA() { return maxA; }
public double getMinB() { return minB; }
public double getMaxB() { return maxB; }
public boolean isNull() { return Double.isNaN(minX); }
/**
* Sets the value of this object to the null value
*/
public void setToNull() {
minX = Double.NaN;
}
public void expandToInclude(Geometry g)
{
g.apply(new BoundingOctagonComponentFilter());
}
public OctagonalEnvelope expandToInclude(CoordinateSequence seq)
{
for (int i = 0; i < seq.size(); i++) {
double x = seq.getX(i);
double y = seq.getY(i);
expandToInclude(x, y);
}
return this;
}
public OctagonalEnvelope expandToInclude(OctagonalEnvelope oct)
{
if (oct.isNull()) return this;
if (isNull()) {
minX = oct.minX;
maxX = oct.maxX;
minY = oct.minY;
maxY = oct.maxY;
minA = oct.minA;
maxA = oct.maxA;
minB = oct.minB;
maxB = oct.maxB;
return this;
}
if (oct.minX < minX) minX = oct.minX;
if (oct.maxX > maxX) maxX = oct.maxX;
if (oct.minY < minY) minY = oct.minY;
if (oct.maxY > maxY) maxY = oct.maxY;
if (oct.minA < minA) minA = oct.minA;
if (oct.maxA > maxA) maxA = oct.maxA;
if (oct.minB < minB) minB = oct.minB;
if (oct.maxB > maxB) maxB = oct.maxB;
return this;
}
public OctagonalEnvelope expandToInclude(Coordinate p)
{
expandToInclude(p.x, p.y);
return this;
}
public OctagonalEnvelope expandToInclude(Envelope 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(double x, double y)
{
double A = computeA(x, y);
double B = computeB(x, y);
if (isNull()) {
minX = x;
maxX = x;
minY = y;
maxY = y;
minA = A;
maxA = A;
minB = B;
maxB = B;
}
else {
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
if (A < minA) minA = A;
if (A > maxA) maxA = A;
if (B < minB) minB = B;
if (B > maxB) maxB = B;
}
return this;
}
public void expandBy(double distance)
{
if (isNull()) return;
double diagonalDistance = SQRT2 * distance;
minX -= distance;
maxX += distance;
minY -= distance;
maxY += distance;
minA -= diagonalDistance;
maxA += diagonalDistance;
minB -= diagonalDistance;
maxB += diagonalDistance;
if (! isValid())
setToNull();
}
/**
* 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 minX <= maxX
&& minY <= maxY
&& minA <= maxA
&& minB <= maxB;
}
public boolean intersects(OctagonalEnvelope other)
{
if (isNull() || other.isNull()) { return false; }
if (minX > other.maxX) return false;
if (maxX < other.minX) return false;
if (minY > other.maxY) return false;
if (maxY < other.minY) return false;
if (minA > other.maxA) return false;
if (maxA < other.minA) return false;
if (minB > other.maxB) return false;
if (maxB < other.minB) return false;
return true;
}
public boolean intersects(Coordinate p)
{
if (minX > p.x) return false;
if (maxX < p.x) return false;
if (minY > p.y) return false;
if (maxY < p.y) return false;
double A = computeA(p.x, p.y);
double B = computeB(p.x, p.y);
if (minA > A) return false;
if (maxA < A) return false;
if (minB > B) return false;
if (maxB < B) return false;
return true;
}
public boolean contains(OctagonalEnvelope other)
{
if (isNull() || other.isNull()) { return false; }
return other.minX >= minX
&& other.maxX <= maxX
&& other.minY >= minY
&& other.maxY <= maxY
&& other.minA >= minA
&& other.maxA <= maxA
&& other.minB >= minB
&& other.maxB <= maxB;
}
public Geometry toGeometry(GeometryFactory geomFactory)
{
if (isNull()) {
return geomFactory.createPoint((CoordinateSequence)null);
}
Coordinate px00 = new Coordinate(minX, minA - minX);
Coordinate px01 = new Coordinate(minX, minX - minB);
Coordinate px10 = new Coordinate(maxX, maxX - maxB);
Coordinate px11 = new Coordinate(maxX, maxA - maxX);
Coordinate py00 = new Coordinate(minA - minY, minY);
Coordinate py01 = new Coordinate(minY + maxB, minY);
Coordinate py10 = new Coordinate(maxY + minB, maxY);
Coordinate py11 = new Coordinate(maxA - maxY, maxY);
PrecisionModel pm = geomFactory.getPrecisionModel();
pm.makePrecise(px00);
pm.makePrecise(px01);
pm.makePrecise(px10);
pm.makePrecise(px11);
pm.makePrecise(py00);
pm.makePrecise(py01);
pm.makePrecise(py10);
pm.makePrecise(py11);
CoordinateList coordList = new CoordinateList();
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 geomFactory.createPoint(px00);
}
if (coordList.size() == 2) {
Coordinate[] pts = coordList.toCoordinateArray();
return geomFactory.createLineString(pts);
}
// must be a polygon, so add closing point
coordList.add(px00, false);
Coordinate[] pts = coordList.toCoordinateArray();
return geomFactory.createPolygon(geomFactory.createLinearRing(pts), null);
}
private class BoundingOctagonComponentFilter
implements GeometryComponentFilter
{
public void filter(Geometry geom)
{
if (geom instanceof LineString) {
expandToInclude( ((LineString) geom).getCoordinateSequence());
}
else if (geom instanceof Point) {
expandToInclude( ((Point) geom).getCoordinateSequence());
}
}
}
}