package com.revolsys.geometry.graph; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Predicate; import com.revolsys.collection.map.LinkedHashMapEx; import com.revolsys.collection.map.MapEx; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.End; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.coordinates.list.CoordinatesListUtil; import com.revolsys.geometry.util.LineStringUtil; import com.revolsys.properties.ObjectWithProperties; import com.revolsys.util.Property; public class Edge<T> implements LineString, ObjectWithProperties, Externalizable { public static <T> void addEdgeToEdgesByLine(final Map<LineString, Set<Edge<T>>> lineEdgeMap, final Edge<T> edge) { final LineString line = edge.getLine(); for (final Entry<LineString, Set<Edge<T>>> entry : lineEdgeMap.entrySet()) { final LineString keyLine = entry.getKey(); if (LineStringUtil.equalsIgnoreDirection2d(line, keyLine)) { final Set<Edge<T>> edges = entry.getValue(); edges.add(edge); return; } } final HashSet<Edge<T>> edges = new HashSet<>(); edges.add(edge); lineEdgeMap.put(line, edges); } public static <T> void addEdgeToEdgesByLine(final Node<T> node, final Map<LineString, Set<Edge<T>>> lineEdgeMap, final Edge<T> edge) { LineString line = edge.getLine(); for (final Entry<LineString, Set<Edge<T>>> entry : lineEdgeMap.entrySet()) { final LineString keyLine = entry.getKey(); if (LineStringUtil.equalsIgnoreDirection2d(line, keyLine)) { final Set<Edge<T>> edges = entry.getValue(); edges.add(edge); return; } } final HashSet<Edge<T>> edges = new HashSet<>(); if (edge.getEnd(node).isFrom()) { line = line.reverse(); } edges.add(edge); lineEdgeMap.put(line, edges); } public static <T> Set<Edge<T>> getEdges(final Collection<Edge<T>> edges, final LineString line) { final Set<Edge<T>> newEdges = new LinkedHashSet<>(); for (final Edge<T> edge : edges) { if (LineStringUtil.equalsIgnoreDirection2d(line, edge.getLine())) { newEdges.add(edge); } } return newEdges; } public static <T> List<Edge<T>> getEdges(final List<Edge<T>> edges, final Predicate<Edge<T>> filter) { final List<Edge<T>> filteredEdges = new ArrayList<>(); for (final Edge<T> edge : edges) { if (filter.test(edge)) { filteredEdges.add(edge); } } return filteredEdges; } public static <T> Set<Edge<T>> getEdges(final Map<LineString, Set<Edge<T>>> lineEdgeMap, final LineString line) { for (final Entry<LineString, Set<Edge<T>>> entry : lineEdgeMap.entrySet()) { final LineString keyLine = entry.getKey(); if (LineStringUtil.equalsIgnoreDirection2d(line, keyLine)) { final Set<Edge<T>> edges = entry.getValue(); return edges; } } return null; } public static <T> Map<LineString, Set<Edge<T>>> getEdgesByLine(final List<Edge<T>> edges) { final Map<LineString, Set<Edge<T>>> edgesByLine = new HashMap<>(); for (final Edge<T> edge : edges) { addEdgeToEdgesByLine(edgesByLine, edge); } return edgesByLine; } public static <T> Map<LineString, Set<Edge<T>>> getEdgesByLine(final Node<T> node, final List<Edge<T>> edges) { final Map<LineString, Set<Edge<T>>> edgesByLine = new HashMap<>(); for (final Edge<T> edge : edges) { addEdgeToEdgesByLine(node, edgesByLine, edge); } return edgesByLine; } public static <T> List<Edge<T>> getEdgesMatchingObjectFilter(final List<Edge<T>> edges, final Predicate<T> filter) { final List<Edge<T>> filteredEdges = new ArrayList<>(); for (final Edge<T> edge : edges) { if (!edge.isRemoved()) { final T object = edge.getObject(); if (filter.test(object)) { filteredEdges.add(edge); } } } return filteredEdges; } /** * Get the list of objects from the collection of edges. * * @param <T> The type of the objects. * @param edges The collection of edges. * @return The collection of edges. */ public static <T> List<T> getObjects(final Collection<Edge<T>> edges) { final List<T> objects = new ArrayList<>(); for (final Edge<T> edge : edges) { final T object = edge.getObject(); objects.add(object); } return objects; } /** * Get the map of type name to list of edges. * * @param <T> The type of object stored in the edge. * @param edges The list of edges. * @return The map of type name to list of edges. */ public static <T> Map<String, List<Edge<T>>> getTypeNameEdgesMap(final List<Edge<T>> edges) { final Map<String, List<Edge<T>>> edgesByTypeName = new HashMap<>(); for (final Edge<T> edge : edges) { final String typePath = edge.getTypeName(); List<Edge<T>> typeEdges = edgesByTypeName.get(typePath); if (typeEdges == null) { typeEdges = new ArrayList<>(); edgesByTypeName.put(typePath, typeEdges); } typeEdges.add(edge); } return edgesByTypeName; } public static <T> boolean hasEdgeMatchingObjectFilter(final List<Edge<T>> edges, final Predicate<T> filter) { for (final Edge<T> edge : edges) { final T object = edge.getObject(); if (filter.test(object)) { return true; } } return false; } public static <T> void remove(final Collection<Edge<T>> edges) { for (final Edge<T> edge : edges) { edge.remove(); } } public static <T> void setEdgesAttribute(final List<Edge<T>> edges, final String fieldName, final Object value) { for (final Edge<T> edge : edges) { edge.setProperty(fieldName, value); } } private int fromNodeId; /** The graph the edge is part of. */ private Graph<T> graph; private int id; private int toNodeId; public Edge() { } public Edge(final int id, final Graph<T> graph, final Node<T> fromNode, final Node<T> toNode) { this.id = id; this.graph = graph; this.fromNodeId = fromNode.getId(); this.toNodeId = toNode.getId(); fromNode.addOutEdge(this); toNode.addInEdge(this); } @Override public void clearProperties() { if (this.graph != null) { final Map<Integer, MapEx> propertiesById = this.graph.getEdgePropertiesById(); propertiesById.remove(this.id); } } @SuppressWarnings("unchecked") @Override public Edge<T> clone() { try { return (Edge<T>)super.clone(); } catch (final CloneNotSupportedException e) { return null; } } @Override public int compareTo(final Object other) { if (other instanceof Edge<?>) { final Edge<?> edge = (Edge<?>)other; if (this == edge) { return 0; } else if (isRemoved()) { return 1; } else if (edge.isRemoved()) { return -1; } else { final Node<?> otherFromNode = edge.getFromNode(); final Node<?> fromNode = getFromNode(); final int fromCompare = fromNode.compareTo(otherFromNode); if (fromCompare == 0) { final Node<?> otherToNode = edge.getToNode(); final Node<T> toNode = getToNode(); final int toCompare = toNode.compareTo(otherToNode); if (toCompare == 0) { final double otherLength = edge.getLength(); final double length = getLength(); final int lengthCompare = Double.compare(length, otherLength); if (lengthCompare == 0) { final String name = toSuperString(); final String otherName = edge.toSuperString(); final int nameCompare = name.compareTo(otherName); if (nameCompare == 0) { return ((Integer)this.id).compareTo(edge.id); } else { return nameCompare; } } return lengthCompare; } else { return toCompare; } } else { return fromCompare; } } } else if (other instanceof LineString) { final LineString otherLine = (LineString)other; final LineString line = getLine(); return line.compareTo(otherLine); } return 1; } @Override protected void finalize() throws Throwable { if (this.graph != null) { this.graph.evict(this); } super.finalize(); } public double getAngle(final Node<T> node) { if (node.getGraph() == this.graph) { final int nodeId = node.getId(); if (nodeId == this.fromNodeId) { return getFromAngle(); } else if (nodeId == this.toNodeId) { return getToAngle(); } } return Double.NaN; } @Override public int getAxisCount() { return this.graph.getAxisCount(); } @Override public BoundingBox getBoundingBox() { return getLine().getBoundingBox(); } public Collection<Node<T>> getCommonNodes(final Edge<T> edge) { final Collection<Node<T>> nodes1 = getNodes(); final Collection<Node<T>> nodes2 = edge.getNodes(); nodes1.retainAll(nodes2); return nodes1; } @Override public double getCoordinate(final int vertexIndex, final int axisIndex) { final LineString line = getLine(); return line.getCoordinate(vertexIndex, axisIndex); } @Override public double[] getCoordinates() { final LineString line = getLine(); return line.getCoordinates(); } public List<Edge<T>> getEdgesToNextJunctionNode(final Node<T> node) { final List<Edge<T>> edges = new ArrayList<>(); edges.add(this); Edge<T> currentEdge = this; Node<T> currentNode = getOppositeNode(node); while (currentNode.getDegree() == 2) { currentEdge = currentNode.getNextEdge(currentEdge); final Node<T> nextNode = currentEdge.getOppositeNode(currentNode); if (nextNode != currentNode) { currentNode = nextNode; edges.add(currentEdge); } else { return edges; } } return edges; } /** * Get the direction of the edge from the specified node. If the node is at * the start of the edge then return {@link End#FROM}. If the node is at the end of the * edge return {@link End#TO}. Otherwise return null if it's not at the node. * * @param node The node to test the direction from. * @return True if the node is at the start of the edge. */ public End getEnd(final Point point) { if (Property.hasValue(point)) { if (point.equals(getFromNode())) { return End.FROM; } else if (point.equals(getToNode())) { return End.TO; } } return null; } public double getFromAngle() { final LineString line = getLine(); final LineString points = line; return CoordinatesListUtil.angleToNext(points, 0); } public Node<T> getFromNode() { return this.graph.getNode(this.fromNodeId); } public Graph<T> getGraph() { return this.graph; } public int getId() { return this.id; } @Override public double getLength() { final LineString line = getLine(); return line.getLength(); } public LineString getLine() { return this.graph.getEdgeLine(this.id); } public Node<T> getNextJunctionNode(final Node<T> node) { Edge<T> currentEdge = this; Node<T> currentNode = getOppositeNode(node); while (currentNode.getDegree() == 2) { currentEdge = currentNode.getNextEdge(currentEdge); final Node<T> nextNode = currentEdge.getOppositeNode(currentNode); if (nextNode != currentNode) { currentNode = nextNode; } else { return currentNode; } } return currentNode; } public Node<T> getNode(final boolean from) { if (from) { return getFromNode(); } else { return getToNode(); } } public Collection<Node<T>> getNodes() { final LinkedHashSet<Node<T>> nodes = new LinkedHashSet<>(); nodes.add(getFromNode()); nodes.add(getToNode()); return nodes; } public T getObject() { return this.graph.getEdgeObject(this.id); } public Node<T> getOppositeNode(final Node<T> node) { if (node.getGraph() == node.getGraph()) { final int nodeId = node.getId(); if (this.fromNodeId == nodeId) { return getToNode(); } else if (this.toNodeId == nodeId) { return getFromNode(); } } return null; } @Override public MapEx getProperties() { if (this.graph == null) { return MapEx.EMPTY; } else { final Map<Integer, MapEx> propertiesById = this.graph.getEdgePropertiesById(); MapEx properties = propertiesById.get(this.id); if (properties == null) { properties = new LinkedHashMapEx(); propertiesById.put(this.id, properties); } return properties; } } @Override public <V> V getProperty(final String name) { if (this.graph != null) { final Map<Integer, MapEx> propertiesById = this.graph.getEdgePropertiesById(); final Map<String, Object> properties = propertiesById.get(this.id); return ObjectWithProperties.getProperty(this, properties, name); } return null; } public double getToAngle() { final LineString line = getLine(); if (line == null) { return Double.NaN; } else { final LineString points = line; return CoordinatesListUtil.angleToPrevious(points, points.getVertexCount() - 1); } } public Node<T> getToNode() { return this.graph.getNode(this.toNodeId); } public String getTypeName() { return this.graph.getTypeName(this); } @Override public int getVertexCount() { final LineString line = getLine(); return line.getVertexCount(); } @Override public int hashCode() { return this.id; } public boolean hasNode(final Node<T> node) { if (node != null && node.getGraph() == this.graph) { final int nodeId = node.getId(); if (this.fromNodeId == nodeId) { return true; } else if (this.toNodeId == nodeId) { return true; } } return false; } public boolean hasNode(final Point point) { final Node<T> node = this.graph.findNode(point); if (node == null) { return false; } else { return hasNode(node); } } public boolean isRemoved() { return this.graph == null; } @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { final int graphId = in.readInt(); this.graph = Graph.getGraph(graphId); this.id = in.readInt(); this.fromNodeId = in.readInt(); this.toNodeId = in.readInt(); } public void remove() { if (this.graph != null) { this.graph.remove(this); } } void removeInternal() { final Node<T> fromNode = this.graph.getNode(this.fromNodeId); if (fromNode != null) { fromNode.remove(this); } final Node<T> toNode = this.graph.getNode(this.toNodeId); if (toNode != null) { toNode.remove(this); } this.graph = null; } public List<Edge<T>> replace(final LineString... lines) { return replace(Arrays.asList(lines)); } public List<Edge<T>> replace(final List<LineString> lines) { if (isRemoved()) { return Collections.emptyList(); } else { final Graph<T> graph = getGraph(); return graph.replaceEdge(this, lines); } } public <V extends Point> List<Edge<T>> splitEdge(final Collection<V> splitPoints) { return this.graph.splitEdge(this, splitPoints); } public <V extends Point> List<Edge<T>> splitEdge(final Collection<V> points, final double maxDistance) { return this.graph.splitEdge(this, points, maxDistance); } public List<Edge<T>> splitEdge(final List<Point> points) { return this.graph.splitEdge(this, points); } public List<Edge<T>> splitEdge(final Point... points) { return splitEdge(Arrays.asList(points)); } public List<Edge<T>> splitEdge(final Point point) { return this.graph.splitEdge(this, point); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(' '); if (isRemoved()) { return "Removed Edge"; } else { final String typeName = getTypeName(); if (typeName != null) { sb.append(typeName.toString()); } sb.append(this.id); sb.append('{'); sb.append(this.fromNodeId); sb.append(','); sb.append(this.toNodeId); sb.append("}\tLINESTRING("); final Node<T> fromNode = getFromNode(); sb.append(fromNode.getX()); sb.append(" "); sb.append(fromNode.getY()); sb.append(","); final Node<T> toNode = getToNode(); sb.append(toNode.getX()); sb.append(" "); sb.append(toNode.getY()); sb.append(")\n"); sb.append(getObject()); } return sb.toString(); } private String toSuperString() { return super.toString(); } public boolean touches(final Edge<T> edge) { final Collection<Node<T>> nodes1 = getCommonNodes(edge); return !nodes1.isEmpty(); } @Override public void writeExternal(final ObjectOutput out) throws IOException { final int graphId = this.graph.getId(); out.writeInt(graphId); out.writeInt(this.id); out.writeInt(this.fromNodeId); out.writeInt(this.toNodeId); } }