/*
* 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.geomgraph;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.model.TopologyException;
import com.revolsys.geometry.util.Assert;
/**
* A DirectedEdgeStar is an ordered list of <b>outgoing</b> DirectedEdges around a node.
* It supports labelling the edges as well as linking the edges to form both
* MaximalEdgeRings and MinimalEdgeRings.
*
* @version 1.7
*/
public class DirectedEdgeStar extends EdgeEndStar<DirectedEdge> {
private Label label;
private final int LINKING_TO_OUTGOING = 2;
/**
* A list of all outgoing edges in the result, in CCW order
*/
private List<DirectedEdge> resultAreaEdgeList;
private final int SCANNING_FOR_INCOMING = 1;
public DirectedEdgeStar() {
}
public void computeDepths(final DirectedEdge de) {
final int edgeIndex = findIndex(de);
final int startDepth = de.getDepth(Position.LEFT);
final int targetLastDepth = de.getDepth(Position.RIGHT);
// compute the depths from this edge up to the end of the edge array
final int nextDepth = computeDepths(edgeIndex + 1, this.edgeList.size(), startDepth);
// compute the depths for the initial part of the array
final int lastDepth = computeDepths(0, edgeIndex, nextDepth);
if (lastDepth != targetLastDepth) {
throw new TopologyException("depth mismatch at " + de.getCoordinate());
}
}
/**
* Compute the DirectedEdge depths for a subsequence of the edge array.
*
* @return the last depth assigned (from the R side of the last edge visited)
*/
private int computeDepths(final int startIndex, final int endIndex, final int startDepth) {
int currDepth = startDepth;
for (int i = startIndex; i < endIndex; i++) {
final DirectedEdge nextDe = this.edgeList.get(i);
nextDe.setEdgeDepths(Position.RIGHT, currDepth);
currDepth = nextDe.getDepth(Position.LEFT);
}
return currDepth;
}
/**
* Compute the labelling for all dirEdges in this star, as well
* as the overall labelling
*/
@Override
public void computeLabelling(final GeometryGraph[] geom) {
// Debug.print(this);
super.computeLabelling(geom);
// determine the overall labelling for this DirectedEdgeStar
// (i.e. for the node it is based at)
this.label = new Label(Location.NONE);
for (final DirectedEdge de : this) {
final Edge e = de.getEdge();
final Label eLabel = e.getLabel();
for (int i = 0; i < 2; i++) {
final Location eLoc = eLabel.getLocation(i);
if (eLoc == Location.INTERIOR || eLoc == Location.BOUNDARY) {
this.label.setLocation(i, Location.INTERIOR);
}
}
}
// Debug.print(this);
}
/**
* Traverse the star of edges, maintaing the current location in the result
* area at this node (if any).
* If any L edges are found in the interior of the result, mark them as covered.
*/
public void findCoveredLineEdges() {
// Debug.print("findCoveredLineEdges");
// Debug.print(this);
// 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
/**
* Find first DirectedEdge of result area (if any).
* The interior of the result is on the RHS of the edge,
* so the start location will be:
* - INTERIOR if the edge is outgoing
* - EXTERIOR if the edge is incoming
*/
Location startLoc = Location.NONE;
for (final DirectedEdge nextOut : this) {
final DirectedEdge nextIn = nextOut.getSym();
if (!nextOut.isLineEdge()) {
if (nextOut.isInResult()) {
startLoc = Location.INTERIOR;
break;
}
if (nextIn.isInResult()) {
startLoc = Location.EXTERIOR;
break;
}
}
}
// no A edges found, so can't determine if L edges are covered or not
if (startLoc == Location.NONE) {
return;
}
/**
* move around ring, keeping track of the current location
* (Interior or Exterior) for the result area.
* If L edges are found, mark them as covered if they are in the interior
*/
Location currLoc = startLoc;
for (final DirectedEdge nextOut : this) {
final DirectedEdge nextIn = nextOut.getSym();
if (nextOut.isLineEdge()) {
nextOut.getEdge().setCovered(currLoc == Location.INTERIOR);
// Debug.println(nextOut);
} else { // edge is an Area edge
if (nextOut.isInResult()) {
currLoc = Location.EXTERIOR;
}
if (nextIn.isInResult()) {
currLoc = Location.INTERIOR;
}
}
}
}
public Label getLabel() {
return this.label;
}
public int getOutgoingDegree() {
int degree = 0;
for (final DirectedEdge de : this) {
if (de.isInResult()) {
degree++;
}
}
return degree;
}
public int getOutgoingDegree(final EdgeRing er) {
int degree = 0;
for (final DirectedEdge de : this) {
if (de.getEdgeRing() == er) {
degree++;
}
}
return degree;
}
private List<DirectedEdge> getResultAreaEdges() {
// print(System.out);
if (this.resultAreaEdgeList != null) {
return this.resultAreaEdgeList;
}
this.resultAreaEdgeList = new ArrayList<>();
for (final DirectedEdge de : this) {
if (de.isInResult() || de.getSym().isInResult()) {
this.resultAreaEdgeList.add(de);
}
}
return this.resultAreaEdgeList;
}
public DirectedEdge getRightmostEdge() {
final List<DirectedEdge> edges = getEdges();
final int size = edges.size();
if (size < 1) {
return null;
}
final DirectedEdge de0 = edges.get(0);
if (size == 1) {
return de0;
}
final DirectedEdge deLast = edges.get(size - 1);
final int quad0 = de0.getQuadrant();
final int quad1 = deLast.getQuadrant();
if (Quadrant.isNorthern(quad0) && Quadrant.isNorthern(quad1)) {
return de0;
} else if (!Quadrant.isNorthern(quad0) && !Quadrant.isNorthern(quad1)) {
return deLast;
} else {
// edges are in different hemispheres - make sure we return one that is
// non-horizontal
if (de0.getDy() != 0) {
return de0;
} else if (deLast.getDy() != 0) {
return deLast;
}
}
Assert.shouldNeverReachHere("found two horizontal edges incident on node");
return null;
}
/**
* Insert a directed edge in the list
*/
@Override
public void insert(final EdgeEnd edgeEnd) {
final DirectedEdge de = (DirectedEdge)edgeEnd;
insertEdgeEnd(de, de);
}
public void linkAllDirectedEdges() {
getEdges();
// find first area edge (if any) to start linking at
DirectedEdge prevOut = null;
DirectedEdge firstIn = null;
// link edges in CW order
for (int i = this.edgeList.size() - 1; i >= 0; i--) {
final DirectedEdge nextOut = this.edgeList.get(i);
final DirectedEdge nextIn = nextOut.getSym();
if (firstIn == null) {
firstIn = nextIn;
}
if (prevOut != null) {
nextIn.setNext(prevOut);
}
// record outgoing edge, in order to link the last incoming edge
prevOut = nextOut;
}
firstIn.setNext(prevOut);
// Debug.print(this);
}
public void linkMinimalDirectedEdges(final EdgeRing er) {
// find first area edge (if any) to start linking at
DirectedEdge firstOut = null;
DirectedEdge incoming = null;
int state = this.SCANNING_FOR_INCOMING;
// link edges in CW order
for (int i = this.resultAreaEdgeList.size() - 1; i >= 0; i--) {
final DirectedEdge nextOut = this.resultAreaEdgeList.get(i);
final DirectedEdge nextIn = nextOut.getSym();
// record first outgoing edge, in order to link the last incoming edge
if (firstOut == null && nextOut.getEdgeRing() == er) {
firstOut = nextOut;
}
switch (state) {
case SCANNING_FOR_INCOMING:
if (nextIn.getEdgeRing() != er) {
continue;
}
incoming = nextIn;
state = this.LINKING_TO_OUTGOING;
break;
case LINKING_TO_OUTGOING:
if (nextOut.getEdgeRing() != er) {
continue;
}
incoming.setNextMin(nextOut);
state = this.SCANNING_FOR_INCOMING;
break;
}
}
// print(System.out);
if (state == this.LINKING_TO_OUTGOING) {
Assert.isTrue(firstOut != null, "found null for first outgoing dirEdge");
Assert.isTrue(firstOut.getEdgeRing() == er, "unable to link last incoming dirEdge");
incoming.setNextMin(firstOut);
}
}
/**
* Traverse the star of DirectedEdges, linking the included edges together.
* To link two dirEdges, the <next> pointer for an incoming dirEdge
* is set to the next outgoing edge.
* <p>
* DirEdges are only linked if:
* <ul>
* <li>they belong to an area (i.e. they have sides)
* <li>they are marked as being in the result
* </ul>
* <p>
* Edges are linked in CCW order (the order they are stored).
* This means that rings have their face on the Right
* (in other words,
* the topological location of the face is given by the RHS label of the DirectedEdge)
* <p>
* PRECONDITION: No pair of dirEdges are both marked as being in the result
*/
public void linkResultDirectedEdges() {
// make sure edges are copied to resultAreaEdges list
getResultAreaEdges();
// find first area edge (if any) to start linking at
DirectedEdge firstOut = null;
DirectedEdge incoming = null;
int state = this.SCANNING_FOR_INCOMING;
// link edges in CCW order
for (int i = 0; i < this.resultAreaEdgeList.size(); i++) {
final DirectedEdge nextOut = this.resultAreaEdgeList.get(i);
final DirectedEdge nextIn = nextOut.getSym();
// skip de's that we're not interested in
if (!nextOut.getLabel().isArea()) {
continue;
}
// record first outgoing edge, in order to link the last incoming edge
if (firstOut == null && nextOut.isInResult()) {
firstOut = nextOut;
// assert: sym.isInResult() == false, since pairs of dirEdges should
// have been removed already
}
switch (state) {
case SCANNING_FOR_INCOMING:
if (!nextIn.isInResult()) {
continue;
}
incoming = nextIn;
state = this.LINKING_TO_OUTGOING;
break;
case LINKING_TO_OUTGOING:
if (!nextOut.isInResult()) {
continue;
}
incoming.setNext(nextOut);
state = this.SCANNING_FOR_INCOMING;
break;
}
}
// Debug.print(this);
if (state == this.LINKING_TO_OUTGOING) {
// Debug.print(firstOut == null, this);
if (firstOut == null) {
throw new TopologyException("no outgoing dirEdge found", getCoordinate());
}
// Assert.isTrue(firstOut != null, "no outgoing dirEdge found (at " +
// getCoordinate() );
Assert.isTrue(firstOut.isInResult(), "unable to link last incoming dirEdge");
incoming.setNext(firstOut);
}
}
/**
* For each dirEdge in the star,
* merge the label from the sym dirEdge into the label
*/
public void mergeSymLabels() {
for (final DirectedEdge de : this) {
final Label label = de.getLabel();
label.merge(de.getSym().getLabel());
}
}
@Override
public void print(final PrintStream out) {
System.out.println("DirectedEdgeStar: " + getCoordinate());
for (final DirectedEdge de : this) {
out.print("out ");
de.print(out);
out.println();
out.print("in ");
de.getSym().print(out);
out.println();
}
}
/**
* Update incomplete dirEdge labels from the labelling for the node
*/
public void updateLabelling(final Label nodeLabel) {
for (final DirectedEdge de : this) {
final Label label = de.getLabel();
label.setAllLocationsIfNull(0, nodeLabel.getLocation(0));
label.setAllLocationsIfNull(1, nodeLabel.getLocation(1));
}
}
}