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.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; 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.graph.attribute.NodeProperties; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.properties.ObjectPropertyProxy; import com.revolsys.properties.ObjectWithProperties; public class Node<T> extends PointDoubleXY implements ObjectWithProperties, Externalizable { public static <V> Predicate<Node<V>> filterDegree(final int degree) { return (node) -> { return node.getDegree() == degree; }; } public static <V> int getEdgeIndex(final List<Edge<V>> edges, final Edge<V> edge) { return edges.indexOf(edge); } public static <T> Set<Edge<T>> getEdgesBetween(final Node<T> node0, final Node<T> node1) { final Set<Edge<T>> commonEdges = new HashSet<>(); if (node1 == null) { return commonEdges; } else if (node0 == node1) { for (final Edge<T> edge : node0.getEdges()) { if (edge.getFromNode() == edge.getToNode()) { commonEdges.add(edge); } } } else { final Collection<Edge<T>> edges0 = node0.getEdges(); commonEdges.addAll(edges0); final Collection<Edge<T>> edges1 = node1.getEdges(); commonEdges.retainAll(edges1); } return commonEdges; } public static <T> Collection<Edge<T>> getEdgesBetween(final String typePath, final Node<T> node0, final Node<T> node1) { final Collection<Edge<T>> edges = getEdgesBetween(node0, node1); for (final Iterator<Edge<T>> edgeIter = edges.iterator(); edgeIter.hasNext();) { final Edge<T> edge = edgeIter.next(); if (!edge.getTypeName().equals(typePath)) { edgeIter.remove(); } } return edges; } public static <V> Edge<V> getNextEdge(final List<Edge<V>> edges, final Edge<V> edge) { final int index = getEdgeIndex(edges, edge); final int nextIndex = (index + 1) % edges.size(); return edges.get(nextIndex); } public static <T> boolean hasEdgesBetween(final String typePath, final Node<T> node0, final Node<T> node1) { if (node1 == null) { return false; } if (node0 == node1) { for (final Edge<T> edge : node0.getEdges()) { if (edge.getTypeName().equals(typePath)) { if (edge.getFromNode() == edge.getToNode()) { return true; } } } } else { for (final Edge<T> edge : node0.getEdges()) { if (edge.getTypeName().equals(typePath)) { if (edge.hasNode(node1)) { return true; } } } for (final Edge<T> edge : node1.getEdges()) { if (edge.getTypeName().equals(typePath)) { if (edge.hasNode(node0)) { return true; } } } } return false; } private Graph<T> graph; private int id; private int[] inEdgeIds = new int[0]; private int[] outEdgeIds = new int[0]; public Node() { } protected Node(final int nodeId, final Graph<T> graph, final double x, final double y) { super(x, y); this.id = nodeId; this.graph = graph; } private int[] addEdge(final int[] oldEdgeIds, final Edge<T> edge) { final Graph<T> graph = getGraph(); final List<Edge<T>> edges = graph.getEdges(oldEdgeIds); edges.add(edge); final EdgeToAngleComparator<T> comparator = EdgeToAngleComparator.get(); Collections.sort(edges, comparator); return graph.getEdgeIds(edges); } protected void addInEdge(final Edge<T> edge) { this.inEdgeIds = addEdge(this.inEdgeIds, edge); updateAttributes(); } protected void addOutEdge(final Edge<T> edge) { this.outEdgeIds = addEdge(this.outEdgeIds, edge); updateAttributes(); } @Override public void clearProperties() { if (this.graph != null) { final Map<Integer, MapEx> propertiesById = this.graph.getNodePropertiesById(); propertiesById.remove(this.id); } } public int compareTo(final Node<T> node) { return compareTo((Point)node); } public boolean containsEdge(final Edge<T> edge) { if (edge != null) { final Graph<T> graph = getGraph(); if (graph == edge.getGraph()) { for (final int edgeId : this.inEdgeIds) { if (graph.getEdge(edgeId) == edge) { return true; } } for (final int edgeId : this.outEdgeIds) { if (graph.getEdge(edgeId) == edge) { return true; } } } } return false; } public boolean equalsCoordinate(final double x, final double y) { return this.getX() == x && this.getY() == y; } @Override protected void finalize() throws Throwable { if (this.graph != null) { this.graph.evict(this); } super.finalize(); } public Point get3dCoordinates(final String typePath) { if (!isRemoved()) { final List<Edge<T>> edges = NodeProperties.getEdgesByType(this, typePath); if (!edges.isEmpty()) { Point coordinates = null; for (final Edge<T> edge : edges) { final LineString line = edge.getLine(); final LineString points = line; Point point = null; if (edge.getFromNode() == this) { point = points.getPoint(0); } else if (edge.getToNode() == this) { point = points.getPoint(points.getVertexCount() - 1); } if (point != null) { final double z = point.getZ(); if (z == 0 || java.lang.Double.isNaN(z)) { coordinates = point; } else { return point; } } } return coordinates; } } return this; } @Override public double getCoordinate(final int index) { switch (index) { case 0: return this.getX(); case 1: return this.getY(); default: return java.lang.Double.NaN; } } public int getDegree() { return this.inEdgeIds.length + this.outEdgeIds.length; } public Edge<T> getEdge(final int i) { final List<Edge<T>> edges = getEdges(); return edges.get(i); } public int getEdgeCount() { return getDegree(); } public int getEdgeIndex(final Edge<T> edge) { final List<Edge<T>> edges = getEdges(); return getEdgeIndex(edges, edge); } public List<Edge<T>> getEdges() { final ArrayList<Edge<T>> edges = new ArrayList<>(); final List<Edge<T>> inEdges = getInEdges(); final Iterator<Edge<T>> inIterator = inEdges.iterator(); final List<Edge<T>> outEdges = getOutEdges(); final Iterator<Edge<T>> outIterator = outEdges.iterator(); if (!inIterator.hasNext()) { edges.addAll(outEdges); } else if (!outIterator.hasNext()) { edges.addAll(inEdges); } else { Edge<T> inEdge = inIterator.next(); final double inAngle = inEdge.getToAngle(); Edge<T> outEdge = outIterator.next(); final double outAngle = outEdge.getFromAngle(); do { boolean nextIn = false; boolean nextOut = false; if (inEdge == null) { edges.add(outEdge); nextOut = true; } else if (outEdge == null) { edges.add(inEdge); nextIn = true; } else { if (outAngle <= inAngle) { edges.add(outEdge); nextOut = true; } else { edges.add(inEdge); nextIn = true; } } if (nextOut) { if (outIterator.hasNext()) { outEdge = outIterator.next(); } else { outEdge = null; } } if (nextIn) { if (inIterator.hasNext()) { inEdge = inIterator.next(); } else { inEdge = null; } } } while (inEdge != null || outEdge != null); } return edges; } public Set<Edge<T>> getEdgesTo(final Node<T> node) { return getEdgesBetween(this, node); } /** * Get all the edges from a node which do not have an attribute with the * specified name. * * @param node The node to get the edges for. * @param fieldName The attribute name. * @return The list of edges without the attribute. */ public List<Edge<T>> getEdgesWithoutAttribute(final String fieldName) { final List<Edge<T>> edges = new ArrayList<>(); for (final Edge<T> edge : getEdges()) { if (edge.getProperty(fieldName) == null) { edges.add(edge); } } return edges; } @Override public GeometryFactory getGeometryFactory() { return this.graph.getGeometryFactory(); } public Graph<T> getGraph() { return this.graph; } public int getId() { return this.id; } public int getInEdgeIndex(final Edge<T> edge) { return getInEdges().indexOf(edge); } public List<Edge<T>> getInEdges() { final Graph<T> graph = getGraph(); return graph.getEdges(this.inEdgeIds); } public Edge<T> getNextEdge(final Edge<T> edge) { final List<Edge<T>> edges = getEdges(); return getNextEdge(edges, edge); } public Edge<T> getNextInEdge(final Edge<T> edge) { final int index = getInEdgeIndex(edge); final int nextIndex = (index + 1) % this.inEdgeIds.length; final Graph<T> graph = getGraph(); return graph.getEdge(this.inEdgeIds[nextIndex]); } public Edge<T> getNextOutEdge(final Edge<T> edge) { final int index = getOutEdgeIndex(edge); final int nextIndex = (index + 1) % this.outEdgeIds.length; final Graph<T> graph = getGraph(); return graph.getEdge(this.outEdgeIds[nextIndex]); } public int getOutEdgeIndex(final Edge<T> edge) { return getOutEdges().indexOf(edge); } public List<Edge<T>> getOutEdges() { final Graph<T> graph = getGraph(); return graph.getEdges(this.outEdgeIds); } public List<Edge<T>> getOutEdgesTo(final Node<T> node) { final List<Edge<T>> edges = new ArrayList<>(); for (final Edge<T> edge : getOutEdges()) { if (edge.getToNode() == node) { edges.add(edge); } } return edges; } @Override public Point getPoint() { final Graph<T> graph = getGraph(); final GeometryFactory geometryFactory = graph.getGeometryFactory(); return geometryFactory.point(this); } @Override public MapEx getProperties() { if (this.graph == null) { return MapEx.EMPTY; } else { final Map<Integer, MapEx> propertiesById = this.graph.getNodePropertiesById(); 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.getNodePropertiesById(); final Map<String, Object> properties = propertiesById.get(this.id); return ObjectWithProperties.getProperty(this, properties, name); } return null; } public boolean ha(final String name) { return getProperties().containsKey(name); } public boolean hasEdge(final Edge<T> edge) { if (edge.getGraph() == getGraph()) { final int edgeId = edge.getId(); for (final int inEdgeId : this.inEdgeIds) { if (inEdgeId == edgeId) { return true; } } for (final int inEdgeId : this.outEdgeIds) { if (inEdgeId == edgeId) { return true; } } } return false; } public boolean hasEdges() { if (isRemoved()) { return false; } else { return !getEdges().isEmpty(); } } public boolean hasEdgeTo(final Node<T> node) { if (node == this) { return false; } else { for (final Edge<T> edge : getEdges()) { if (edge.hasNode(node)) { return true; } } } return false; } @Override public int hashCode() { return this.id; } public boolean isRemoved() { return this.graph == null; } public void moveNode(final double x, final double y) { moveNode(new PointDoubleXY(x, y)); } public boolean moveNode(final Point point) { if (isRemoved()) { return false; } else { final Node<T> newNode = this.graph.getNode(point); if (equals(newNode)) { return false; } else { this.graph.nodeMoved(this, newNode); final Set<Edge<T>> edges = new HashSet<>(getInEdges()); edges.addAll(getOutEdges()); for (final Edge<T> edge : edges) { if (!edge.isRemoved()) { final LineString line = edge.getLine(); LineString newLine; final int vertexCount = line.getVertexCount(); if (edge.getEnd(this).isFrom()) { if (line.equalsVertex(vertexCount - 1, getX(), getY())) { // LOOPS newLine = line.subLine(newNode, 1, vertexCount - 2, newNode); } else { newLine = line.subLine(newNode, 1, vertexCount - 1, null); } } else { newLine = line.subLine(null, 0, vertexCount - 1, newNode); } this.graph.replaceEdge(edge, newLine); if (!edge.isRemoved()) { throw new RuntimeException("Not node Removed"); } } } if (!isRemoved()) { throw new RuntimeException("Not node Removed"); } return true; } } } @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.inEdgeIds = (int[])in.readObject(); this.outEdgeIds = (int[])in.readObject(); setX(in.readDouble()); setY(in.readDouble()); } void remove() { this.graph = null; this.inEdgeIds = null; this.outEdgeIds = null; } public void remove(final Edge<T> edge) { if (!isRemoved()) { this.outEdgeIds = removeEdge(this.outEdgeIds, edge); this.inEdgeIds = removeEdge(this.inEdgeIds, edge); if (this.inEdgeIds.length == 0 && this.outEdgeIds.length == 0) { this.graph.remove(this); } else { updateAttributes(); } } } public int[] removeEdge(final int[] oldEdgeIds, final Edge<T> edge) { final Graph<T> graph = getGraph(); final List<Edge<T>> edges = graph.getEdges(oldEdgeIds); edges.remove(edge); return graph.getEdgeIds(edges); } @Override public String toString() { final StringBuilder sb = new StringBuilder("Node: "); sb.append(' '); if (isRemoved()) { sb.insert(0, "Removed"); } else { sb.append(this.id); sb.append('{'); sb.append(Arrays.toString(this.inEdgeIds)); sb.append(','); sb.append(Arrays.toString(this.outEdgeIds)); sb.append("}\tPOINT("); sb.append(getX()); sb.append(" "); sb.append(getY()); sb.append(")"); } return sb.toString(); } private void updateAttributes() { for (final Object attribute : getProperties().values()) { if (attribute instanceof ObjectPropertyProxy) { @SuppressWarnings("unchecked") final ObjectPropertyProxy<Object, Node<T>> proxy = (ObjectPropertyProxy<Object, Node<T>>)attribute; proxy.clearValue(); } } } @Override public void writeExternal(final ObjectOutput out) throws IOException { final int graphId = this.graph.getId(); out.writeInt(graphId); out.writeInt(this.id); out.writeObject(this.inEdgeIds); out.writeObject(this.outEdgeIds); out.writeDouble(this.getX()); out.writeDouble(this.getY()); } }