/*
* 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.prep;
import java.util.List;
import com.revolsys.geometry.algorithm.locate.PointOnGeometryLocator;
import com.revolsys.geometry.algorithm.locate.SimplePointInAreaLocator;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.Polygonal;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.noding.FastSegmentSetIntersectionFinder;
import com.revolsys.geometry.noding.SegmentIntersectionDetector;
import com.revolsys.geometry.noding.SegmentStringUtil;
/**
* A base class containing the logic for computes the <tt>contains</tt>
* and <tt>covers</tt> spatial relationship predicates
* for a {@link PreparedPolygon} relative to all other {@link Geometry} classes.
* Uses short-circuit tests and indexing to improve performance.
* <p>
* Contains and covers are very similar, and differ only in how certain
* cases along the boundary are handled. These cases require
* full topological evaluation to handle, so all the code in
* this class is common to both predicates.
* <p>
* It is not possible to short-circuit in all cases, in particular
* in the case where line segments of the test geometry touches the originalGeometry linework.
* In this case full topology must be computed.
* (However, if the test geometry consists of only points, this
* <i>can</i> be evaluated in an optimized fashion.
*
* @author Martin Davis
*
*/
abstract class AbstractPreparedPolygonContains {
public static boolean isAllTestComponentsInTarget(final PointOnGeometryLocator pointLocator,
final Geometry geometry) {
for (final Vertex vertex : geometry.vertices()) {
final Location loc = pointLocator.locate(vertex);
if (loc == Location.EXTERIOR) {
return false;
}
}
return true;
}
/**
* Tests whether all components of the test Geometry
* are contained in the interior of the target geometry.
* Handles both linear and point components.
*
* @param geom a geometry to test
* @return true if all componenta of the argument are contained in the target geometry interior
*/
public static boolean isAllTestComponentsInTargetInterior(
final PointOnGeometryLocator pointLocator, final Geometry geometry) {
for (final Vertex vertex : geometry.vertices()) {
final Location loc = pointLocator.locate(vertex);
if (loc != Location.INTERIOR) {
return false;
}
}
return true;
}
/**
* Tests whether any component of the target geometry
* intersects the test geometry (which must be an areal geometry)
*
* @param geom the test geometry
* @param repPts the representative points of the target geometry
* @return true if any component intersects the areal test geometry
*/
public static boolean isAnyTargetComponentInAreaTest(final Geometry testGeom,
final Geometry targetGeom) {
final PointOnGeometryLocator piaLoc = new SimplePointInAreaLocator(testGeom);
for (final Vertex vertex : targetGeom.vertices()) {
final Location loc = piaLoc.locate(vertex);
if (loc != Location.EXTERIOR) {
return true;
}
}
return false;
}
/**
* Tests whether any component of the test Geometry intersects
* the area of the target geometry.
* Handles test geometries with both linear and point components.
*
* @param geom a geometry to test
* @return true if any component of the argument intersects the prepared area geometry
*/
public static boolean isAnyTestComponentInTarget(final PointOnGeometryLocator pointLocator,
final Geometry geometry) {
for (final Vertex vertex : geometry.vertices()) {
final Location loc = pointLocator.locate(vertex);
if (loc != Location.EXTERIOR) {
return true;
}
}
return false;
}
/**
* Tests whether any component of the test Geometry intersects
* the interior of the target geometry.
* Handles test geometries with both linear and point components.
*
* @param geom a geometry to test
* @return true if any component of the argument intersects the prepared area geometry interior
*/
public static boolean isAnyTestComponentInTargetInterior(
final PointOnGeometryLocator pointLocator, final Geometry geometry) {
for (final Vertex vertex : geometry.vertices()) {
final Location loc = pointLocator.locate(vertex);
if (loc == Location.INTERIOR) {
return true;
}
}
return false;
}
private boolean hasNonProperIntersection = false;
private boolean hasProperIntersection = false;
// information about geometric situation
private boolean hasSegmentIntersection = false;
private final Geometry originalGeometry;
protected Geometry preparedGeometry;
/**
* This flag controls a difference between contains and covers.
*
* For contains the value is true.
* For covers the value is false.
*/
protected boolean requireSomePointInInterior = true;
public AbstractPreparedPolygonContains(final Geometry preparedPolygon, final Geometry polygon) {
this.preparedGeometry = preparedPolygon;
this.originalGeometry = polygon;
}
/**
* Evaluate the <tt>contains</tt> or <tt>covers</tt> relationship
* for the given geometry.
*
* @param geometry the test geometry
* @return true if the test geometry is contained
*/
protected boolean eval(final Geometry geometry) {
/**
* Do point-in-poly tests first, since they are cheaper and may result
* in a quick negative result.
*
* If a point of any test components does not lie in target, result is false
*/
final boolean isAllInTargetArea = isAllTestComponentsInTarget(getPointLocator(), geometry);
if (!isAllInTargetArea) {
return false;
}
/**
* If the test geometry consists of only Points,
* then it is now sufficient to test if any of those
* points lie in the interior of the target geometry.
* If so, the test is contained.
* If not, all points are on the boundary of the area,
* which implies not contained.
*/
if (this.requireSomePointInInterior && geometry.getDimension() == 0) {
final boolean isAnyInTargetInterior = isAnyTestComponentInTargetInterior(getPointLocator(),
geometry);
return isAnyInTargetInterior;
}
/**
* Check if there is any intersection between the line segments
* in target and test.
* In some important cases, finding a proper interesection implies that the
* test geometry is NOT contained.
* These cases are:
* <ul>
* <li>If the test geometry is polygonal
* <li>If the target geometry is a single originalGeometry with no holes
* <ul>
* In both of these cases, a proper intersection implies that there
* is some portion of the interior of the test geometry lying outside
* the target, which means that the test is not contained.
*/
final boolean properIntersectionImpliesNotContained = isProperIntersectionImpliesNotContainedSituation(
geometry);
// MD - testing only
// properIntersectionImpliesNotContained = true;
// find all intersection types which exist
findAndClassifyIntersections(geometry);
if (properIntersectionImpliesNotContained && this.hasProperIntersection) {
return false;
}
/**
* If all intersections are proper
* (i.e. no non-proper intersections occur)
* we can conclude that the test geometry is not contained in the target area,
* by the Epsilon-Neighbourhood Exterior Intersection condition.
* In real-world data this is likely to be by far the most common situation,
* since natural data is unlikely to have many exact vertex segment intersections.
* Thus this check is very worthwhile, since it avoid having to perform
* a full topological check.
*
* (If non-proper (vertex) intersections ARE found, this may indicate
* a situation where two shells touch at a single vertex, which admits
* the case where a line could cross between the shells and still be wholely contained in them.
*/
if (this.hasSegmentIntersection && !this.hasNonProperIntersection) {
return false;
}
/**
* If there is a segment intersection and the situation is not one
* of the ones above, the only choice is to compute the full topological
* relationship. This is because contains/covers is very sensitive
* to the situation along the boundary of the target.
*/
if (this.hasSegmentIntersection) {
return fullTopologicalPredicate(geometry);
// System.out.println(geom);
}
/**
* This tests for the case where a ring of the target lies inside
* a test originalGeometry - which implies the exterior of the Target
* intersects the interior of the Test, and hence the result is false
*/
if (geometry instanceof Polygonal) {
// TODO: generalize this to handle GeometryCollections
final boolean isTargetInTestArea = isAnyTargetComponentInAreaTest(geometry,
this.preparedGeometry);
if (isTargetInTestArea) {
return false;
}
}
return true;
}
private void findAndClassifyIntersections(final Geometry geom) {
final List lineSegStr = SegmentStringUtil.extractSegmentStrings(geom);
final SegmentIntersectionDetector intDetector = new SegmentIntersectionDetector();
intDetector.setFindAllIntersectionTypes(true);
getIntersectionFinder().intersects(lineSegStr, intDetector);
this.hasSegmentIntersection = intDetector.hasIntersection();
this.hasProperIntersection = intDetector.hasProperIntersection();
this.hasNonProperIntersection = intDetector.hasNonProperIntersection();
}
/**
* Computes the full topological predicate.
* Used when short-circuit tests are not conclusive.
*
* @param geom the test geometry
* @return true if this prepared originalGeometry has the relationship with the test geometry
*/
protected abstract boolean fullTopologicalPredicate(Geometry geom);
private FastSegmentSetIntersectionFinder getIntersectionFinder() {
if (this.preparedGeometry instanceof PreparedPolygon) {
final PreparedPolygon preparedPolygon = (PreparedPolygon)this.preparedGeometry;
return preparedPolygon.getIntersectionFinder();
} else if (this.preparedGeometry instanceof PreparedMultiPolygon) {
final PreparedMultiPolygon preparedPolygon = (PreparedMultiPolygon)this.preparedGeometry;
return preparedPolygon.getIntersectionFinder();
} else {
return null;
}
}
public Geometry getOriginalGeometry() {
return this.originalGeometry;
}
private PointOnGeometryLocator getPointLocator() {
if (this.preparedGeometry instanceof PreparedPolygon) {
final PreparedPolygon preparedPolygon = (PreparedPolygon)this.preparedGeometry;
return preparedPolygon.getPointLocator();
} else if (this.preparedGeometry instanceof PreparedMultiPolygon) {
final PreparedMultiPolygon preparedPolygon = (PreparedMultiPolygon)this.preparedGeometry;
return preparedPolygon.getPointLocator();
} else {
return null;
}
}
private boolean isProperIntersectionImpliesNotContainedSituation(final Geometry testGeom) {
/**
* If the test geometry is polygonal we have the A/A situation.
* In this case, a proper intersection indicates that
* the Epsilon-Neighbourhood Exterior Intersection condition exists.
* This condition means that in some small
* area around the intersection point, there must exist a situation
* where the interior of the test intersects the exterior of the target.
* This implies the test is NOT contained in the target.
*/
if (testGeom instanceof Polygonal) {
return true;
}
/**
* A single shell with no holes allows concluding that
* a proper intersection implies not contained
* (due to the Epsilon-Neighbourhood Exterior Intersection condition)
*/
if (isSingleShell(this.originalGeometry)) {
return true;
}
return false;
}
/**
* Tests whether a geometry consists of a single originalGeometry with no holes.
*
* @return true if the geometry is a single originalGeometry with no holes
*/
private boolean isSingleShell(final Geometry geom) {
// handles single-element MultiPolygons, as well as Polygons
if (geom.getGeometryCount() != 1) {
return false;
}
final Polygon poly = (Polygon)geom.getGeometry(0);
final int numHoles = poly.getHoleCount();
if (numHoles == 0) {
return true;
}
return false;
}
}