SegmentNodeList.java
/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.noding;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.util.Assert;
/**
* A list of the {@link SegmentNode}s present along a noded {@link SegmentString}.
*
* @version 1.7
*/
public class SegmentNodeList
{
private Map nodeMap = new TreeMap();
private NodedSegmentString edge; // the parent edge
public SegmentNodeList(NodedSegmentString edge)
{
this.edge = edge;
}
/**
* Gets the number of nodes in the list
*
* @return the size of the list
*/
public int size() {
return nodeMap.size();
}
public NodedSegmentString getEdge() { return edge; }
/**
* Adds an intersection into the list, if it isn't already there.
* The input segmentIndex and dist are expected to be normalized.
*
* @return the SegmentIntersection found or added
*/
public SegmentNode add(Coordinate intPt, int segmentIndex)
{
SegmentNode eiNew = new SegmentNode(edge, intPt, segmentIndex, edge.getSegmentOctant(segmentIndex));
SegmentNode ei = (SegmentNode) nodeMap.get(eiNew);
if (ei != null) {
// debugging sanity check
Assert.isTrue(ei.coord.equals2D(intPt), "Found equal nodes with different coordinates");
// if (! ei.coord.equals2D(intPt))
// Debug.println("Found equal nodes with different coordinates");
return ei;
}
// node does not exist, so create it
nodeMap.put(eiNew, eiNew);
return eiNew;
}
/**
* returns an iterator of SegmentNodes
*/
public Iterator iterator() { return nodeMap.values().iterator(); }
/**
* Adds nodes for the first and last points of the edge
*/
private void addEndpoints()
{
int maxSegIndex = edge.size() - 1;
add(edge.getCoordinate(0), 0);
add(edge.getCoordinate(maxSegIndex), maxSegIndex);
}
/**
* Adds nodes for any collapsed edge pairs.
* Collapsed edge pairs can be caused by inserted nodes, or they can be
* pre-existing in the edge vertex list.
* In order to provide the correct fully noded semantics,
* the vertex at the base of a collapsed pair must also be added as a node.
*/
private void addCollapsedNodes()
{
List collapsedVertexIndexes = new ArrayList();
findCollapsesFromInsertedNodes(collapsedVertexIndexes);
findCollapsesFromExistingVertices(collapsedVertexIndexes);
// node the collapses
for (Iterator it = collapsedVertexIndexes.iterator(); it.hasNext(); ) {
int vertexIndex = ((Integer) it.next()).intValue();
add(edge.getCoordinate(vertexIndex), vertexIndex);
}
}
/**
* Adds nodes for any collapsed edge pairs
* which are pre-existing in the vertex list.
*/
private void findCollapsesFromExistingVertices(List collapsedVertexIndexes)
{
for (int i = 0; i < edge.size() - 2; i++) {
Coordinate p0 = edge.getCoordinate(i);
Coordinate p1 = edge.getCoordinate(i + 1);
Coordinate p2 = edge.getCoordinate(i + 2);
if (p0.equals2D(p2)) {
// add base of collapse as node
collapsedVertexIndexes.add(i + 1);
}
}
}
/**
* Adds nodes for any collapsed edge pairs caused by inserted nodes
* Collapsed edge pairs occur when the same coordinate is inserted as a node
* both before and after an existing edge vertex.
* To provide the correct fully noded semantics,
* the vertex must be added as a node as well.
*/
private void findCollapsesFromInsertedNodes(List collapsedVertexIndexes)
{
int[] collapsedVertexIndex = new int[1];
Iterator it = iterator();
// there should always be at least two entries in the list, since the endpoints are nodes
SegmentNode eiPrev = (SegmentNode) it.next();
while (it.hasNext()) {
SegmentNode ei = (SegmentNode) it.next();
boolean isCollapsed = findCollapseIndex(eiPrev, ei, collapsedVertexIndex);
if (isCollapsed)
collapsedVertexIndexes.add(collapsedVertexIndex[0]);
eiPrev = ei;
}
}
private boolean findCollapseIndex(SegmentNode ei0, SegmentNode ei1, int[] collapsedVertexIndex)
{
// only looking for equal nodes
if (! ei0.coord.equals2D(ei1.coord)) return false;
int numVerticesBetween = ei1.segmentIndex - ei0.segmentIndex;
if (! ei1.isInterior()) {
numVerticesBetween--;
}
// if there is a single vertex between the two equal nodes, this is a collapse
if (numVerticesBetween == 1) {
collapsedVertexIndex[0] = ei0.segmentIndex + 1;
return true;
}
return false;
}
/**
* Creates new edges for all the edges that the intersections in this
* list split the parent edge into.
* Adds the edges to the provided argument list
* (this is so a single list can be used to accumulate all split edges
* for a set of {@link SegmentString}s).
*/
public void addSplitEdges(Collection edgeList)
{
// ensure that the list has entries for the first and last point of the edge
addEndpoints();
addCollapsedNodes();
Iterator it = iterator();
// there should always be at least two entries in the list, since the endpoints are nodes
SegmentNode eiPrev = (SegmentNode) it.next();
while (it.hasNext()) {
SegmentNode ei = (SegmentNode) it.next();
SegmentString newEdge = createSplitEdge(eiPrev, ei);
/*
if (newEdge.size() < 2)
throw new RuntimeException("created single point edge: " + newEdge.toString());
*/
edgeList.add(newEdge);
eiPrev = ei;
}
//checkSplitEdgesCorrectness(testingSplitEdges);
}
/**
* Checks the correctness of the set of split edges corresponding to this edge.
*
* @param splitEdges the split edges for this edge (in order)
*/
private void checkSplitEdgesCorrectness(List splitEdges)
{
Coordinate[] edgePts = edge.getCoordinates();
// check that first and last points of split edges are same as endpoints of edge
SegmentString split0 = (SegmentString) splitEdges.get(0);
Coordinate pt0 = split0.getCoordinate(0);
if (! pt0.equals2D(edgePts[0]))
throw new RuntimeException("bad split edge start point at " + pt0);
SegmentString splitn = (SegmentString) splitEdges.get(splitEdges.size() - 1);
Coordinate[] splitnPts = splitn.getCoordinates();
Coordinate ptn = splitnPts[splitnPts.length - 1];
if (! ptn.equals2D(edgePts[edgePts.length - 1]))
throw new RuntimeException("bad split edge end point at " + ptn);
}
/**
* Create a new "split edge" with the section of points between
* (and including) the two intersections.
* The label for the new edge is the same as the label for the parent edge.
*/
private SegmentString createSplitEdge(SegmentNode ei0, SegmentNode ei1)
{
Coordinate[] pts = createSplitEdgePts(ei0, ei1);
return new NodedSegmentString(pts, edge.getData());
}
/**
* Extracts the points for a split edge running between two nodes.
* The extracted points should contain no duplicate points.
* There should always be at least two points extracted
* (which will be the given nodes).
*
* @param ei0 the start node of the split edge
* @param ei1 the end node of the split edge
* @return the points for the split edge
*/
private Coordinate[] createSplitEdgePts(SegmentNode ei0, SegmentNode ei1) {
//Debug.println("\ncreateSplitEdge"); Debug.print(ei0); Debug.print(ei1);
int npts = ei1.segmentIndex - ei0.segmentIndex + 2;
// if only two points in split edge they must be the node points
if (npts == 2) return new Coordinate[] { new Coordinate(ei0.coord), new Coordinate(ei1.coord) };
Coordinate lastSegStartPt = edge.getCoordinate(ei1.segmentIndex);
/**
* If the last intersection point is not equal to the its segment start pt,
* add it to the points list as well.
* This check is needed because the distance metric is not totally reliable!
*
* Also ensure that the created edge always has at least 2 points.
*
* The check for point equality is 2D only - Z values are ignored
*/
boolean useIntPt1 = ei1.isInterior() || ! ei1.coord.equals2D(lastSegStartPt);
if (! useIntPt1) {
npts--;
}
Coordinate[] pts = new Coordinate[npts];
int ipt = 0;
pts[ipt++] = ei0.coord.copy();
for (int i = ei0.segmentIndex + 1; i <= ei1.segmentIndex; i++) {
pts[ipt++] = edge.getCoordinate(i);
}
if (useIntPt1) pts[ipt] = ei1.coord.copy();
return pts;
}
/**
* Gets the list of coordinates for the fully noded segment string,
* including all original segment string vertices and vertices
* introduced by nodes in this list.
* Repeated coordinates are collapsed.
*
* @return an array of Coordinates
*
*/
public Coordinate[] getSplitCoordinates()
{
CoordinateList coordList = new CoordinateList();
// ensure that the list has entries for the first and last point of the edge
addEndpoints();
Iterator it = iterator();
// there should always be at least two entries in the list, since the endpoints are nodes
SegmentNode eiPrev = (SegmentNode) it.next();
while (it.hasNext()) {
SegmentNode ei = (SegmentNode) it.next();
addEdgeCoordinates(eiPrev, ei, coordList);
eiPrev = ei;
}
return coordList.toCoordinateArray();
}
private void addEdgeCoordinates(SegmentNode ei0, SegmentNode ei1,
CoordinateList coordList) {
Coordinate[] pts = createSplitEdgePts(ei0, ei1);
coordList.add(pts, false);
}
public void print(PrintStream out)
{
out.println("Intersections:");
for (Iterator it = iterator(); it.hasNext(); ) {
SegmentNode ei = (SegmentNode) it.next();
ei.print(out);
}
}
}
// INCOMPLETE!
class NodeVertexIterator
implements Iterator
{
private SegmentNodeList nodeList;
private NodedSegmentString edge;
private Iterator nodeIt;
private SegmentNode currNode = null;
private SegmentNode nextNode = null;
private int currSegIndex = 0;
NodeVertexIterator(SegmentNodeList nodeList)
{
this.nodeList = nodeList;
edge = nodeList.getEdge();
nodeIt = nodeList.iterator();
readNextNode();
}
public boolean hasNext() {
if (nextNode == null) return false;
return true;
}
public Object next()
{
if (currNode == null) {
currNode = nextNode;
currSegIndex = currNode.segmentIndex;
readNextNode();
return currNode;
}
// check for trying to read too far
if (nextNode == null) return null;
if (nextNode.segmentIndex == currNode.segmentIndex) {
currNode = nextNode;
currSegIndex = currNode.segmentIndex;
readNextNode();
return currNode;
}
if (nextNode.segmentIndex > currNode.segmentIndex) {
}
return null;
}
private void readNextNode()
{
if (nodeIt.hasNext())
nextNode = (SegmentNode) nodeIt.next();
else
nextNode = null;
}
/**
* Not implemented.
*
*@throws UnsupportedOperationException This method is not implemented.
*/
public void remove() {
throw new UnsupportedOperationException(getClass().getName());
}
}