package com.revolsys.geometry.algorithm.linematch; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.TreeSet; import com.revolsys.geometry.graph.Edge; import com.revolsys.geometry.graph.Graph; import com.revolsys.geometry.graph.Node; import com.revolsys.geometry.graph.comparator.NodeDistanceComparator; import com.revolsys.geometry.graph.visitor.BoundingBoxIntersectsEdgeVisitor; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDoubleGF; public class LineMatchGraph<T> extends Graph<LineSegmentMatch> { private final GeometryFactory geometryFactory; private final List<T> objects = new ArrayList<>(); private final List<Set<Node<LineSegmentMatch>>> startNodes = new ArrayList<>(); private final int tolerance = 1; public LineMatchGraph(final GeometryFactory geometryFactory, final LineString line) { this.geometryFactory = geometryFactory; add(line, 0); } public LineMatchGraph(final GeometryFactory geometryFactory, final T object, final LineString line) { this.geometryFactory = geometryFactory; addLine(object, line); } public LineMatchGraph(final LineString line) { this(line.getGeometryFactory(), line); } public LineMatchGraph(final T object, final LineString line) { this(line.getGeometryFactory(), object, line); } public boolean add(final Lineal lineal) { final int index = this.startNodes.size(); if (lineal != null) { for (final LineString line : lineal.lineStrings()) { add(line, index); } if (index > 0) { integrateMultiLine(lineal, index); return hasMatchedLines(index); } } return false; } private Edge<LineSegmentMatch> add(final LineSegmentMatch lineSegmentMatch, final Node<LineSegmentMatch> from, final Node<LineSegmentMatch> to) { final Edge<LineSegmentMatch> newEdge = add(from, to); final LineSegmentMatch newLineSegmentMatch = newEdge.getObject(); for (int i = 0; i < lineSegmentMatch.getSegmentCount(); i++) { if (lineSegmentMatch.hasSegment(i)) { final LineSegment realSegment = lineSegmentMatch.getSegment(i); final Point coordinate0 = realSegment.project(from); final Point coordinate1 = realSegment.project(to); final LineSegment newSegment = new LineSegmentDoubleGF(this.geometryFactory, coordinate0, coordinate1); newLineSegmentMatch.addSegment(newSegment, i); } } return newEdge; } public boolean add(final LineString line) { final int index = this.startNodes.size(); add(line, index); if (index > 0) { integrateLine(line, index); return hasMatchedLines(index); } else { return false; } } private void add(final LineString line, final int index) { if (line.getLength() > 0) { final LineString coords = line; final Point coordinate0 = coords.getPoint(0); final Node<LineSegmentMatch> node = getNode(coordinate0); final Set<Node<LineSegmentMatch>> indexStartNodes = getStartNodes(index); indexStartNodes.add(node); Point previousCoordinate = coordinate0; for (int i = 1; i < coords.getVertexCount(); i++) { final Point coordinate = coords.getPoint(i); final LineSegment segment = new LineSegmentDoubleGF(this.geometryFactory, previousCoordinate, coordinate); if (segment.getLength() > 0) { add(previousCoordinate, coordinate, segment, index); } previousCoordinate = coordinate; } } } private Edge<LineSegmentMatch> add(final Node<LineSegmentMatch> startNode, final Node<LineSegmentMatch> endNode) { Edge<LineSegmentMatch> edge = getEdge(startNode, endNode); if (edge == null) { final LineSegmentMatch lineSegmentMatch = new LineSegmentMatch(this.geometryFactory, startNode, endNode); edge = addEdge(lineSegmentMatch, lineSegmentMatch.getLine()); } return edge; } private Edge<LineSegmentMatch> add(final Point start, final Point end) { final Node<LineSegmentMatch> startNode = getNode(start); final Node<LineSegmentMatch> endNode = getNode(end); return add(startNode, endNode); } private Edge<LineSegmentMatch> add(final Point start, final Point end, final LineSegment segment, final int index) { final Edge<LineSegmentMatch> edge = add(start, end); final LineSegmentMatch lineSegmentMatch = edge.getObject(); lineSegmentMatch.addSegment(segment, index); return edge; } public boolean add(final T object, final Lineal lineal) { if (add(lineal)) { addObject(object); return true; } else { return false; } } public boolean addLine(final T object, final Lineal lineal) { if (add(lineal)) { addObject(object); return true; } else { return false; } } public boolean addLine(final T object, final LineString line) { if (add(line)) { addObject(object); return true; } else { return false; } } private void addObject(final T object) { final int index = this.startNodes.size() - 1; for (int i = this.objects.size(); i < index; i++) { this.objects.add(null); } this.objects.add(object); } public boolean cleanOverlappingMatches() { final Lineal lines = getOverlappingMatches(); return lines.isEmpty(); } public Lineal getCurrentMatchedLines() { return getMatchedLines(this.startNodes.size() - 1); } public double getDuplicateMatchLength(final Node<LineSegmentMatch> node, final boolean direction, final int index1, final int index2) { List<Edge<LineSegmentMatch>> edges; if (direction) { edges = node.getOutEdges(); } else { edges = node.getInEdges(); } for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.getMatchCount(index1) > 2) { if (lineSegmentMatch.hasMatches(index1, index2)) { final LineSegment segment = lineSegmentMatch.getSegment(index2); final double length = segment.getLength(); final Node<LineSegmentMatch> nextNode = edge.getOppositeNode(node); return length + getDuplicateMatchLength(nextNode, direction, index1, index2); } } } return 0; } private Edge<LineSegmentMatch> getEdge(final Node<LineSegmentMatch> node1, final Node<LineSegmentMatch> node2) { final List<Edge<LineSegmentMatch>> edges = node1.getOutEdgesTo(node2); if (edges.isEmpty()) { return null; } else { return edges.get(0); } } private Edge<LineSegmentMatch> getEdge(final Point coordinate0, final Point coordinate1) { final Node<LineSegmentMatch> node1 = findNode(coordinate0); final Node<LineSegmentMatch> node2 = findNode(coordinate1); return getEdge(node1, node2); } private Set<Edge<LineSegmentMatch>> getEdgesWithoutMatch(final LineString line, final int index) { final Set<Edge<LineSegmentMatch>> edges = new LinkedHashSet<>(); final LineString coordinatesList = line; final Point coordinate0 = coordinatesList.getPoint(0); Point previousCoordinate = coordinate0; for (int i = 1; i < coordinatesList.getVertexCount(); i++) { final Point coordinate = coordinatesList.getPoint(i); if (previousCoordinate.distancePoint(coordinate) > 0) { final Edge<LineSegmentMatch> edge = getEdge(previousCoordinate, coordinate); if (edge != null) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (!lineSegmentMatch.hasMatches(index)) { edges.add(edge); } } } previousCoordinate = coordinate; } return edges; } public int getIndexCount() { return this.startNodes.size(); } public Lineal getMatchedLines(final int index) { final List<LineString> lines = new ArrayList<>(); final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(index)) { final List<Point> coordinates = new ArrayList<>(); while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index) && !processedEdges.contains(edge)) { if (lineSegmentMatch.hasMatches(index)) { if (coordinates.isEmpty()) { final Point startCoordinate = currentNode; coordinates.add(startCoordinate); } final Node<LineSegmentMatch> toNode = edge.getOppositeNode(currentNode); final Point toCoordinate = toNode; coordinates.add(toCoordinate); } else { newLine(lines, coordinates); coordinates.clear(); } processedEdges.add(edge); nextNode = edge.getOppositeNode(currentNode); } } currentNode = nextNode; } newLine(lines, coordinates); } return this.geometryFactory.lineal(lines); } public Lineal getMatchedLines(final int index1, final int index2) { final List<LineString> lines = getMatchedLinesList(index1, index2); return this.geometryFactory.lineal(lines); } public List<LineString> getMatchedLinesList(final int index1, final int index2) { final List<LineString> lines = new ArrayList<>(); final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(index2)) { final List<Point> coordinates = new ArrayList<>(); while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index2) && !processedEdges.contains(edge)) { if (lineSegmentMatch.hasMatches(index1, index2)) { if (coordinates.isEmpty()) { final Point startCoordinate = currentNode; coordinates.add(startCoordinate); } final Node<LineSegmentMatch> toNode = edge.getToNode(); final Point toCoordinate = toNode; coordinates.add(toCoordinate); } else { newLine(lines, coordinates); coordinates.clear(); } processedEdges.add(edge); nextNode = edge.getToNode(); } } currentNode = nextNode; } newLine(lines, coordinates); } return lines; } public List<T> getMatchedObjects() { final ArrayList<T> matchedObjects = new ArrayList<>(); for (final T object : this.objects) { if (object != null) { matchedObjects.add(object); } } return matchedObjects; } public double getMatchLength(final Node<LineSegmentMatch> node, final boolean direction, final int index1, final int index2) { List<Edge<LineSegmentMatch>> edges; if (direction) { edges = node.getOutEdges(); } else { edges = node.getInEdges(); } for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasMatches(index1, index2)) { final LineSegment segment = lineSegmentMatch.getSegment(index2); final double length = segment.getLength(); final Node<LineSegmentMatch> nextNode = edge.getOppositeNode(node); return length + getMatchLength(nextNode, direction, index1, index2); } } return 0; } public Lineal getNonMatchedLines(final int index) { final List<LineString> lines = new ArrayList<>(); final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(index)) { final List<Point> coordinates = new ArrayList<>(); while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index) && !processedEdges.contains(edge)) { final Node<LineSegmentMatch> toNode = edge.getToNode(); if (!lineSegmentMatch.hasMatches(index)) { if (coordinates.isEmpty()) { coordinates.add(currentNode); } coordinates.add(toNode); } else { newLine(lines, coordinates); coordinates.clear(); } processedEdges.add(edge); nextNode = toNode; } } currentNode = nextNode; } newLine(lines, coordinates); } return this.geometryFactory.lineal(lines); } public Lineal getNonMatchedLines(final int index1, final int index2) { final List<LineString> lines = new ArrayList<>(); final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(index1)) { final List<Point> coordinates = new ArrayList<>(); while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index1) && !processedEdges.contains(edge)) { if (!lineSegmentMatch.hasMatches(index1, index2)) { if (coordinates.isEmpty()) { final Point startCoordinate = currentNode; coordinates.add(startCoordinate); } final Node<LineSegmentMatch> toNode = edge.getToNode(); final Point toCoordinate = toNode; coordinates.add(toCoordinate); } else { newLine(lines, coordinates); coordinates.clear(); } processedEdges.add(edge); nextNode = edge.getToNode(); } } currentNode = nextNode; } newLine(lines, coordinates); } return this.geometryFactory.lineal(lines); } public T getObject(final int index) { if (index < this.objects.size()) { return this.objects.get(index); } else { return null; } } public Lineal getOverlappingMatches() { final List<LineString> overlappingLines = new ArrayList<>(); final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(0)) { while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(0) && !processedEdges.contains(edge)) { if (lineSegmentMatch.getMatchCount(0) > 2) { for (int i = 1; i < lineSegmentMatch.getSegmentCount() && lineSegmentMatch.getMatchCount(0) > 2; i++) { if (lineSegmentMatch.hasSegment(i)) { if (!hasMatch(currentNode, false, 0, i)) { final Node<LineSegmentMatch> toNode = edge.getToNode(); if (!hasMatch(toNode, true, 0, i)) { lineSegmentMatch.removeSegment(i); } else { final double matchLength = getMatchLength(currentNode, false, 0, i); final double duplicateMatchLength = getDuplicateMatchLength(currentNode, true, 0, i); if (matchLength + duplicateMatchLength <= 2) { lineSegmentMatch.removeSegment(i); } } } } } if (lineSegmentMatch.getMatchCount(0) > 2) { overlappingLines.add(lineSegmentMatch.getLine()); } } processedEdges.add(edge); nextNode = edge.getToNode(); } } currentNode = nextNode; } } final Lineal lines = this.geometryFactory.lineal(overlappingLines); return lines; } private Set<Node<LineSegmentMatch>> getStartNodes(final int index) { while (index >= this.startNodes.size()) { this.startNodes.add(new LinkedHashSet<Node<LineSegmentMatch>>()); } return this.startNodes.get(index); } public Edge<LineSegmentMatch> getUnprocessedEdgeWithSegment(final Node<LineSegmentMatch> node, final int index, final Set<Edge<LineSegmentMatch>> processedEdges) { final List<Edge<LineSegmentMatch>> edges = node.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index) && !processedEdges.contains(edge)) { processedEdges.add(edge); return edge; } } return null; } public boolean hasMatch(final Node<LineSegmentMatch> node, final boolean direction, final int index1, final int index2) { List<Edge<LineSegmentMatch>> edges; if (direction) { edges = node.getOutEdges(); } else { edges = node.getInEdges(); } for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasMatches(index1, index2)) { return true; } } return false; } public boolean hasMatchedLines(final int index) { final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(index)) { while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(index) && !processedEdges.contains(edge)) { if (lineSegmentMatch.hasMatches(index)) { return true; } nextNode = edge.getToNode(); } processedEdges.add(edge); } currentNode = nextNode; } } return false; } public boolean hasOverlappingMatches() { final Set<Edge<LineSegmentMatch>> processedEdges = new HashSet<>(); for (Node<LineSegmentMatch> currentNode : getStartNodes(0)) { while (currentNode != null) { Node<LineSegmentMatch> nextNode = null; final List<Edge<LineSegmentMatch>> edges = currentNode.getOutEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.hasSegment(0) && !processedEdges.contains(edge)) { if (lineSegmentMatch.getMatchCount(0) > 2) { return true; } processedEdges.add(edge); nextNode = edge.getToNode(); } } currentNode = nextNode; } } return false; } public boolean hasSegmentsWithIndex(final Node<LineSegmentMatch> node, final int index) { final List<Edge<LineSegmentMatch>> edges = node.getEdges(); for (final Edge<LineSegmentMatch> edge : edges) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); if (lineSegmentMatch.getSegmentCount() > index) { if (lineSegmentMatch.getSegment(index) != null) { return true; } } } return false; } /** * Integrate the line, splitting any edges which the nodes from this line are * on the other edges. * * @param line * @param index */ private void integrateLine(final LineString line, final int index) { final Set<Edge<LineSegmentMatch>> edgesToProcess = getEdgesWithoutMatch(line, index); if (!edgesToProcess.isEmpty()) { for (final Edge<LineSegmentMatch> edge : edgesToProcess) { if (!edge.isRemoved()) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); final LineSegment segment = lineSegmentMatch.getSegment(); if (!lineSegmentMatch.hasMatches(index)) { final List<Edge<LineSegmentMatch>> matchEdges = BoundingBoxIntersectsEdgeVisitor .getEdges(this, edge, this.tolerance); if (!matchEdges.isEmpty()) { final boolean allowSplit = edge.getLength() >= 2 * this.tolerance; final Set<Node<LineSegmentMatch>> splitNodes = new TreeSet<>( new NodeDistanceComparator<>(edge.getFromNode())); final Node<LineSegmentMatch> lineStart = edge.getFromNode(); final Node<LineSegmentMatch> lineEnd = edge.getToNode(); for (final ListIterator<Edge<LineSegmentMatch>> iterator = matchEdges .listIterator(); iterator.hasNext();) { final Edge<LineSegmentMatch> matchEdge = iterator.next(); iterator.remove(); final LineSegmentMatch matchLineSegmentMatch = matchEdge.getObject(); if (!matchLineSegmentMatch.hasSegment(index) && matchLineSegmentMatch.hasOtherSegment(index)) { final Node<LineSegmentMatch> line2Start = matchEdge.getFromNode(); final Node<LineSegmentMatch> line2End = matchEdge.getToNode(); final Set<Node<LineSegmentMatch>> matchSplitNodes = new TreeSet<>( new NodeDistanceComparator<>(line2Start)); final LineSegment matchSegment = matchLineSegmentMatch.getSegment(); if (matchEdge.getLength() >= 2 * this.tolerance) { if (matchSegment.isPointOnLineMiddle(lineStart, this.tolerance)) { matchSplitNodes.add(lineStart); } if (matchSegment.isPointOnLineMiddle(lineEnd, this.tolerance)) { matchSplitNodes.add(lineEnd); } } if (!matchSplitNodes.isEmpty()) { final List<Edge<LineSegmentMatch>> splitEdges = splitEdge(matchEdge, matchSplitNodes); for (final Edge<LineSegmentMatch> splitEdge : splitEdges) { iterator.add(splitEdge); } } else if (allowSplit) { if (segment.isPointOnLineMiddle(line2Start, this.tolerance)) { splitNodes.add(line2Start); } if (segment.isPointOnLineMiddle(line2End, this.tolerance)) { splitNodes.add(line2End); } } } } if (!splitNodes.isEmpty() && allowSplit) { splitEdge(edge, splitNodes); } } } } } } } private void integrateMultiLine(final Lineal lineal, final int index) { for (final LineString line : lineal.lineStrings()) { integrateLine(line, index); } } public boolean isFullyMatched(final int index) { final Lineal difference = getNonMatchedLines(index); if (difference.isEmpty()) { return true; } else { return false; } } private void newLine(final List<LineString> lines, final List<Point> coordinates) { if (!coordinates.isEmpty()) { final LineString line = this.geometryFactory.lineString(coordinates); lines.add(line); } } @Override public List<Edge<LineSegmentMatch>> splitEdge(final Edge<LineSegmentMatch> edge, final Point point) { final LineSegmentMatch lineSegmentMatch = edge.getObject(); final LineSegment segment = lineSegmentMatch.getSegment(); final Edge<LineSegmentMatch> edge1 = add(segment.getPoint(0), point); final LineSegmentMatch lineSegmentMatch1 = edge1.getObject(); final Edge<LineSegmentMatch> edge2 = add(point, segment.getPoint(1)); final LineSegmentMatch lineSegmentMatch2 = edge2.getObject(); for (int i = 0; i < lineSegmentMatch.getSegmentCount(); i++) { if (lineSegmentMatch.hasSegment(i)) { final LineSegment realSegment = lineSegmentMatch.getSegment(i); final Point projectedCoordinate = realSegment.project(point); final Point startCoordinate = realSegment.getPoint(0); final LineSegment segment1 = new LineSegmentDoubleGF(this.geometryFactory, startCoordinate, projectedCoordinate); lineSegmentMatch1.addSegment(segment1, i); final Point endCoordinate = realSegment.getPoint(1); final LineSegment segment2 = new LineSegmentDoubleGF(this.geometryFactory, projectedCoordinate, endCoordinate); lineSegmentMatch2.addSegment(segment2, i); } } remove(edge); return Arrays.asList(edge1, edge2); } private List<Edge<LineSegmentMatch>> splitEdge(final Edge<LineSegmentMatch> edge, final Set<Node<LineSegmentMatch>> splitNodes) { final Node<LineSegmentMatch> fromNode = edge.getFromNode(); final Node<LineSegmentMatch> toNode = edge.getToNode(); splitNodes.remove(fromNode); splitNodes.remove(toNode); if (splitNodes.isEmpty()) { return Collections.singletonList(edge); } else { final List<Edge<LineSegmentMatch>> edges = new ArrayList<>(); final LineSegmentMatch lineSegmentMatch = edge.getObject(); Node<LineSegmentMatch> previousNode = fromNode; for (final Node<LineSegmentMatch> currentNode : splitNodes) { edges.add(add(lineSegmentMatch, previousNode, currentNode)); previousNode = currentNode; } edges.add(add(lineSegmentMatch, previousNode, toNode)); remove(edge); return edges; } } }