/*
* 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.algorithm;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.impl.PointDoubleXY;
/**
* Computes a point in the interior of an areal geometry.
*
* <h2>Algorithm</h2>
* <ul>
* <li>Find a Y value which is close to the centre of
* the geometry's vertical extent but is different
* to any of it's Y ordinates.
* <li>Construct a new horizontal bisector line using the Y value
* and the geometry's horizontal extent
* <li>Find the intersection between the geometry
* and the horizontal bisector line.
* The intersection is a collection of lines and points.
* <li>Pick the midpoint of the largest intersection geometry
* </ul>
*
* <h3>KNOWN BUGS</h3>
* <ul>
* <li>If a fixed precision model is used,
* in some cases this method may return a point
* which does not lie in the interior.
* </ul>
*
* @version 1.7
*/
public class InteriorPointArea {
/**
* Finds a safe bisector Y ordinate
* by projecting to the Y axis
* and finding the Y-ordinate interval
* which contains the centre of the Y extent.
* The centre of this interval is returned as the bisector Y-ordinate.
*
* @author mdavis
*
*/
private static class SafeBisectorFinder {
public static double getBisectorY(final Polygon poly) {
final SafeBisectorFinder finder = new SafeBisectorFinder(poly);
return finder.getBisectorY();
}
private final double centreY;
private double hiY = Double.MAX_VALUE;
private double loY = -Double.MAX_VALUE;
private final Polygon poly;
public SafeBisectorFinder(final Polygon poly) {
this.poly = poly;
// initialize using extremal values
this.hiY = poly.getBoundingBox().getMaxY();
this.loY = poly.getBoundingBox().getMinY();
this.centreY = avg(this.loY, this.hiY);
}
public double getBisectorY() {
process(this.poly.getShell());
for (int i = 0; i < this.poly.getHoleCount(); i++) {
process(this.poly.getHole(i));
}
final double bisectY = avg(this.hiY, this.loY);
return bisectY;
}
private void process(final LineString line) {
final LineString seq = line;
for (int i = 0; i < seq.getVertexCount(); i++) {
final double y = seq.getY(i);
updateInterval(y);
}
}
private void updateInterval(final double y) {
if (y <= this.centreY) {
if (y > this.loY) {
this.loY = y;
}
} else if (y > this.centreY) {
if (y < this.hiY) {
this.hiY = y;
}
}
}
}
private static double avg(final double a, final double b) {
return (a + b) / 2.0;
}
/**
* Returns the centre point of the envelope.
* @param envelope the envelope to analyze
* @return the centre of the envelope
*/
public static Point centre(final BoundingBox envelope) {
return new PointDoubleXY(avg(envelope.getMinX(), envelope.getMaxX()),
avg(envelope.getMinY(), envelope.getMaxY()));
}
private final GeometryFactory factory;
private Point interiorPoint = null;
private double maxWidth = 0.0;
/**
* Creates a new interior point finder
* for an areal geometry.
*
* @param g an areal geometry
*/
public InteriorPointArea(final Geometry g) {
this.factory = g.getGeometryFactory();
add(g);
}
/**
* Tests the interior vertices (if any)
* defined by an areal Geometry for the best inside point.
* If a component Geometry is not of dimension 2 it is not tested.
*
* @param geometry the geometry to add
*/
private void add(final Geometry geometry) {
if (geometry instanceof Polygon) {
addPolygon(geometry);
} else if (geometry.isGeometryCollection()) {
for (final Geometry part : geometry.geometries()) {
add(part);
}
}
}
/**
* Finds an interior point of a Polygon.
* @param geometry the geometry to analyze
*/
private void addPolygon(final Geometry geometry) {
if (geometry.isEmpty()) {
return;
}
Point intPt;
double width = 0;
final LineString bisector = horizontalBisector(geometry);
if (bisector.getLength() == 0.0) {
width = 0;
intPt = bisector.getPoint();
} else {
final Geometry intersections = bisector.intersection(geometry);
final Geometry widestIntersection = widestGeometry(intersections);
width = widestIntersection.getBoundingBox().getWidth();
intPt = centre(widestIntersection.getBoundingBox());
}
if (this.interiorPoint == null || width > this.maxWidth) {
this.interiorPoint = intPt;
this.maxWidth = width;
}
}
/**
* Gets the computed interior point.
*
* @return the coordinate of an interior point
*/
public Point getInteriorPoint() {
return this.interiorPoint;
}
protected LineString horizontalBisector(final Geometry geometry) {
final BoundingBox envelope = geometry.getBoundingBox();
/**
* Original algorithm. Fails when geometry contains a horizontal
* segment at the Y midpoint.
*/
// Assert: for areas, minx <> maxx
// double avgY = avg(envelope.getMinY(), envelope.getMaxY());
final double bisectY = SafeBisectorFinder.getBisectorY((Polygon)geometry);
return this.factory.lineString(2, envelope.getMinX(), bisectY, envelope.getMaxX(), bisectY);
}
// @return if geometry is a collection, the widest sub-geometry; otherwise,
// the geometry itself
private Geometry widestGeometry(final Geometry geometry) {
if (geometry.isGeometryCollection()) {
if (geometry.isEmpty()) {
return geometry;
} else {
double widestWidth = 0;
Geometry widestGeometry = null;
// scan remaining geom components to see if any are wider
for (final Geometry part : geometry.geometries()) {
final double width = part.getBoundingBox().getWidth();
if (widestGeometry == null || width > widestWidth) {
widestGeometry = part;
widestWidth = width;
}
}
return widestGeometry;
}
} else {
return geometry;
}
}
}