/*
* 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.topograph2D.Edge;
import org.geotools.geometry.iso.topograph2D.EdgeEnd;
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.Position;
/**
* A collection of EdgeStubs which obey the following invariant: They originate
* at the same node and have the same direction.
* Contains all {@link EdgeEnd}s which start at the same point and are
* parallel.
*
* @source $URL$
*/
public class EdgeEndBundle extends EdgeEnd {
private List edgeEnds = new ArrayList();
public EdgeEndBundle(EdgeEnd e) {
super(e.getEdge(), e.getCoordinate(), e.getDirectedCoordinate(),
new Label(e.getLabel()));
insert(e);
}
/**
*
*/
public Label getLabel() {
return label;
}
/**
*
* @return
*/
public Iterator iterator() {
return edgeEnds.iterator();
}
/**
*
* @return
*/
public List getEdgeEnds() {
return edgeEnds;
}
/**
*
* @param e
*/
public void insert(EdgeEnd e) {
// Assert: start point is the same
// Assert: direction is the same
edgeEnds.add(e);
}
/**
* This computes the overall edge label for the set of edges in this
* EdgeStubBundle. It essentially merges the ON and side labels for each
* edge. These labels must be compatible
*/
public void computeLabel() {
// create the label. If any of the edges belong to areas,
// the label must be an area label
boolean isArea = false;
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
if (e.getLabel().isArea())
isArea = true;
}
if (isArea)
label = new Label(Location.NONE, Location.NONE, Location.NONE);
else
label = new Label(Location.NONE);
// compute the On label, and the side labels if present
for (int i = 0; i < 2; i++) {
computeLabelOn(i);
if (isArea)
computeLabelSides(i);
}
}
/**
* Compute the overall ON location for the list of EdgeStubs. (This is
* essentially equivalent to computing the self-overlay of a single
* Geometry) edgeStubs can be either on the boundary (eg Polygon edge) OR in
* the interior (e.g. segment of a LineString) of their parent Geometry. In
* addition, GeometryCollections use the mod-2 rule to determine whether a
* segment is on the boundary or not. Finally, in GeometryCollections it can
* still occur that an edge is both on the boundary and in the interior
* (e.g. a LineString segment lying on top of a Polygon edge.) In this case
* as usual the Boundary is given precendence. <br>
* These observations result in the following rules for computing the ON
* location:
* <ul>
* <li> if there are an odd number of Bdy edges, the attribute is Bdy
* <li> if there are an even number >= 2 of Bdy edges, the attribute is Int
* <li> if there are any Int edges, the attribute is Int
* <li> otherwise, the attribute is NULL.
* </ul>
*
* @param geomIndex
*/
private void computeLabelOn(int geomIndex) {
// compute the ON location value
int boundaryCount = 0;
boolean foundInterior = false;
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
int loc = e.getLabel().getLocation(geomIndex);
if (loc == Location.BOUNDARY)
boundaryCount++;
if (loc == Location.INTERIOR)
foundInterior = true;
}
int loc = Location.NONE;
if (foundInterior)
loc = Location.INTERIOR;
if (boundaryCount > 0) {
loc = GeometryGraph.determineBoundary(boundaryCount);
}
label.setLocation(geomIndex, loc);
}
/**
* Compute the labelling for each side
*/
private void computeLabelSides(int geomIndex) {
computeLabelSide(geomIndex, Position.LEFT);
computeLabelSide(geomIndex, Position.RIGHT);
}
/**
* To compute the summary label for a side, the algorithm is: FOR all edges
* IF any edge's location is INTERIOR for the side, side location = INTERIOR
* ELSE IF there is at least one EXTERIOR attribute, side location =
* EXTERIOR ELSE side location = NULL <br>
* Note that it is possible for two sides to have apparently contradictory
* information i.e. one edge side may indicate that it is in the interior of
* a geometry, while another edge side may indicate the exterior of the same
* geometry. This is not an incompatibility - GeometryCollections may
* contain two Polygons that touch along an edge. This is the reason for
* Interior-primacy rule above - it results in the summary label having the
* Geometry interior on <b>both</b> sides.
*
* @param geomIndex
* @param side
*/
private void computeLabelSide(int geomIndex, int side) {
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
if (e.getLabel().isArea()) {
int loc = e.getLabel().getLocation(geomIndex, side);
if (loc == Location.INTERIOR) {
label.setLocation(geomIndex, side, Location.INTERIOR);
return;
} else if (loc == Location.EXTERIOR)
label.setLocation(geomIndex, side, Location.EXTERIOR);
}
}
}
/**
* Update the IM with the contribution for the computed label for the
* EdgeStubs.
*
* @param im
*/
void updateIM(IntersectionMatrix im) {
Edge.updateIM(label, im);
}
// public void print(PrintStream out) {
// out.println("EdgeEndBundle--> Label: " + label);
// for (Iterator it = iterator(); it.hasNext();) {
// EdgeEnd ee = (EdgeEnd) it.next();
// ee.print(out);
// out.println();
// }
// }
}