/* * 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 org.geotools.geometry.iso.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. * * * * @source $URL$ */ public class DirectedEdgeStar extends EdgeEndStar { /** * A list of all outgoing edges in the result, in CCW order */ private List resultAreaEdgeList; private Label label; public DirectedEdgeStar() { } /** * Insert a directed edge in the list */ public void insert(EdgeEnd ee) { DirectedEdge de = (DirectedEdge) ee; insertEdgeEnd(de, de); } public Label getLabel() { return label; } public int getOutgoingDegree() { int degree = 0; for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); if (de.isInResult()) degree++; } return degree; } public int getOutgoingDegree(EdgeRing er) { int degree = 0; for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); if (de.getEdgeRing() == er) degree++; } return degree; } public DirectedEdge getRightmostEdge() { List edges = getEdges(); int size = edges.size(); if (size < 1) return null; DirectedEdge de0 = (DirectedEdge) edges.get(0); if (size == 1) return de0; DirectedEdge deLast = (DirectedEdge) edges.get(size - 1); int quad0 = de0.getQuadrant(); 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 // Assert.isTrue(de0.getDy() != 0, "should never return horizontal // edge!"); DirectedEdge nonHorizontalEdge = null; if (de0.getDy() != 0) return de0; else if (deLast.getDy() != 0) return deLast; } Assert .shouldNeverReachHere("found two horizontal edges incident on node"); return null; } /** * Compute the labelling for all dirEdges in this star, as well as the * overall labelling */ public void computeLabelling(GeometryGraph[] geom) { // Debug.print(this); super.computeLabelling(geom); // determine the overall labelling for this DirectedEdgeStar // (i.e. for the node it is based at) label = new Label(Location.NONE); for (Iterator it = iterator(); it.hasNext();) { EdgeEnd ee = (EdgeEnd) it.next(); Edge e = ee.getEdge(); Label eLabel = e.getLabel(); for (int i = 0; i < 2; i++) { int eLoc = eLabel.getLocation(i); if (eLoc == Location.INTERIOR || eLoc == Location.BOUNDARY) label.setLocation(i, Location.INTERIOR); } } // Debug.print(this); } /** * For each dirEdge in the star, merge the label from the sym dirEdge into * the label */ public void mergeSymLabels() { for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); Label label = de.getLabel(); label.merge(de.getSym().getLabel()); } } /** * Update incomplete dirEdge labels from the labelling for the node */ public void updateLabelling(Label nodeLabel) { for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); Label label = de.getLabel(); label.setAllLocationsIfNull(0, nodeLabel.getLocation(0)); label.setAllLocationsIfNull(1, nodeLabel.getLocation(1)); } } private List getResultAreaEdges() { // print(System.out); if (resultAreaEdgeList != null) return resultAreaEdgeList; resultAreaEdgeList = new ArrayList(); for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); if (de.isInResult() || de.getSym().isInResult()) resultAreaEdgeList.add(de); } return resultAreaEdgeList; } private final int SCANNING_FOR_INCOMING = 1; private final int LINKING_TO_OUTGOING = 2; /** * 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 = SCANNING_FOR_INCOMING; // link edges in CCW order for (int i = 0; i < resultAreaEdgeList.size(); i++) { DirectedEdge nextOut = (DirectedEdge) resultAreaEdgeList.get(i); 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 = LINKING_TO_OUTGOING; break; case LINKING_TO_OUTGOING: if (!nextOut.isInResult()) continue; incoming.setNext(nextOut); state = SCANNING_FOR_INCOMING; break; } } // Debug.print(this); if (state == 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); } } public void linkMinimalDirectedEdges(EdgeRing er) { // find first area edge (if any) to start linking at DirectedEdge firstOut = null; DirectedEdge incoming = null; int state = SCANNING_FOR_INCOMING; // link edges in CW order for (int i = resultAreaEdgeList.size() - 1; i >= 0; i--) { DirectedEdge nextOut = (DirectedEdge) resultAreaEdgeList.get(i); 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 = LINKING_TO_OUTGOING; break; case LINKING_TO_OUTGOING: if (nextOut.getEdgeRing() != er) continue; incoming.setNextMin(nextOut); state = SCANNING_FOR_INCOMING; break; } } // print(System.out); if (state == 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); } } 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 = edgeList.size() - 1; i >= 0; i--) { DirectedEdge nextOut = (DirectedEdge) edgeList.get(i); 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); } /** * 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 */ int startLoc = Location.NONE; for (Iterator it = iterator(); it.hasNext();) { DirectedEdge nextOut = (DirectedEdge) it.next(); 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 */ int currLoc = startLoc; for (Iterator it = iterator(); it.hasNext();) { DirectedEdge nextOut = (DirectedEdge) it.next(); 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 void computeDepths(DirectedEdge de) { int edgeIndex = findIndex(de); Label label = de.getLabel(); int startDepth = de.getDepth(Position.LEFT); int targetLastDepth = de.getDepth(Position.RIGHT); // compute the depths from this edge up to the end of the edge array int nextDepth = computeDepths(edgeIndex + 1, edgeList.size(), startDepth); // compute the depths for the initial part of the array int lastDepth = computeDepths(0, edgeIndex, nextDepth); // Debug.print(lastDepth != targetLastDepth, this); // Debug.print(lastDepth != targetLastDepth, "mismatch: " + lastDepth + // " / " + targetLastDepth); if (lastDepth != targetLastDepth) throw new TopologyException("depth mismatch at " + de.getCoordinate()); // Assert.isTrue(lastDepth == targetLastDepth, "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(int startIndex, int endIndex, int startDepth) { int currDepth = startDepth; for (int i = startIndex; i < endIndex; i++) { DirectedEdge nextDe = (DirectedEdge) edgeList.get(i); Label label = nextDe.getLabel(); nextDe.setEdgeDepths(Position.RIGHT, currDepth); currDepth = nextDe.getDepth(Position.LEFT); } return currDepth; } public void print(PrintStream out) { System.out.println("DirectedEdgeStar: " + getCoordinate()); for (Iterator it = iterator(); it.hasNext();) { DirectedEdge de = (DirectedEdge) it.next(); out.print("out "); de.print(out); out.println(); out.print("in "); de.getSym().print(out); out.println(); } } }