/*
* 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.topograph2D;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.geotools.geometry.iso.util.Assert;
import org.geotools.geometry.iso.util.algorithm2D.SimplePointInAreaLocator;
/**
* A EdgeEndStar is an ordered list of EdgeEnds around a node. They are
* maintained in CCW order (starting with the positive x-axis) around the node
* for efficient lookup and topology building.
*
* @source $URL$
*/
abstract public class EdgeEndStar {
/**
* A map which maintains the edges in sorted order around the node
*/
protected Map edgeMap = new TreeMap();
/**
* A list of all outgoing edges in the result, in CCW order
*/
protected List edgeList;
/**
* The location of the point for this star in Geometry i Areas
*/
private int[] ptInAreaLocation = { Location.NONE, Location.NONE };
public EdgeEndStar() {
}
/**
* Insert a EdgeEnd into this EdgeEndStar
*/
abstract public void insert(EdgeEnd e);
/**
* Insert an EdgeEnd into the map, and clear the edgeList cache, since the
* list of edges has now changed
*/
protected void insertEdgeEnd(EdgeEnd e, Object obj) {
edgeMap.put(e, obj);
edgeList = null; // edge list has changed - clear the cache
}
/**
* @return the coordinate for the node this star is based at
*/
public Coordinate getCoordinate() {
Iterator it = iterator();
if (!it.hasNext())
return null;
EdgeEnd e = (EdgeEnd) it.next();
return e.getCoordinate();
}
public int getDegree() {
return edgeMap.size();
}
/**
* Iterator access to the ordered list of edges is optimized by copying the
* map collection to a list. (This assumes that once an iterator is
* requested, it is likely that insertion into the map is complete).
*/
public Iterator iterator() {
return getEdges().iterator();
}
public List getEdges() {
if (edgeList == null) {
edgeList = new ArrayList(edgeMap.values());
}
return edgeList;
}
public EdgeEnd getNextCW(EdgeEnd ee) {
getEdges();
int i = edgeList.indexOf(ee);
int iNextCW = i - 1;
if (i == 0)
iNextCW = edgeList.size() - 1;
return (EdgeEnd) edgeList.get(iNextCW);
}
/**
* Computes the labelling for the ???
*
* @param geom
*/
public void computeLabelling(GeometryGraph[] geom) {
computeEdgeEndLabels();
// Propagate side labels around the edges in the star
// for each parent Geometry
// Debug.print(this);
propagateSideLabels(0);
// Debug.print(this);
// Debug.printIfWatch(this);
propagateSideLabels(1);
// Debug.print(this);
// Debug.printIfWatch(this);
/**
* If there are edges that still have null labels for a geometry this
* must be because there are no area edges for that geometry incident on
* this node. In this case, to label the edge for that geometry we must
* test whether the edge is in the interior of the geometry. To do this
* it suffices to determine whether the node for the edge is in the
* interior of an area. If so, the edge has location INTERIOR for the
* geometry. In all other cases (e.g. the node is on a line, on a point,
* or not on the geometry at all) the edge has the location EXTERIOR for
* the geometry.
* <p>
* Note that the edge cannot be on the BOUNDARY of the geometry, since
* then there would have been a parallel edge from the Geometry at this
* node also labelled BOUNDARY and this edge would have been labelled in
* the previous step.
* <p>
* This code causes a problem when dimensional collapses are present,
* since it may try and determine the location of a node where a
* dimensional collapse has occurred. The point should be considered to
* be on the EXTERIOR of the polygon, but locate() will return INTERIOR,
* since it is passed the original Geometry, not the collapsed version.
*
* If there are incident edges which are Line edges labelled BOUNDARY,
* then they must be edges resulting from dimensional collapses. In this
* case the other edges can be labelled EXTERIOR for this Geometry.
*
* MD 8/11/01 - NOT TRUE! The collapsed edges may in fact be in the
* interior of the Geometry, which means the other edges should be
* labelled INTERIOR for this Geometry. Not sure how solve this...
* Possibly labelling needs to be split into several phases: area label
* propagation, symLabel merging, then finally null label resolution.
*/
boolean[] hasDimensionalCollapseEdge = { false, false };
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
Label label = e.getLabel();
for (int geomi = 0; geomi < 2; geomi++) {
if (label.isLine(geomi)
&& label.getLocation(geomi) == Location.BOUNDARY)
hasDimensionalCollapseEdge[geomi] = true;
}
}
// Debug.print(this);
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
Label label = e.getLabel();
// Debug.println(e);
for (int geomi = 0; geomi < 2; geomi++) {
if (label.isAnyNull(geomi)) {
int loc = Location.NONE;
if (hasDimensionalCollapseEdge[geomi]) {
loc = Location.EXTERIOR;
} else {
Coordinate p = e.getCoordinate();
loc = getLocation(geomi, p, geom);
}
label.setAllLocationsIfNull(geomi, loc);
}
}
// Debug.println(e);
}
// Debug.print(this);
// Debug.printIfWatch(this);
}
private void computeEdgeEndLabels() {
// Compute edge label for each EdgeEnd
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd ee = (EdgeEnd) it.next();
ee.computeLabel();
}
}
int getLocation(int geomIndex, Coordinate p, GeometryGraph[] geom) {
// compute location only on demand
if (ptInAreaLocation[geomIndex] == Location.NONE) {
ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.locate(p,
geom[geomIndex].getGeometry());
}
return ptInAreaLocation[geomIndex];
}
public boolean isAreaLabelsConsistent() {
computeEdgeEndLabels();
return checkAreaLabelsConsistent(0);
}
private boolean checkAreaLabelsConsistent(int geomIndex) {
// Since edges are stored in CCW order around the node,
// As we move around the ring we move from the right to the left side of
// the edge
List edges = getEdges();
// if no edges, trivially consistent
if (edges.size() <= 0)
return true;
// initialize startLoc to location of last L side (if any)
int lastEdgeIndex = edges.size() - 1;
Label startLabel = ((EdgeEnd) edges.get(lastEdgeIndex)).getLabel();
int startLoc = startLabel.getLocation(geomIndex, Position.LEFT);
Assert.isTrue(startLoc != Location.NONE, "Found unlabelled area edge");
int currLoc = startLoc;
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
Label label = e.getLabel();
// we assume that we are only checking a area
Assert.isTrue(label.isArea(geomIndex), "Found non-area edge");
int leftLoc = label.getLocation(geomIndex, Position.LEFT);
int rightLoc = label.getLocation(geomIndex, Position.RIGHT);
// System.out.println(leftLoc + " " + rightLoc);
// Debug.print(this);
// check that edge is really a boundary between inside and outside!
if (leftLoc == rightLoc) {
return false;
}
// check side location conflict
// Assert.isTrue(rightLoc == currLoc, "side location conflict " +
// locStr);
if (rightLoc != currLoc) {
// Debug.print(this);
return false;
}
currLoc = leftLoc;
}
return true;
}
void propagateSideLabels(int geomIndex) {
// Since edges are stored in CCW order around the node,
// As we move around the ring we move from the right to the left side of
// the edge
int startLoc = Location.NONE;
// initialize loc to location of last L side (if any)
// System.out.println("finding start location");
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
Label label = e.getLabel();
if (label.isArea(geomIndex)
&& label.getLocation(geomIndex, Position.LEFT) != Location.NONE)
startLoc = label.getLocation(geomIndex, Position.LEFT);
}
// no labelled sides found, so no labels to propagate
if (startLoc == Location.NONE)
return;
int currLoc = startLoc;
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
Label label = e.getLabel();
// set null ON values to be in current location
if (label.getLocation(geomIndex, Position.ON) == Location.NONE)
label.setLocation(geomIndex, Position.ON, currLoc);
// set side labels (if any)
// if (label.isArea()) { //ORIGINAL
if (label.isArea(geomIndex)) {
int leftLoc = label.getLocation(geomIndex, Position.LEFT);
int rightLoc = label.getLocation(geomIndex, Position.RIGHT);
// if there is a right location, that is the next location to
// propagate
if (rightLoc != Location.NONE) {
// Debug.print(rightLoc != currLoc, this);
if (rightLoc != currLoc)
throw new TopologyException("side location conflict", e
.getCoordinate());
if (leftLoc == Location.NONE) {
Assert
.shouldNeverReachHere("found single null side (at "
+ e.getCoordinate() + ")");
}
currLoc = leftLoc;
} else {
/**
* RHS is null - LHS must be null too. This must be an edge
* from the other geometry, which has no location labelling
* for this geometry. This edge must lie wholly inside or
* outside the other geometry (which is determined by the
* current location). Assign both sides to be the current
* location.
*/
Assert
.isTrue(
label.getLocation(geomIndex, Position.LEFT) == Location.NONE,
"found single null side");
label.setLocation(geomIndex, Position.RIGHT, currLoc);
label.setLocation(geomIndex, Position.LEFT, currLoc);
}
}
}
}
public int findIndex(EdgeEnd eSearch) {
iterator(); // force edgelist to be computed
for (int i = 0; i < edgeList.size(); i++) {
EdgeEnd e = (EdgeEnd) edgeList.get(i);
if (e == eSearch)
return i;
}
return -1;
}
public void print(PrintStream out) {
System.out.println("EdgeEndStar: " + getCoordinate());
for (Iterator it = iterator(); it.hasNext();) {
EdgeEnd e = (EdgeEnd) it.next();
e.print(out);
}
}
}