/*
* 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;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.revolsys.geometry.algorithm.BoundaryNodeRule;
import com.revolsys.geometry.geomgraph.Edge;
import com.revolsys.geometry.geomgraph.EdgeEnd;
import com.revolsys.geometry.geomgraph.GeometryGraph;
import com.revolsys.geometry.geomgraph.Label;
import com.revolsys.geometry.geomgraph.Position;
import com.revolsys.geometry.model.IntersectionMatrix;
import com.revolsys.geometry.model.Location;
/**
* A collection of {@link EdgeEnd}s which obey the following invariant:
* They originate at the same node and have the same direction.
*
* @version 1.7
*/
public class EdgeEndBundle extends EdgeEnd implements Iterable<EdgeEnd> {
// private BoundaryNodeRule boundaryNodeRule;
private final List<EdgeEnd> edgeEnds = new ArrayList<>();
public EdgeEndBundle(final BoundaryNodeRule boundaryNodeRule, final EdgeEnd e) {
super(e.getEdge(), e.getCoordinate(), e.getDirectedCoordinate(), new Label(e.getLabel()));
insert(e);
/*
* if (boundaryNodeRule != null) this.boundaryNodeRule = boundaryNodeRule; else boundaryNodeRule
* = BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE;
*/
}
public EdgeEndBundle(final EdgeEnd e) {
this(null, 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
*/
@Override
public void computeLabel(final BoundaryNodeRule boundaryNodeRule) {
// create the label. If any of the edges belong to areas,
// the label must be an area label
boolean isArea = false;
for (final EdgeEnd edgeEnd : this) {
if (edgeEnd.getLabel().isArea()) {
isArea = true;
}
}
if (isArea) {
setLabel(new Label(Location.NONE, Location.NONE, Location.NONE));
} else {
setLabel(new Label(Location.NONE));
}
// compute the On label, and the side labels if present
for (int i = 0; i < 2; i++) {
computeLabelOn(i, boundaryNodeRule);
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 a {@link BoundaryNodeRule} to determine
* whether a segment is on the boundary or not.
* Finally, in GeometryCollections it can 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 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>
*/
private void computeLabelOn(final int geomIndex, final BoundaryNodeRule boundaryNodeRule) {
// compute the ON location value
int boundaryCount = 0;
boolean foundInterior = false;
for (final EdgeEnd e : this) {
final Location loc = e.getLabel().getLocation(geomIndex);
if (loc == Location.BOUNDARY) {
boundaryCount++;
}
if (loc == Location.INTERIOR) {
foundInterior = true;
}
}
Location loc = Location.NONE;
if (foundInterior) {
loc = Location.INTERIOR;
}
if (boundaryCount > 0) {
loc = GeometryGraph.determineBoundary(boundaryNodeRule, boundaryCount);
}
getLabel().setLocation(geomIndex, loc);
}
/**
* 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.
*/
private void computeLabelSide(final int geomIndex, final int side) {
for (final EdgeEnd e : this) {
if (e.getLabel().isArea()) {
final Location loc = e.getLabel().getLocation(geomIndex, side);
if (loc == Location.INTERIOR) {
getLabel().setLocation(geomIndex, side, Location.INTERIOR);
return;
} else if (loc == Location.EXTERIOR) {
getLabel().setLocation(geomIndex, side, Location.EXTERIOR);
}
}
}
}
/**
* Compute the labelling for each side
*/
private void computeLabelSides(final int geomIndex) {
computeLabelSide(geomIndex, Position.LEFT);
computeLabelSide(geomIndex, Position.RIGHT);
}
public List<EdgeEnd> getEdgeEnds() {
return this.edgeEnds;
}
public void insert(final EdgeEnd e) {
// Assert: start point is the same
// Assert: direction is the same
this.edgeEnds.add(e);
}
@Override
public Iterator<EdgeEnd> iterator() {
return this.edgeEnds.iterator();
}
@Override
public void print(final PrintStream out) {
out.println("EdgeEndBundle--> Label: " + getLabel());
for (final EdgeEnd e : this) {
e.print(out);
out.println();
}
}
/**
* Update the IM with the contribution for the computed label for the EdgeStubs.
*/
void updateIM(final IntersectionMatrix im) {
Edge.updateIM(getLabel(), im);
}
}