package com.revolsys.geometry.graph.linestring; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import java.util.function.Predicate; import com.revolsys.comparator.CollectionComparator; import com.revolsys.geometry.graph.Edge; import com.revolsys.geometry.graph.Graph; import com.revolsys.geometry.graph.Node; import com.revolsys.geometry.graph.comparator.EdgeAttributeValueComparator; import com.revolsys.geometry.graph.comparator.NodeDistanceComparator; import com.revolsys.geometry.graph.filter.EdgeObjectFilter; import com.revolsys.geometry.graph.filter.NodeCoordinatesFilter; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.coordinates.LineSegmentUtil; import com.revolsys.geometry.model.coordinates.comparator.CoordinatesDistanceComparator; import com.revolsys.geometry.model.coordinates.filter.CrossingLineSegmentFilter; import com.revolsys.geometry.model.coordinates.filter.PointOnLineSegment; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDoubleGF; public class LineStringGraph extends Graph<LineSegment> { private static final String INDEX = "INDEX"; private static final CollectionComparator<Integer> INDEX_COMPARATOR = new CollectionComparator<>(); public static Edge<LineSegment> getFirstEdge(final Collection<Edge<LineSegment>> edges) { final Iterator<Edge<LineSegment>> iterator = edges.iterator(); if (iterator.hasNext()) { Edge<LineSegment> edge = iterator.next(); List<Integer> index = edge.getProperty(INDEX); while (iterator.hasNext()) { final Edge<LineSegment> edge2 = iterator.next(); final List<Integer> index2 = edge2.getProperty(INDEX); if (INDEX_COMPARATOR.compare(index, index2) > 0) { edge = edge2; index = index2; } } return edge; } else { return null; } } private BoundingBox envelope; private Point fromPoint; private GeometryFactory geometryFactory; private LineString points; public LineStringGraph(final GeometryFactory geometryFactory, final LineString line) { super(false); setGeometryFactory(geometryFactory); setLineString(line); } public LineStringGraph(final LineString line) { this(line.getGeometryFactory(), line); } @Override protected LineSegment clone(final LineSegment object, final LineString line) { return new LineSegmentDoubleGF(line); } @Override public LineString getEdgeLine(final int edgeId) { final LineSegment segment = getEdgeObject(edgeId); if (segment == null) { return null; } else { return segment; } } public LineString getLine() { final Set<Edge<LineSegment>> processedEdges = new HashSet<>(); final List<Point> newPoints = new ArrayList<>(); Node<LineSegment> previousNode = findNode(this.fromPoint); newPoints.add(previousNode); do { final List<Edge<LineSegment>> outEdges = previousNode.getOutEdges(); outEdges.removeAll(processedEdges); if (outEdges.isEmpty()) { previousNode = null; } else { final Edge<LineSegment> edge = getFirstEdge(outEdges); processedEdges.add(edge); final Node<LineSegment> nextNode = edge.getToNode(); newPoints.add(nextNode); previousNode = nextNode; } } while (previousNode != null && !previousNode.equals(2, this.fromPoint)); return this.geometryFactory.lineString(newPoints); } public List<LineString> getLines() { removeDuplicateEdges(); final EdgeAttributeValueComparator<LineSegment> comparator = new EdgeAttributeValueComparator<>( "INDEX"); final List<LineString> lines = new ArrayList<>(); final List<Point> points = new ArrayList<>(); final Consumer<Edge<LineSegment>> action = new Consumer<Edge<LineSegment>>() { private Node<LineSegment> previousNode = null; @Override public void accept(final Edge<LineSegment> edge) { final LineSegment lineSegment = edge.getObject(); if (lineSegment.getLength() > 0) { final Node<LineSegment> fromNode = edge.getFromNode(); final Node<LineSegment> toNode = edge.getToNode(); if (this.previousNode == null) { points.add(lineSegment.getPoint(0)); points.add(lineSegment.getPoint(1)); } else if (fromNode == this.previousNode) { if (edge.getLength() > 0) { points.add(toNode); } } else { if (points.size() > 1) { final LineString line = LineStringGraph.this.geometryFactory.lineString(points); lines.add(line); } points.clear(); ; points.add(lineSegment.getPoint(0)); points.add(lineSegment.getPoint(1)); } if (points.size() > 1) { final int toDegree = toNode.getDegree(); if (toDegree != 2) { final LineString line = LineStringGraph.this.geometryFactory.lineString(points); lines.add(line); points.clear(); ; points.add(toNode); } } this.previousNode = toNode; } } }; forEachEdge(comparator, action); if (points.size() > 1) { final LineString line = this.geometryFactory.lineString(points); lines.add(line); } return lines; } public Map<Edge<LineSegment>, List<Node<LineSegment>>> getPointsOnEdges( final Graph<LineSegment> graph1, final double tolerance) { final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge1 = new HashMap<>(); for (final Edge<LineSegment> edge : getEdges()) { final Node<LineSegment> fromNode = edge.getFromNode(); final Node<LineSegment> toNode = edge.getToNode(); final LineSegment lineSegment = edge.getObject(); final PointOnLineSegment coordinatesFilter = new PointOnLineSegment(lineSegment, tolerance); final NodeCoordinatesFilter<LineSegment> nodeFilter = new NodeCoordinatesFilter<>( coordinatesFilter); final NodeDistanceComparator<LineSegment> comparator = new NodeDistanceComparator<>(fromNode); final List<Node<LineSegment>> nodes = graph1.getNodes(nodeFilter, comparator); for (final Iterator<Node<LineSegment>> iterator = nodes.iterator(); iterator.hasNext();) { final Node<LineSegment> node = iterator.next(); if (node.equals(2, fromNode)) { iterator.remove(); } else if (node.equals(2, toNode)) { iterator.remove(); } } if (!nodes.isEmpty()) { pointsOnEdge1.put(edge, nodes); } } return pointsOnEdge1; } public Geometry getSelfIntersections() { final Set<Point> intersectionPoints = new HashSet<>(); for (int i = 0; i < this.points.getVertexCount(); i++) { final Point point = this.points.getPoint(i); final Node<LineSegment> node = getNode(point); if (node.getDegree() > 2 || hasTouchingEdges(node)) { intersectionPoints.add(point); } } forEachEdge((edge1) -> { final LineSegment lineSegment1 = edge1.getObject(); forEachEdge(edge1, (edge2) -> { if (edge1 != edge2) { final LineSegment lineSegment2 = edge2.getObject(); final Geometry intersections = ((LineSegment)lineSegment1 .convertGeometry(getGeometryFactory())).getIntersection(lineSegment2); for (final Point intersection : intersections.vertices()) { if (!lineSegment1.isEndPoint(intersection) && !lineSegment2.isEndPoint(intersection)) { intersectionPoints.add(intersection); } } } }); }); return this.geometryFactory.punctual(intersectionPoints); } /** * Get the z-value for the point if it is at a node or on an edge. * * @param point The point to get the z-value for. * @return The z-value or Double.NaN. */ public double getZ(final Point point) { final Node<LineSegment> node = findNode(point); if (node == null) { final double maxDistance = this.geometryFactory.getScaleXY() / 1000; for (final Edge<LineSegment> edge : getEdges(point, maxDistance)) { final LineSegment segment = edge.getObject(); if (segment.isPointOnLineMiddle(point, maxDistance)) { final double elevation = segment.getElevation(point); return this.geometryFactory.makeZPrecise(elevation); } } return Double.NaN; } else { return node.getZ(); } } public boolean hasTouchingEdges(final Node<LineSegment> node) { final GeometryFactory precisionModel = getPrecisionModel(); final List<Edge<LineSegment>> edges = getEdges(node, precisionModel.getScaleXY()); for (final Edge<LineSegment> edge : edges) { final Point lineStart = edge.getFromNode(); final Point lineEnd = edge.getToNode(); if (LineSegmentUtil.isPointOnLineMiddle(precisionModel, lineStart, lineEnd, node)) { return true; } } return false; } public boolean intersects(final LineString line) { BoundingBox envelope = line.getBoundingBox(); final double scaleXY = this.geometryFactory.getScaleXY(); double maxDistance = 0; if (scaleXY > 0) { maxDistance = 1 / scaleXY; } envelope = envelope.expand(maxDistance); if (envelope.intersects(this.envelope)) { final LineString points = line; final int numPoints = points.getVertexCount(); final Point fromPoint = points.getPoint(0); final Point toPoint = points.getPoint(numPoints - 1); Point previousPoint = fromPoint; for (int i = 1; i < numPoints; i++) { final Point nextPoint = points.getPoint(i); final LineSegment line1 = new LineSegmentDoubleGF(previousPoint, nextPoint); final List<Edge<LineSegment>> edges = EdgeLessThanDistance.getEdges(this, line1, maxDistance); for (final Edge<LineSegment> edge2 : edges) { final LineSegment line2 = edge2.getObject(); final Geometry intersections = line1.getIntersection(line2); for (final Point intersection : intersections.vertices()) { if (intersection.equals(fromPoint) || intersection.equals(toPoint)) { // Point intersection, make sure it's not at the start final Node<LineSegment> node = findNode(intersection); final int degree = node.getDegree(); if (node.equals(2, this.fromPoint)) { if (degree > 2) { // Intersection not at the start/end of the other line, taking // into account loops return true; } } else if (degree > 1) { // Intersection not at the start/end of the other line return true; } } else { // Intersection not at the start/end of the line return true; } } for (final Point point : line1.vertices()) { if (line2.distancePoint(point) < maxDistance) { if (point.equals(fromPoint) || point.equals(toPoint)) { // Point intersection, make sure it's not at the start final double maxDistance1 = maxDistance; for (final Node<LineSegment> node : this.getNodes(point, maxDistance1)) { final int degree = node.getDegree(); if (node.equals(2, this.fromPoint)) { if (degree > 2) { // Intersection not at the start/end of the other line, // taking // into account loops return true; } } else if (degree > 1) { // Intersection not at the start/end of the other line return true; } } } else { // Intersection not at the start/end of the line return true; } } } } previousPoint = nextPoint; } } return false; } public boolean isSimple() { for (final Node<LineSegment> node : getNodes()) { if (node.getDegree() > 2) { return false; } } for (final Edge<LineSegment> edge : getEdges()) { final LineSegment line = edge.getObject(); final EdgeObjectFilter<LineSegment> filter = new EdgeObjectFilter<>( new LineSegmentIntersectingFilter(line)); final List<Edge<LineSegment>> edges = getEdges(line, filter); for (final Edge<LineSegment> edge2 : edges) { final LineSegment line2 = edge2.getObject(); final Geometry intersections = line.getIntersection(line2); if (intersections instanceof LineSegment) { return false; } else if (intersections instanceof Point) { if (edge.getCommonNodes(edge2).isEmpty()) { return false; } } } } return true; } @Override public void nodeMoved(final Node<LineSegment> node, final Node<LineSegment> newNode) { if (this.fromPoint.equals(2, node)) { this.fromPoint = newNode.newPoint2D(); } } private void removeDuplicateEdges() { final Comparator<Edge<LineSegment>> comparator = new EdgeAttributeValueComparator<>(INDEX); forEachEdge(comparator, (edge) -> { final Node<LineSegment> fromNode = edge.getFromNode(); final Node<LineSegment> toNode = edge.getToNode(); final Collection<Edge<LineSegment>> edges = fromNode.getEdgesTo(toNode); final int duplicateCount = edges.size(); if (duplicateCount > 1) { edges.remove(edge); Edge.remove(edges); } }); } @Override public void setGeometryFactory(final GeometryFactory geometryFactory) { this.geometryFactory = geometryFactory; setPrecisionModel(geometryFactory); } private void setLineString(final LineString lineString) { this.points = lineString; int index = 0; for (final LineSegment lineSegment : lineString.segments()) { final double fromX = lineSegment.getX(0); final double fromY = lineSegment.getY(0); final double toX = lineSegment.getX(1); final double toY = lineSegment.getY(1); final Edge<LineSegment> edge = addEdge((LineSegment)lineSegment.clone(), fromX, fromY, toX, toY); edge.setProperty(INDEX, Arrays.asList(index++)); } this.fromPoint = lineString.getPoint(0).newPoint2D(); this.envelope = lineString.getBoundingBox(); } public void splitCrossingEdges() { final Comparator<Edge<LineSegment>> comparator = new EdgeAttributeValueComparator<>(INDEX); forEachEdge(comparator, (edge) -> { final LineSegment line1 = edge.getObject(); final Predicate<LineSegment> lineFilter = new CrossingLineSegmentFilter(line1); final Predicate<Edge<LineSegment>> filter = new EdgeObjectFilter<>(lineFilter); final List<Edge<LineSegment>> edges = getEdges(line1, filter); if (!edges.isEmpty()) { final List<Point> points = new ArrayList<>(); for (final Edge<LineSegment> edge2 : edges) { final LineSegment line2 = edge2.getObject(); final Geometry intersections = line1.getIntersection(line2); if (intersections instanceof Point) { final Point intersection = (Point)intersections; points.add(intersection); edge2.split(intersection); } } if (!points.isEmpty()) { edge.splitEdge(points); } } }); } @Override public <V extends Point> List<Edge<LineSegment>> splitEdge(final Edge<LineSegment> edge, final Collection<V> nodes) { final List<Edge<LineSegment>> newEdges = new ArrayList<>(); if (!edge.isRemoved()) { final Node<LineSegment> fromNode = edge.getFromNode(); final Node<LineSegment> toNode = edge.getToNode(); final CoordinatesDistanceComparator comparator = new CoordinatesDistanceComparator( fromNode.getX(), toNode.getY()); final Set<Point> newPoints = new TreeSet<>(comparator); for (final Point point : nodes) { newPoints.add(point); } newPoints.add(toNode); final List<Integer> index = edge.getProperty(INDEX); int i = 0; Point previousPoint = fromNode; for (final Point point : newPoints) { final LineSegment lineSegment = new LineSegmentDoubleGF(previousPoint, point); final Edge<LineSegment> newEdge = addEdge(lineSegment, previousPoint, point); final List<Integer> newIndecies = new ArrayList<>(index); newIndecies.add(i++); newEdge.setProperty(INDEX, newIndecies); newEdges.add(newEdge); previousPoint = point; } remove(edge); } return newEdges; } public <V extends Point> void splitEdges(final Map<Edge<LineSegment>, List<V>> pointsOnEdge1) { for (final Entry<Edge<LineSegment>, List<V>> entry : pointsOnEdge1.entrySet()) { final Edge<LineSegment> edge = entry.getKey(); final List<V> nodes = entry.getValue(); splitEdge(edge, nodes); } } public void splitEdgesCloseToNodes() { double distance = 0; final double scaleXY = this.geometryFactory.getScaleXY(); if (scaleXY > 0) { distance = 1 / scaleXY; } for (final Node<LineSegment> node : getNodes()) { final List<Edge<LineSegment>> edges = getEdges(node, distance); edges.removeAll(node.getEdges()); if (!edges.isEmpty()) { for (final Edge<LineSegment> edge : edges) { final LineSegment line = edge.getObject(); if (line.isPointOnLineMiddle(node, distance)) { edge.split(node); } } } } } }