/* * 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.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.revolsys.geometry.algorithm.BoundaryNodeRule; import com.revolsys.geometry.algorithm.locate.SimplePointInAreaLocator; import com.revolsys.geometry.model.Location; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.TopologyException; import com.revolsys.geometry.util.Assert; /** * 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. * * @version 1.7 */ abstract public class EdgeEndStar<E extends EdgeEnd> implements Iterable<E> { /** * A list of all outgoing edges in the result, in CCW order */ protected List<E> edgeList; /** * A map which maintains the edges in sorted order around the node */ protected Map<EdgeEnd, E> edgeMap = new TreeMap<>(); /** * The location of the point for this star in Geometry i Areas */ private final Location[] ptInAreaLocation = { Location.NONE, Location.NONE }; public EdgeEndStar() { } private boolean checkAreaLabelsConsistent(final 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 final List<E> edges = getEdges(); // if no edges, trivially consistent if (edges.size() <= 0) { return true; } // initialize startLoc to location of last L side (if any) final int lastEdgeIndex = edges.size() - 1; final Label startLabel = ((EdgeEnd)edges.get(lastEdgeIndex)).getLabel(); final Location startLoc = startLabel.getLocation(geomIndex, Position.LEFT); Assert.isTrue(startLoc != Location.NONE, "Found unlabelled area edge"); Location currLoc = startLoc; for (final EdgeEnd e : this) { final Label label = e.getLabel(); // we assume that we are only checking a area Assert.isTrue(label.isArea(geomIndex), "Found non-area edge"); final Location leftLoc = label.getLocation(geomIndex, Position.LEFT); final Location 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; } private void computeEdgeEndLabels(final BoundaryNodeRule boundaryNodeRule) { // Compute edge label for each EdgeEnd for (final EdgeEnd ee : this) { ee.computeLabel(boundaryNodeRule); } } public void computeLabelling(final GeometryGraph[] geomGraph) { computeEdgeEndLabels(geomGraph[0].getBoundaryNodeRule()); // 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. */ final boolean[] hasDimensionalCollapseEdge = { false, false }; for (final EdgeEnd e : this) { final 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 (final EdgeEnd e : this) { final Label label = e.getLabel(); // Debug.println(e); for (int geomi = 0; geomi < 2; geomi++) { if (label.isAnyNull(geomi)) { Location loc = Location.NONE; if (hasDimensionalCollapseEdge[geomi]) { loc = Location.EXTERIOR; } else { final Point p = e.getCoordinate(); loc = getLocation(geomi, p, geomGraph); } label.setAllLocationsIfNull(geomi, loc); } } // Debug.println(e); } // Debug.print(this); // Debug.printIfWatch(this); } public int findIndex(final EdgeEnd eSearch) { iterator(); // force edgelist to be computed for (int i = 0; i < this.edgeList.size(); i++) { final EdgeEnd e = this.edgeList.get(i); if (e == eSearch) { return i; } } return -1; } /** * @return the coordinate for the node this star is based at */ public Point getCoordinate() { final Iterator<E> it = iterator(); if (!it.hasNext()) { return null; } final EdgeEnd e = it.next(); return e.getCoordinate(); } public int getDegree() { return this.edgeMap.size(); } public List<E> getEdges() { if (this.edgeList == null) { this.edgeList = new ArrayList<>(this.edgeMap.values()); } return this.edgeList; } private Location getLocation(final int geomIndex, final Point p, final GeometryGraph[] geom) { // compute location only on demand if (this.ptInAreaLocation[geomIndex] == Location.NONE) { this.ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.locate(p, geom[geomIndex].getGeometry()); } return this.ptInAreaLocation[geomIndex]; } public EdgeEnd getNextCW(final EdgeEnd ee) { getEdges(); final int i = this.edgeList.indexOf(ee); int iNextCW = i - 1; if (i == 0) { iNextCW = this.edgeList.size() - 1; } return this.edgeList.get(iNextCW); } /** * 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(final EdgeEnd e, final E obj) { this.edgeMap.put(e, obj); this.edgeList = null; // edge list has changed - clear the cache } public boolean isAreaLabelsConsistent(final GeometryGraph geomGraph) { computeEdgeEndLabels(geomGraph.getBoundaryNodeRule()); return checkAreaLabelsConsistent(0); } /** * 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). */ @Override public Iterator<E> iterator() { return getEdges().iterator(); } public void print(final PrintStream out) { System.out.println("EdgeEndStar: " + getCoordinate()); for (final EdgeEnd e : this) { e.print(out); } } void propagateSideLabels(final 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 Location startLoc = Location.NONE; // initialize loc to location of last L side (if any) // System.out.println("finding start location"); for (final EdgeEnd e : this) { final 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; } Location currLoc = startLoc; for (final EdgeEnd e : this) { final 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(geomIndex)) { final Location leftLoc = label.getLocation(geomIndex, Position.LEFT); final Location 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); } } } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("EdgeEndStar: " + getCoordinate()); buf.append("\n"); for (final EdgeEnd e : this) { buf.append(e); buf.append("\n"); } return buf.toString(); } }