/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2006 Vivid Solutions * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. */ package org.geotools.geometry.iso.operation.relate; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.geotools.geometry.iso.coordinate.EnvelopeImpl; import org.geotools.geometry.iso.root.GeometryImpl; import org.geotools.geometry.iso.topograph2D.Dimension; import org.geotools.geometry.iso.topograph2D.Edge; import org.geotools.geometry.iso.topograph2D.EdgeEnd; import org.geotools.geometry.iso.topograph2D.EdgeIntersection; import org.geotools.geometry.iso.topograph2D.GeometryGraph; import org.geotools.geometry.iso.topograph2D.IntersectionMatrix; import org.geotools.geometry.iso.topograph2D.Label; import org.geotools.geometry.iso.topograph2D.Location; import org.geotools.geometry.iso.topograph2D.Node; import org.geotools.geometry.iso.topograph2D.NodeMap; import org.geotools.geometry.iso.topograph2D.index.SegmentIntersector; import org.geotools.geometry.iso.util.Assert; import org.geotools.geometry.iso.util.algorithm2D.LineIntersector; import org.geotools.geometry.iso.util.algorithm2D.PointLocator; import org.geotools.geometry.iso.util.algorithm2D.RobustLineIntersector; /** * 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). * * * * @source $URL$ */ public class RelateComputer { private LineIntersector li = new RobustLineIntersector(); private PointLocator ptLocator = new PointLocator(); private GeometryGraph[] arg; // the arg(s) of the operation private NodeMap nodes = new NodeMap(new RelateNodeFactory()); // this intersection matrix will hold the results compute for the relate //private IntersectionMatrix im = null; private ArrayList isolatedEdges = new ArrayList(); // the intersection point found (if any) //private Coordinate invalidPoint; public RelateComputer(GeometryGraph[] arg) { this.arg = arg; } /** * Computes the Intersection Matrix for the two given geometry objects * @return Intersection Matrix */ public IntersectionMatrix computeIM() { IntersectionMatrix tIM = new IntersectionMatrix(); // since Geometries are finite and embedded in a 2-D space, the EE // element must always be 2 tIM.set(Location.EXTERIOR, Location.EXTERIOR, 2); // if the Geometries don't overlap there is nothing to do // if (!arg[0].getGeometry().getEnvelopeInternal().intersects( // arg[1].getGeometry().getEnvelopeInternal())) { EnvelopeImpl env1 = (EnvelopeImpl) arg[0].getGeometry().getEnvelope(); EnvelopeImpl env2 = (EnvelopeImpl) arg[1].getGeometry().getEnvelope(); if (!env1.intersects(env2)) { computeDisjointIM(tIM); return tIM; } this.arg[0].computeSelfNodes(this.li, false); this.arg[1].computeSelfNodes(this.li, false); // compute intersections between edges of the two input geometries SegmentIntersector tIntersector = this.arg[0].computeEdgeIntersections(this.arg[1], this.li, false); this.computeIntersectionNodes(0); this.computeIntersectionNodes(1); // Copy the labelling for the nodes in the parent Geometries. These // override any labels determined by intersections between the // geometries. this.copyNodesAndLabels(0); this.copyNodesAndLabels(1); // complete the labelling for any nodes which only have a label for a // single geometry this.labelIsolatedNodes(); // If a proper intersection was found, // we can set a lower bound on the IM. this.computeProperIntersectionIM(tIntersector, tIM); // Now process improper intersections (e.g. 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 EdgeEndBuilder eeBuilder = new EdgeEndBuilder(); List ee0 = eeBuilder.computeEdgeEnds(arg[0].getEdgeIterator()); this.insertEdgeEnds(ee0); List ee1 = eeBuilder.computeEdgeEnds(arg[1].getEdgeIterator()); this.insertEdgeEnds(ee1); this.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. this.labelIsolatedEdges(0, 1); this.labelIsolatedEdges(1, 0); // update the IM from all components this.updateIM(tIM); return tIM; } private void insertEdgeEnds(List ee) { for (Iterator i = ee.iterator(); i.hasNext();) { EdgeEnd e = (EdgeEnd) i.next(); nodes.add(e); } } private void computeProperIntersectionIM(SegmentIntersector intersector, IntersectionMatrix im) { // If a proper intersection is found, we can set a lower bound on the // IM. // int dimA = arg[0].getGeometry().getDimension(); // int dimB = arg[1].getGeometry().getDimension(); int dimA = arg[0].getGeometry().getDimension(null); int dimB = arg[1].getGeometry().getDimension(null); boolean hasProper = intersector.hasProperIntersection(); 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(int argIndex) { for (Iterator i = arg[argIndex].getNodeIterator(); i.hasNext();) { Node graphNode = (Node) i.next(); Node newNode = nodes.addNode(graphNode.getCoordinate()); newNode.setLabel(argIndex, graphNode.getLabel().getLocation( argIndex)); } } /** * 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. * * @param argIndex */ private void computeIntersectionNodes(int argIndex) { for (Iterator i = this.arg[argIndex].getEdgeIterator(); i.hasNext();) { Edge e = (Edge) i.next(); int eLoc = e.getLabel().getLocation(argIndex); for (Iterator eiIt = e.getEdgeIntersectionList().iterator(); eiIt .hasNext();) { EdgeIntersection ei = (EdgeIntersection) eiIt.next(); RelateNode n = (RelateNode) this.nodes.addNode(ei.coord); if (eLoc == Location.BOUNDARY) n.setLabelBoundary(argIndex); else { if (n.getLabel().isNull(argIndex)) n.setLabel(argIndex, Location.INTERIOR); } } } } /** * For all intersections on the edges of a Geometry, label the corresponding * node IF it doesn't 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 labelIntersectionNodes(int argIndex) { for (Iterator i = arg[argIndex].getEdgeIterator(); i.hasNext();) { Edge e = (Edge) i.next(); int eLoc = e.getLabel().getLocation(argIndex); for (Iterator eiIt = e.getEdgeIntersectionList().iterator(); eiIt .hasNext();) { EdgeIntersection ei = (EdgeIntersection) eiIt.next(); RelateNode n = (RelateNode) nodes.find(ei.coord); if (n.getLabel().isNull(argIndex)) { if (eLoc == Location.BOUNDARY) n.setLabelBoundary(argIndex); else n.setLabel(argIndex, Location.INTERIOR); } // n.print(System.out); } } } /** * If the Geometries are disjoint, we need to enter their dimension and * boundary dimension in the EXTERIOR rows in the IM */ private void computeDisjointIM(IntersectionMatrix im) { GeometryImpl ga = arg[0].getGeometry(); // if (!ga.isEmpty()) { // im.set(Location.INTERIOR, Location.EXTERIOR, ga.getDimension()); // im.set(Location.BOUNDARY, Location.EXTERIOR, ga // .getBoundaryDimension()); // } im.set(Location.INTERIOR, Location.EXTERIOR, ga.getDimension(null)); if (ga.getBoundary() == null) { im.set(Location.BOUNDARY, Location.EXTERIOR, Dimension.FALSE); } else { im.set(Location.BOUNDARY, Location.EXTERIOR, ga.getBoundary().getDimension(null)); } GeometryImpl gb = arg[1].getGeometry(); // if (!gb.isEmpty()) { // im.set(Location.EXTERIOR, Location.INTERIOR, gb.getDimension()); // im.set(Location.EXTERIOR, Location.BOUNDARY, gb // .getBoundaryDimension()); // } im.set(Location.EXTERIOR, Location.INTERIOR, gb.getDimension(null)); if (gb.getBoundary() == null) { im.set(Location.EXTERIOR, Location.BOUNDARY, Dimension.FALSE); } else { im.set(Location.EXTERIOR, Location.BOUNDARY, gb.getBoundary().getDimension(null)); } } private void labelNodeEdges() { for (Iterator ni = nodes.iterator(); ni.hasNext();) { RelateNode node = (RelateNode) ni.next(); node.getEdges().computeLabelling(arg); } } /** * Update the IM with the sum of the IMs for each component * * @param IntersectionMatrix */ private void updateIM(IntersectionMatrix im) { for (Iterator ei = isolatedEdges.iterator(); ei.hasNext();) { Edge e = (Edge) ei.next(); e.updateIM(im); } for (Iterator ni = nodes.iterator(); ni.hasNext();) { RelateNode node = (RelateNode) ni.next(); //System.out.println(node.getCoordinate()); node.updateIM(im); node.updateIMFromEdges(im); } } /** * 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) * * @param thisIndex * @param targetIndex */ private void labelIsolatedEdges(int thisIndex, int targetIndex) { for (Iterator ei = arg[thisIndex].getEdgeIterator(); ei.hasNext();) { Edge e = (Edge) ei.next(); if (e.isIsolated()) { this.labelIsolatedEdge(e, targetIndex, arg[targetIndex] .getGeometry()); isolatedEdges.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(Edge e, int targetIndex, GeometryImpl target) { // this won't work for GeometryCollections with both dim 2 and 1 geoms // if (target.getDimension() > 0) { if (target.getDimension(null) > 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 int loc = ptLocator.locate(e.getCoordinate(), target); e.getLabel().setAllLocations(targetIndex, loc); } else { e.getLabel().setAllLocations(targetIndex, Location.EXTERIOR); } } /** * 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 (Iterator ni = nodes.iterator(); ni.hasNext();) { Node n = (Node) ni.next(); 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); } } } /** * Label an isolated node with its relationship to the target geometry. */ private void labelIsolatedNode(Node n, int targetIndex) { int loc = ptLocator.locate(n.getCoordinate(), arg[targetIndex] .getGeometry()); n.getLabel().setAllLocations(targetIndex, loc); } }