using System.Collections; using System.IO; using Core.GIS.GeoAPI.Geometries; using Core.GIS.NetTopologySuite.Algorithm; using Core.GIS.NetTopologySuite.Geometries; using Core.GIS.NetTopologySuite.Utilities; using Wintellect.PowerCollections; namespace Core.GIS.NetTopologySuite.GeometriesGraph { /// /// 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. /// public abstract class EdgeEndStar { /// /// A map which maintains the edges in sorted order around the node. /// protected IDictionary edgeMap = new OrderedDictionary(); /// /// A list of all outgoing edges in the result, in CCW order. /// protected IList edgeList; /// /// The location of the point for this star in Geometry i Areas. /// private readonly Locations[] ptInAreaLocation = new Locations[] { Locations.Null, Locations.Null }; /// /// The coordinate for the node this star is based at. /// public ICoordinate Coordinate { get { IEnumerator it = GetEnumerator(); if (!it.MoveNext()) { return null; } EdgeEnd e = (EdgeEnd) it.Current; return e.Coordinate; } } /// /// /// public int Degree { get { return edgeMap.Count; } } /// /// /// public IList Edges { get { if (edgeList == null) { edgeList = new ArrayList(edgeMap.Values); } return edgeList; } } /// /// /// public bool IsAreaLabelsConsistent { get { ComputeEdgeEndLabels(); return CheckAreaLabelsConsistent(0); } } /// /// Insert a EdgeEnd into this EdgeEndStar. /// /// public abstract void Insert(EdgeEnd e); /// /// 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 IEnumerator GetEnumerator() { return Edges.GetEnumerator(); } /// /// /// /// /// public EdgeEnd GetNextCW(EdgeEnd ee) { IList temp = Edges; temp = null; // Hack for calling property int i = edgeList.IndexOf(ee); int iNextCW = i - 1; if (i == 0) { iNextCW = edgeList.Count - 1; } return (EdgeEnd) edgeList[iNextCW]; } /// /// /// /// public virtual void ComputeLabelling(GeometryGraph[] geom) { ComputeEdgeEndLabels(); // Propagate side labels around the edges in the star // for each parent Geometry PropagateSideLabels(0); PropagateSideLabels(1); /* * If there are edges that still have null labels for a point * this must be because there are no area edges for that point incident on this node. * In this case, to label the edge for that point we must test whether the * edge is in the interior of the point. * 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 point. * In all other cases (e.g. the node is on a line, on a point, or not on the point at all) the edge * has the location Exterior for the point. * * Note that the edge cannot be on the Boundary of the point, 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. * * 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. */ bool[] hasDimensionalCollapseEdge = { false, false }; for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) { if (label.IsLine(geomi) && label.GetLocation(geomi) == Locations.Boundary) { hasDimensionalCollapseEdge[geomi] = true; } } } for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) { if (label.IsAnyNull(geomi)) { Locations loc; if (hasDimensionalCollapseEdge[geomi]) { loc = Locations.Exterior; } else { ICoordinate p = e.Coordinate; loc = GetLocation(geomi, p, geom); } label.SetAllLocationsIfNull(geomi, loc); } } } } /// /// /// /// /// /// /// public Locations GetLocation(int geomIndex, ICoordinate p, GeometryGraph[] geom) { // compute location only on demand if (ptInAreaLocation[geomIndex] == Locations.Null) { ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.Locate(p, geom[geomIndex].Geometry); } return ptInAreaLocation[geomIndex]; } /// /// /// /// public 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 Locations startLoc = Locations.Null; // initialize loc to location of last Curve side (if any) for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; if (label.IsArea(geomIndex) && label.GetLocation(geomIndex, Positions.Left) != Locations.Null) { startLoc = label.GetLocation(geomIndex, Positions.Left); } } // no labelled sides found, so no labels to propagate if (startLoc == Locations.Null) { return; } Locations currLoc = startLoc; for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; // set null On values to be in current location if (label.GetLocation(geomIndex, Positions.On) == Locations.Null) { label.SetLocation(geomIndex, Positions.On, currLoc); } // set side labels (if any) if (label.IsArea(geomIndex)) { Locations leftLoc = label.GetLocation(geomIndex, Positions.Left); Locations rightLoc = label.GetLocation(geomIndex, Positions.Right); // if there is a right location, that is the next location to propagate if (rightLoc != Locations.Null) { if (rightLoc != currLoc) { throw new TopologyException("side location conflict", e.Coordinate); } if (leftLoc == Locations.Null) { Assert.ShouldNeverReachHere("found single null side (at " + e.Coordinate + ")"); } currLoc = leftLoc; } else { /* RHS is null - LHS must be null too. * This must be an edge from the other point, which has no location * labelling for this point. This edge must lie wholly inside or outside * the other point (which is determined by the current location). * Assign both sides to be the current location. */ Assert.IsTrue(label.GetLocation(geomIndex, Positions.Left) == Locations.Null, "found single null side"); label.SetLocation(geomIndex, Positions.Right, currLoc); label.SetLocation(geomIndex, Positions.Left, currLoc); } } } } /// /// /// /// /// public int FindIndex(EdgeEnd eSearch) { GetEnumerator(); // force edgelist to be computed for (int i = 0; i < edgeList.Count; i++) { EdgeEnd e = (EdgeEnd) edgeList[i]; if (e == eSearch) { return i; } } return -1; } /// /// /// /// public virtual void Write(StreamWriter outstream) { for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; e.Write(outstream); } } /// /// 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[e] = obj; edgeList = null; // edge list has changed - clear the cache } /// /// /// private void ComputeEdgeEndLabels() { // Compute edge label for each EdgeEnd for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd ee = (EdgeEnd) it.Current; ee.ComputeLabel(); } } /// /// /// /// /// private bool 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 IList edges = Edges; // if no edges, trivially consistent if (edges.Count <= 0) { return true; } // initialize startLoc to location of last Curve side (if any) int lastEdgeIndex = edges.Count - 1; Label startLabel = ((EdgeEnd) edges[lastEdgeIndex]).Label; Locations startLoc = startLabel.GetLocation(geomIndex, Positions.Left); Assert.IsTrue(startLoc != Locations.Null, "Found unlabelled area edge"); Locations currLoc = startLoc; for (IEnumerator it = GetEnumerator(); it.MoveNext();) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; // we assume that we are only checking a area Assert.IsTrue(label.IsArea(geomIndex), "Found non-area edge"); Locations leftLoc = label.GetLocation(geomIndex, Positions.Left); Locations rightLoc = label.GetLocation(geomIndex, Positions.Right); // check that edge is really a boundary between inside and outside! if (leftLoc == rightLoc) { return false; } // check side location conflict if (rightLoc != currLoc) { return false; } currLoc = leftLoc; } return true; } } }