/*
* 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.operation.relate;
/**
* @version 1.7
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.revolsys.geometry.algorithm.LineIntersector;
import com.revolsys.geometry.algorithm.PointLocator;
import com.revolsys.geometry.algorithm.RobustLineIntersector;
import com.revolsys.geometry.geomgraph.Edge;
import com.revolsys.geometry.geomgraph.EdgeEnd;
import com.revolsys.geometry.geomgraph.EdgeIntersection;
import com.revolsys.geometry.geomgraph.GeometryGraph;
import com.revolsys.geometry.geomgraph.Label;
import com.revolsys.geometry.geomgraph.Node;
import com.revolsys.geometry.geomgraph.NodeMap;
import com.revolsys.geometry.geomgraph.index.SegmentIntersector;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.IntersectionMatrix;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.util.Assert;
/**
* Computes the topological relationship between two Geometries.
* <p>
* RelateComputer does not need to build a complete graph structure to compute
* the IntersectionMatrix. The relationship between the geometries can
* be computed by simply examining the labelling of edges incident on each node.
* <p>
* RelateComputer does not currently support arbitrary GeometryCollections.
* This is because GeometryCollections can contain overlapping Polygons.
* In order to correct compute relate on overlapping Polygons, they
* would first need to be noded and merged (if not explicitly, at least
* implicitly).
*
* @version 1.7
*/
public class RelateComputer {
private final GeometryGraph[] arg; // the arg(s) of the operation
// this intersection matrix will hold the results compute for the relate
private final ArrayList isolatedEdges = new ArrayList();
private final LineIntersector li = new RobustLineIntersector();
private final NodeMap nodes = new NodeMap(new RelateNodeFactory());
private final PointLocator ptLocator = new PointLocator();
public RelateComputer(final GeometryGraph[] arg) {
this.arg = arg;
}
/**
* If the Geometries are disjoint, we need to enter their dimension and
* boundary dimension in the Ext rows in the IM
*/
private void computeDisjointIM(final IntersectionMatrix im) {
final Geometry ga = this.arg[0].getGeometry();
if (!ga.isEmpty()) {
im.set(Location.INTERIOR, Location.EXTERIOR, ga.getDimension());
im.set(Location.BOUNDARY, Location.EXTERIOR, ga.getBoundaryDimension());
}
final Geometry gb = this.arg[1].getGeometry();
if (!gb.isEmpty()) {
im.set(Location.EXTERIOR, Location.INTERIOR, gb.getDimension());
im.set(Location.EXTERIOR, Location.BOUNDARY, gb.getBoundaryDimension());
}
}
public IntersectionMatrix computeIM() {
final IntersectionMatrix im = new IntersectionMatrix();
// since Geometries are finite and embedded in a 2-D space, the EE element
// must always be 2
im.set(Location.EXTERIOR, Location.EXTERIOR, 2);
// if the Geometries don't overlap there is nothing to do
if (!this.arg[0].getGeometry()
.getBoundingBox()
.intersects(this.arg[1].getGeometry().getBoundingBox())) {
computeDisjointIM(im);
return im;
}
this.arg[0].computeSelfNodes(this.li, false);
this.arg[1].computeSelfNodes(this.li, false);
// compute intersections between edges of the two input geometries
final SegmentIntersector intersector = this.arg[0].computeEdgeIntersections(this.arg[1],
this.li, false);
// System.out.println("computeIM: # segment intersection tests: " +
// intersector.numTests);
computeIntersectionNodes(0);
computeIntersectionNodes(1);
/**
* Copy the labelling for the nodes in the parent Geometries. These override
* any labels determined by intersections between the geometries.
*/
copyNodesAndLabels(0);
copyNodesAndLabels(1);
// complete the labelling for any nodes which only have a label for a single
// geometry
// Debug.addWatch(nodes.find(new PointDouble((double)110, 200)));
// Debug.printWatch();
labelIsolatedNodes();
// Debug.printWatch();
// If a proper intersection was found, we can set a lower bound on the IM.
computeProperIntersectionIM(intersector, im);
/**
* Now process improper intersections
* (eg where one or other of the geometries has a vertex at the intersection point)
* We need to compute the edge graph at all nodes to determine the IM.
*/
// build EdgeEnds for all intersections
final EdgeEndBuilder eeBuilder = new EdgeEndBuilder();
final List<EdgeEnd> ee0 = eeBuilder.computeEdgeEnds(this.arg[0].edges());
insertEdgeEnds(ee0);
final List<EdgeEnd> ee1 = eeBuilder.computeEdgeEnds(this.arg[1].edges());
insertEdgeEnds(ee1);
// Debug.println("==== NodeList ===");
// Debug.print(nodes);
labelNodeEdges();
/**
* Compute the labeling for isolated components
* <br>
* Isolated components are components that do not touch any other components in the graph.
* They can be identified by the fact that they will
* contain labels containing ONLY a single element, the one for their parent geometry.
* We only need to check components contained in the input graphs, since
* isolated components will not have been replaced by new components formed by intersections.
*/
// debugPrintln("Graph A isolated edges - ");
labelIsolatedEdges(0, 1);
// debugPrintln("Graph B isolated edges - ");
labelIsolatedEdges(1, 0);
// update the IM from all components
updateIM(im);
return im;
}
/**
* Insert nodes for all intersections on the edges of a Geometry.
* Label the created nodes the same as the edge label if they do not already have a label.
* This allows nodes created by either self-intersections or
* mutual intersections to be labelled.
* Endpoint nodes will already be labelled from when they were inserted.
*/
private void computeIntersectionNodes(final int argIndex) {
for (final Iterator i = this.arg[argIndex].getEdgeIterator(); i.hasNext();) {
final Edge e = (Edge)i.next();
final Location eLoc = e.getLabel().getLocation(argIndex);
for (final Object element : e.getEdgeIntersectionList()) {
final EdgeIntersection ei = (EdgeIntersection)element;
final RelateNode n = (RelateNode)this.nodes.addNode(ei.newPoint2D());
if (eLoc == Location.BOUNDARY) {
n.setLabelBoundary(argIndex);
} else {
if (n.getLabel().isNull(argIndex)) {
n.setLabel(argIndex, Location.INTERIOR);
}
}
// Debug.println(n);
}
}
}
private void computeProperIntersectionIM(final SegmentIntersector intersector,
final IntersectionMatrix im) {
// If a proper intersection is found, we can set a lower bound on the IM.
final int dimA = this.arg[0].getGeometry().getDimension();
final int dimB = this.arg[1].getGeometry().getDimension();
final boolean hasProper = intersector.hasProperIntersection();
final boolean hasProperInterior = intersector.hasProperInteriorIntersection();
// For Geometry's of dim 0 there can never be proper intersections.
/**
* If edge segments of Areas properly intersect, the areas must properly overlap.
*/
if (dimA == 2 && dimB == 2) {
if (hasProper) {
im.setAtLeast("212101212");
}
}
/**
* If an Line segment properly intersects an edge segment of an Area,
* it follows that the Interior of the Line intersects the Boundary of the Area.
* If the intersection is a proper <i>interior</i> intersection, then
* there is an Interior-Interior intersection too.
* Note that it does not follow that the Interior of the Line intersects the Exterior
* of the Area, since there may be another Area component which contains the rest of the Line.
*/
else if (dimA == 2 && dimB == 1) {
if (hasProper) {
im.setAtLeast("FFF0FFFF2");
}
if (hasProperInterior) {
im.setAtLeast("1FFFFF1FF");
}
} else if (dimA == 1 && dimB == 2) {
if (hasProper) {
im.setAtLeast("F0FFFFFF2");
}
if (hasProperInterior) {
im.setAtLeast("1F1FFFFFF");
}
}
/*
* If edges of LineStrings properly intersect *in an interior point*, all we can deduce is that
* the interiors intersect. (We can NOT deduce that the exteriors intersect, since some other
* segments in the geometries might cover the points in the neighbourhood of the intersection.)
* It is important that the point be known to be an interior point of both Geometries, since it
* is possible in a self-intersecting geometry to have a proper intersection on one segment that
* is also a boundary point of another segment.
*/
else if (dimA == 1 && dimB == 1) {
if (hasProperInterior) {
im.setAtLeast("0FFFFFFFF");
}
}
}
/**
* Copy all nodes from an arg geometry into this graph.
* The node label in the arg geometry overrides any previously computed
* label for that argIndex.
* (E.g. a node may be an intersection node with
* a computed label of BOUNDARY,
* but in the original arg Geometry it is actually
* in the interior due to the Boundary Determination Rule)
*/
private void copyNodesAndLabels(final int argIndex) {
for (final Iterator i = this.arg[argIndex].getNodeIterator(); i.hasNext();) {
final Node graphNode = (Node)i.next();
final Node newNode = this.nodes.addNode(graphNode.getPoint());
newNode.setLabel(argIndex, graphNode.getLabel().getLocation(argIndex));
// node.print(System.out);
}
}
private void insertEdgeEnds(final List ee) {
for (final Iterator i = ee.iterator(); i.hasNext();) {
final EdgeEnd e = (EdgeEnd)i.next();
this.nodes.add(e);
}
}
/**
* Label an isolated edge of a graph with its relationship to the target geometry.
* If the target has dim 2 or 1, the edge can either be in the interior or the exterior.
* If the target has dim 0, the edge must be in the exterior
*/
private void labelIsolatedEdge(final Edge e, final int targetIndex, final Geometry target) {
// this won't work for GeometryCollections with both dim 2 and 1 geoms
if (target.getDimension() > 0) {
// since edge is not in boundary, may not need the full generality of
// PointLocator?
// Possibly should use ptInArea locator instead? We probably know here
// that the edge does not touch the bdy of the target Geometry
final Location loc = this.ptLocator.locate(e.getPoint(), target);
e.getLabel().setAllLocations(targetIndex, loc);
} else {
e.getLabel().setAllLocations(targetIndex, Location.EXTERIOR);
}
// System.out.println(e.getLabel());
}
/**
* Processes isolated edges by computing their labelling and adding them
* to the isolated edges list.
* Isolated edges are guaranteed not to touch the boundary of the target (since if they
* did, they would have caused an intersection to be computed and hence would
* not be isolated)
*/
private void labelIsolatedEdges(final int thisIndex, final int targetIndex) {
for (final Iterator ei = this.arg[thisIndex].getEdgeIterator(); ei.hasNext();) {
final Edge e = (Edge)ei.next();
if (e.isIsolated()) {
labelIsolatedEdge(e, targetIndex, this.arg[targetIndex].getGeometry());
this.isolatedEdges.add(e);
}
}
}
/**
* Label an isolated node with its relationship to the target geometry.
*/
private void labelIsolatedNode(final Node n, final int targetIndex) {
final Location loc = this.ptLocator.locate(n.getPoint(), this.arg[targetIndex].getGeometry());
n.getLabel().setAllLocations(targetIndex, loc);
// debugPrintln(n.getLabel());
}
/**
* Isolated nodes are nodes whose labels are incomplete
* (e.g. the location for one Geometry is null).
* This is the case because nodes in one graph which don't intersect
* nodes in the other are not completely labelled by the initial process
* of adding nodes to the nodeList.
* To complete the labelling we need to check for nodes that lie in the
* interior of edges, and in the interior of areas.
*/
private void labelIsolatedNodes() {
for (final Object element : this.nodes) {
final Node n = (Node)element;
final Label label = n.getLabel();
// isolated nodes should always have at least one geometry in their label
Assert.isTrue(label.getGeometryCount() > 0, "node with empty label found");
if (n.isIsolated()) {
if (label.isNull(0)) {
labelIsolatedNode(n, 0);
} else {
labelIsolatedNode(n, 1);
}
}
}
}
private void labelNodeEdges() {
for (final Object element : this.nodes) {
final RelateNode node = (RelateNode)element;
node.getEdges().computeLabelling(this.arg);
// Debug.print(node.getEdges());
// node.print(System.out);
}
}
/**
* update the IM with the sum of the IMs for each component
*/
private void updateIM(final IntersectionMatrix im) {
// Debug.println(im);
for (final Iterator ei = this.isolatedEdges.iterator(); ei.hasNext();) {
final Edge e = (Edge)ei.next();
e.updateIM(im);
// Debug.println(im);
}
for (final Object element : this.nodes) {
final RelateNode node = (RelateNode)element;
node.updateIM(im);
// Debug.println(im);
node.updateIMFromEdges(im);
// Debug.println(im);
// node.print(System.out);
}
}
}