package com.revolsys.geometry.graph;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.PreDestroy;
import com.revolsys.collection.bplus.BPlusTreeMap;
import com.revolsys.collection.map.IntHashMap;
import com.revolsys.collection.map.MapEx;
import com.revolsys.comparator.ComparatorProxy;
import com.revolsys.geometry.graph.attribute.NodeProperties;
import com.revolsys.geometry.graph.comparator.NodeDistanceComparator;
import com.revolsys.geometry.graph.event.EdgeEvent;
import com.revolsys.geometry.graph.event.EdgeEventListener;
import com.revolsys.geometry.graph.event.EdgeEventListenerList;
import com.revolsys.geometry.graph.event.NodeEvent;
import com.revolsys.geometry.graph.event.NodeEventListener;
import com.revolsys.geometry.graph.event.NodeEventListenerList;
import com.revolsys.geometry.graph.filter.IsPointOnLineEdgeFilter;
import com.revolsys.geometry.graph.visitor.NodeWithinBoundingBoxVisitor;
import com.revolsys.geometry.index.IdObjectIndex;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.BoundingBoxProxy;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.GeometryFactoryProxy;
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.impl.LineStringDouble;
import com.revolsys.geometry.model.impl.PointDoubleXY;
import com.revolsys.geometry.model.impl.PointDoubleXYZ;
import com.revolsys.io.page.PageValueManager;
import com.revolsys.io.page.SerializablePageValueManager;
import com.revolsys.predicate.PredicateProxy;
import com.revolsys.predicate.Predicates;
import com.revolsys.record.Record;
import com.revolsys.util.ExitLoopException;
import com.revolsys.visitor.CreateListVisitor;
public class Graph<T> implements GeometryFactoryProxy {
private static final AtomicInteger GRAPH_IDS = new AtomicInteger();
private static Map<Integer, WeakReference<Graph<?>>> graphs = new HashMap<>();
@SuppressWarnings("unchecked")
public static <V> Graph<V> getGraph(final int id) {
final WeakReference<Graph<?>> reference = graphs.get(id);
if (reference == null) {
return null;
} else {
final Graph<?> graph = reference.get();
if (graph == null) {
graphs.remove(id);
return null;
} else {
return (Graph<V>)graph;
}
}
}
private final Map<Edge<T>, Integer> edgeIds = new TreeMap<>();
private IdObjectIndex<Edge<T>> edgeIndex;
private Map<Integer, LineString> edgeLinesById = new IntHashMap<>();
private final EdgeEventListenerList<T> edgeListeners = new EdgeEventListenerList<>();
private Map<Integer, T> edgeObjectsById = new IntHashMap<>();
private Map<Integer, MapEx> edgePropertiesById = new IntHashMap<>();
private Map<Integer, Edge<T>> edgesById = new IntHashMap<>();
private GeometryFactory geometryFactory = GeometryFactory.DEFAULT_3D;
private final int id = GRAPH_IDS.incrementAndGet();
private boolean inMemory = true;
private int maxEdgesInMemory = Integer.MAX_VALUE;
private int nextEdgeId;
private int nextNodeId;
private IdObjectIndex<Node<T>> nodeIndex;
private final NodeEventListenerList<T> nodeListeners = new NodeEventListenerList<>();
private Map<Integer, MapEx> nodePropertiesById = new IntHashMap<>();
private Map<Integer, Node<T>> nodesById = new IntHashMap<>();
private Map<Point, Integer> nodesIdsByPoint = new TreeMap<>();
private GeometryFactory precisionModel = GeometryFactory.DEFAULT_3D;
public Graph() {
this(true);
}
protected Graph(final boolean storeLines) {
graphs.put(this.id, new WeakReference<Graph<?>>(this));
if (!storeLines) {
this.edgeLinesById = null;
}
}
public void add(final NodeEventListener<T> listener) {
this.nodeListeners.add(listener);
}
protected Edge<T> addEdge(final T object, final double fromX, final double fromY,
final double toX, final double toY) {
return addEdge(object, null, fromX, fromY, toX, toY);
}
public Edge<T> addEdge(final T object, final LineString line) {
final double fromX = line.getX(0);
final double fromY = line.getY(0);
final int lastVertexIndex = line.getVertexCount() - 1;
final double toX = line.getX(lastVertexIndex);
final double toY = line.getY(lastVertexIndex);
return addEdge(object, line, fromX, fromY, toX, toY);
}
/**
* Actually add the edge.
*
* @param object
* @param line
* @param from
* @param to
* @return
*/
protected Edge<T> addEdge(final T object, final LineString line, final double fromX,
final double fromY, final double toX, final double toY) {
if (this.inMemory && getEdgeCount() >= this.maxEdgesInMemory) {
this.edgePropertiesById = BPlusTreeMap.newIntSeralizableTempDisk(this.edgePropertiesById);
// TODO edgIds
// TODO edgeIndex
this.edgeLinesById = BPlusTreeMap.newIntSeralizableTempDisk(this.edgeLinesById);
this.edgeObjectsById = BPlusTreeMap.newIntSeralizableTempDisk(this.edgeObjectsById);
this.edgesById = BPlusTreeMap.newIntSeralizableTempDisk(this.edgesById);
// TODO nodeIndex
this.nodePropertiesById = BPlusTreeMap.newIntSeralizableTempDisk(this.nodePropertiesById);
this.nodesById = BPlusTreeMap.newIntSeralizableTempDisk(this.nodesById);
this.nodesIdsByPoint = BPlusTreeMap.newTempDisk(this.nodesIdsByPoint,
new SerializablePageValueManager<Point>(), PageValueManager.INT);
this.inMemory = false;
}
final Node<T> fromNode = getNode(fromX, fromY);
final Node<T> toNode = getNode(toX, toY);
final int edgeId = ++this.nextEdgeId;
final Edge<T> edge = new Edge<>(edgeId, this, fromNode, toNode);
if (this.edgeLinesById != null) {
this.edgeLinesById.put(edgeId, line);
}
this.edgeObjectsById.put(edgeId, object);
this.edgesById.put(edgeId, edge);
this.edgeIds.put(edge, edgeId);
if (this.edgeIndex != null) {
this.edgeIndex.add(edge);
}
this.edgeListeners.edgeEvent(edge, null, EdgeEvent.EDGE_ADDED, null);
return edge;
}
protected Edge<T> addEdge(final T object, final Point from, final Point to) {
final double fromX = from.getX();
final double fromY = from.getY();
final double toX = to.getX();
final double toY = to.getY();
return addEdge(object, null, fromX, fromY, toX, toY);
}
public void addEdgeListener(final EdgeEventListener<T> listener) {
this.edgeListeners.add(listener);
}
/**
* Clone the object, setting the line property to the new value.
*
* @param object The object to clone.
* @param line The line.
* @return The new object.
*/
@SuppressWarnings("unchecked")
protected T clone(final T object, final LineString line) {
if (object != null) {
try {
final Class<? extends Object> clazz = object.getClass();
final Method method = clazz.getMethod("clone", new Class[0]);
return (T)method.invoke(object, new Object[0]);
} catch (final Throwable e) {
throw new IllegalArgumentException("Cannot clone", e);
}
} else {
return null;
}
}
@PreDestroy
public void close() {
this.edgePropertiesById.clear();
this.edgeIds.clear();
// TODO edgeIndex
this.edgeLinesById.clear();
this.edgeObjectsById.clear();
this.edgesById.clear();
// TODO nodeIndex.clear();
this.nodePropertiesById.clear();
this.nodesIdsByPoint.clear();
}
public boolean contains(final Edge<T> edge) {
if (edge.getGraph() == this) {
final int id = edge.getId();
return this.edgesById.containsKey(id);
}
return false;
}
public void deleteEdges(final Predicate<Edge<T>> filter) {
forEachEdge(filter, (edge) -> remove(edge));
}
public Iterable<Edge<T>> edges() {
return getEdges();
}
protected void evict(final Edge<T> edge) {
// TODO
}
protected void evict(final Node<T> node) {
// TODO
}
/**
* Find the node by point coordinates returning the node if it exists, null
* otherwise.
*
* @param point The point coordinates to find the node for.
* @return The nod or null if not found.
*/
public Node<T> findNode(final Point point) {
final Integer nodeId = this.nodesIdsByPoint.get(point);
if (nodeId == null) {
return null;
} else {
return getNode(nodeId);
}
}
public List<Node<T>> findNodes(BoundingBox boundingBox) {
boundingBox = boundingBox.convert(getGeometryFactory());
return NodeWithinBoundingBoxVisitor.getNodes(this, boundingBox);
}
/**
* Find all the nodes <= the distance of the edge, that are "on" the line.
*
* @param edge The edge.
* @param distance The distance.
* @return The nodes
* @see IsPointOnLineEdgeFilter
*/
public List<Node<T>> findNodes(final Edge<T> edge, final double distance) {
final IsPointOnLineEdgeFilter<T> filter = new IsPointOnLineEdgeFilter<>(edge, distance);
final Node<T> fromNode = edge.getFromNode();
final Comparator<Node<T>> comparator = new NodeDistanceComparator<>(fromNode);
final BoundingBox boundingBox = filter.getEnvelope();
return getNodes(boundingBox, filter, comparator);
}
public List<Node<T>> findNodesOfDegree(final int degree) {
final List<Node<T>> nodesFound = new ArrayList<>();
for (final Node<T> node : getNodes()) {
if (node.getDegree() == degree) {
nodesFound.add(node);
}
}
return nodesFound;
}
public void forEachEdge(final BoundingBoxProxy boundingBoxProxy, final Consumer<Edge<T>> action) {
final IdObjectIndex<Edge<T>> edgeIndex = getEdgeIndex();
edgeIndex.forEach(boundingBoxProxy.getBoundingBox(), action);
}
public void forEachEdge(final BoundingBoxProxy boundingBox,
final Predicate<? super Edge<T>> filter, final Consumer<Edge<T>> visitor) {
final IdObjectIndex<Edge<T>> edgeIndex = getEdgeIndex();
edgeIndex.forEach(boundingBox.getBoundingBox(), filter, visitor);
}
public void forEachEdge(final Comparator<Edge<T>> comparator, final Consumer<Edge<T>> action) {
forEachEdge(null, action, comparator);
}
@SuppressWarnings("unchecked")
public void forEachEdge(final Consumer<Edge<T>> action) {
Predicate<Edge<T>> filter = null;
if (action instanceof PredicateProxy) {
filter = ((PredicateProxy<Edge<T>>)action).getPredicate();
}
Comparator<Edge<T>> comparator = null;
if (action instanceof ComparatorProxy) {
comparator = ((ComparatorProxy<Edge<T>>)action).getComparator();
}
forEachEdge(filter, action, comparator);
}
public void forEachEdge(final Predicate<Edge<T>> filter, final Consumer<Edge<T>> action) {
forEachEdge(filter, action, null);
}
public void forEachEdge(final Predicate<Edge<T>> filter, final Consumer<Edge<T>> action,
final Comparator<Edge<T>> comparator) {
final LinkedList<Edge<T>> edges = new LinkedList<>(getEdges(filter));
final EdgeEventListener<T> listener;
if (comparator == null) {
listener = (edgeEvent) -> {
final Edge<T> edge = edgeEvent.getEdge();
if (edgeEvent.isAddAction()) {
if (filter == null || filter.test(edge)) {
edges.addFirst(edge);
}
}
};
} else {
Collections.sort(edges, comparator);
listener = (edgeEvent) -> {
final Edge<T> edge = edgeEvent.getEdge();
final String eventAction = edgeEvent.getAction();
if (eventAction.equals(EdgeEvent.EDGE_ADDED)) {
if (filter == null || filter.test(edge)) {
edges.addFirst(edge);
}
if (comparator != null) {
Collections.sort(edges, comparator);
}
} else if (eventAction.equals(EdgeEvent.EDGE_REMOVED)) {
if (comparator != null) {
edges.remove(edge);
}
}
};
}
this.edgeListeners.add(listener);
try {
while (!edges.isEmpty()) {
final Edge<T> edge = edges.remove(0);
if (!edge.isRemoved()) {
action.accept(edge);
}
}
} catch (final ExitLoopException e) {
} finally {
this.edgeListeners.remove(listener);
}
}
public void forEachNode(final BoundingBoxProxy boundingBoxProxy, final Consumer<Node<T>> action) {
final IdObjectIndex<Node<T>> nodeIndex = getNodeIndex();
final BoundingBox boundingBox = boundingBoxProxy.getBoundingBox();
nodeIndex.forEach(boundingBox, action);
}
@SuppressWarnings("unchecked")
public void forEachNode(final Consumer<Node<T>> action) {
Predicate<Node<T>> filter = null;
if (action instanceof PredicateProxy) {
filter = ((PredicateProxy<Node<T>>)action).getPredicate();
}
Comparator<Node<T>> comparator = null;
if (action instanceof ComparatorProxy) {
comparator = ((ComparatorProxy<Node<T>>)action).getComparator();
}
forEachNode(action, filter, comparator);
}
public void forEachNode(final Consumer<Node<T>> action, final Comparator<Node<T>> comparator) {
forEachNode(action, null, comparator);
}
public void forEachNode(final Consumer<Node<T>> action, final Predicate<Node<T>> filter) {
forEachNode(action, filter, null);
}
// TODO make this work with cached nodes
public void forEachNode(final Consumer<Node<T>> action, final Predicate<Node<T>> filter,
final Comparator<Node<T>> comparator) {
final List<Node<T>> nodes = new LinkedList<>();
if (filter == null) {
nodes.addAll(getNodes());
} else {
for (final Node<T> node : getNodes()) {
if (filter.test(node)) {
nodes.add(node);
}
}
}
NodeEventListener<T> listener;
if (comparator == null) {
listener = (nodeEvent) -> {
if (nodeEvent.isAddAction()) {
final Node<T> node = nodeEvent.getNode();
if (filter == null || filter.test(node)) {
nodes.add(node);
}
}
};
} else {
Collections.sort(nodes, comparator);
listener = (nodeEvent) -> {
final Node<T> node = nodeEvent.getNode();
final String eventAction = nodeEvent.getAction();
if (eventAction.equals(NodeEvent.NODE_ADDED)) {
if (filter == null || filter.test(node)) {
nodes.add(node);
}
if (comparator != null) {
Collections.sort(nodes, comparator);
}
} else if (eventAction.equals(NodeEvent.NODE_REMOVED)) {
nodes.remove(node);
}
};
}
this.nodeListeners.add(listener);
try {
while (!nodes.isEmpty()) {
final Node<T> node = nodes.remove(0);
if (!node.isRemoved()) {
action.accept(node);
}
}
} catch (final ExitLoopException e) {
} finally {
this.nodeListeners.remove(listener);
}
}
public int getAxisCount() {
return this.geometryFactory.getAxisCount();
}
public double getClosestDistance(final Node<T> node, final double maxDistance) {
final List<Node<T>> nodes = getNodes(node, maxDistance);
double closestDistance = Double.MAX_VALUE;
for (final Node<T> matchNode : nodes) {
if (matchNode != node) {
final double distance = node.distancePoint(matchNode);
if (distance < closestDistance) {
closestDistance = distance;
}
}
}
return closestDistance;
}
public Edge<T> getEdge(final int edgeId) {
return this.edgesById.get(edgeId);
}
public Edge<T> getEdge(final T object, final LineString line) {
if (object != null) {
final Point fromPoint = line.getPoint(0);
final Node<T> fromNode = findNode(fromPoint);
if (fromNode != null) {
for (final Edge<T> edge : fromNode.getEdges()) {
if (edge.getObject() == object) {
return edge;
}
}
}
}
return null;
}
public int getEdgeCount() {
return this.edgesById.size();
}
public Collection<Integer> getEdgeIds() {
return this.edgesById.keySet();
}
public int[] getEdgeIds(final Collection<Edge<T>> edges) {
final int[] edgeIds = new int[edges.size()];
int i = 0;
for (final Edge<T> edge : edges) {
edgeIds[i] = edge.getId();
i++;
}
return edgeIds;
}
public IdObjectIndex<Edge<T>> getEdgeIndex() {
if (this.edgeIndex == null) {
this.edgeIndex = new EdgeQuadTree<>(this);
}
return this.edgeIndex;
}
public LineString getEdgeLine(final int edgeId) {
return this.edgeLinesById.get(edgeId);
}
public List<LineString> getEdgeLines() {
final List<Integer> edgeIds = new ArrayList<>(this.edgesById.keySet());
return new EdgeLineList(this, edgeIds);
}
public T getEdgeObject(final int edgeId) {
return this.edgeObjectsById.get(edgeId);
}
public List<T> getEdgeObjects() {
final List<T> objects = new ArrayList<>();
for (final Edge<T> edge : getEdges()) {
final T object = edge.getObject();
objects.add(object);
}
return objects;
}
protected Map<Integer, MapEx> getEdgePropertiesById() {
return this.edgePropertiesById;
}
public List<Edge<T>> getEdges() {
final List<Integer> edgeIds = new ArrayList<>(this.edgesById.keySet());
return new EdgeList<>(this, edgeIds);
}
public List<Edge<T>> getEdges(final BoundingBoxProxy boundingBoxProxy) {
return BoundingBox.newArraySorted(this::forEachEdge, boundingBoxProxy);
}
public List<Edge<T>> getEdges(final BoundingBoxProxy boundingBoxProxy,
final Predicate<? super Edge<T>> filter) {
return BoundingBox.newArraySorted(this::forEachEdge, boundingBoxProxy, filter);
}
public List<Edge<T>> getEdges(final BoundingBoxProxy boundingBoxProxy,
final Predicate<? super Edge<T>> filter, final Comparator<Edge<T>> comparator) {
return BoundingBox.newArraySorted(this::forEachEdge, boundingBoxProxy, filter, comparator);
}
public List<Edge<T>> getEdges(final Geometry geometry, final double maxDistance) {
if (geometry == null) {
return Collections.emptyList();
} else {
BoundingBox boundingBox = geometry.getBoundingBox();
boundingBox = boundingBox.expand(maxDistance);
final Predicate<Edge<T>> filter = (edge) -> {
final LineString line = edge.getLine();
final double distance = line.distance(geometry);
if (distance <= maxDistance) {
return true;
} else {
return false;
}
};
return BoundingBox.newArraySorted(this::forEachEdge, boundingBox, filter);
}
}
public List<Edge<T>> getEdges(final int... ids) {
final List<Edge<T>> edges = new ArrayList<>();
for (final int edgeId : ids) {
final Edge<T> edge = getEdge(edgeId);
if (edge != null) {
edges.add(edge);
}
}
return edges;
}
public List<Edge<T>> getEdges(final List<Integer> ids) {
final List<Edge<T>> edges = new ArrayList<>();
for (final int edgeId : ids) {
final Edge<T> edge = getEdge(edgeId);
if (edge != null) {
edges.add(edge);
}
}
return edges;
}
public List<Edge<T>> getEdges(final Point point, final double maxDistance) {
if (point == null) {
return Collections.emptyList();
} else {
BoundingBox boundingBox = point.getBoundingBox();
boundingBox = boundingBox.expand(maxDistance);
final double x = point.getX();
final double y = point.getY();
final Predicate<Edge<T>> filter = (edge) -> {
final LineString line = edge.getLine();
final double distance = line.distance(x, y);
if (distance <= maxDistance) {
return true;
} else {
return false;
}
};
return BoundingBox.newArraySorted(this::forEachEdge, boundingBox, filter);
}
}
public List<Edge<T>> getEdges(final Predicate<Edge<T>> filter) {
final List<Edge<T>> edges = new EdgeList<>(this);
for (final Integer edgeId : getEdgeIds()) {
final Edge<T> edge = getEdge(edgeId);
if (Predicates.matches(filter, edge)) {
edges.add(edge);
}
}
return edges;
}
public List<Edge<T>> getEdges(final Predicate<Edge<T>> filter,
final Comparator<Edge<T>> comparator) {
final List<Edge<T>> targetEdges = getEdges(filter);
if (comparator != null) {
Collections.sort(targetEdges, comparator);
}
return targetEdges;
}
@Override
public GeometryFactory getGeometryFactory() {
return this.geometryFactory;
}
public int getId() {
return this.id;
}
public int getMaxEdgesInMemory() {
return this.maxEdgesInMemory;
}
/**
* Get the node by point coordinates, creating one if it did not exist.
*
* @param point The point coordinates to get the node for.
* @return The node.
*/
public Node<T> getNode(final double x, final double y) {
final PointDoubleXY point = new PointDoubleXY(x, y);
Node<T> node = findNode(point);
if (node == null) {
final int nodeId = ++this.nextNodeId;
node = new Node<>(nodeId, this, x, y);
this.nodesIdsByPoint.put(point, nodeId);
this.nodesById.put(nodeId, node);
if (this.nodeIndex != null) {
this.nodeIndex.add(node);
}
this.nodeListeners.nodeEvent(node, null, null, NodeEvent.NODE_ADDED, null);
}
return node;
}
public Node<T> getNode(final int nodeId) {
return this.nodesById.get(nodeId);
}
/**
* Get the node by point coordinates, creating one if it did not exist.
*
* @param point The point coordinates to get the node for.
* @return The node.
*/
public Node<T> getNode(final Point point) {
Node<T> node = findNode(point);
if (node == null) {
final int nodeId = ++this.nextNodeId;
node = new Node<>(nodeId, this, point.getX(), point.getY());
this.nodesIdsByPoint.put(node.newPoint2D(), nodeId);
this.nodesById.put(nodeId, node);
if (this.nodeIndex != null) {
this.nodeIndex.add(node);
}
this.nodeListeners.nodeEvent(node, null, null, NodeEvent.NODE_ADDED, null);
}
return node;
}
public int getNodeCount() {
return this.nodesById.size();
}
public Collection<Integer> getNodeIds() {
return this.nodesById.keySet();
}
public IdObjectIndex<Node<T>> getNodeIndex() {
if (this.nodeIndex == null) {
this.nodeIndex = new NodeQuadTree<>(this);
}
return this.nodeIndex;
}
protected Map<Integer, MapEx> getNodePropertiesById() {
return this.nodePropertiesById;
}
public List<Node<T>> getNodes() {
final List<Integer> nodeIds = new ArrayList<>(this.nodesIdsByPoint.values());
return new NodeList<>(this, nodeIds);
}
public List<Node<T>> getNodes(final BoundingBox boundingBox, final Predicate<Node<T>> filter) {
return getNodes(boundingBox, filter, null);
}
public List<Node<T>> getNodes(final BoundingBox boundingBox, final Predicate<Node<T>> filter,
final Comparator<Node<T>> comparator) {
final CreateListVisitor<Node<T>> results = new CreateListVisitor<>(filter);
final IdObjectIndex<Node<T>> nodeIndex = getNodeIndex();
nodeIndex.forEach(boundingBox, results);
final List<Node<T>> nodes = results.getList();
if (comparator == null) {
Collections.sort(nodes);
} else {
Collections.sort(nodes, comparator);
}
return nodes;
}
public List<Node<T>> getNodes(final Comparator<Node<T>> comparator) {
final List<Node<T>> targetNodes = getNodes();
if (comparator != null) {
Collections.sort(targetNodes, comparator);
}
return targetNodes;
}
/**
* Find the nodes <= the distance of the specified geometry.
*
* @param geometry The geometry.
* @param maxDistance The maximum distance.
* @return The list of nodes.
*/
public List<Node<T>> getNodes(final Geometry geometry, final double maxDistance) {
BoundingBox boundingBox = geometry.getBoundingBox();
boundingBox = boundingBox.expand(maxDistance);
final IdObjectIndex<Node<T>> nodeIndex = getNodeIndex();
final Predicate<? super Node<T>> filter = (node) -> {
final double distance = geometry.distancePoint(node);
return distance <= maxDistance;
};
return BoundingBox.newArraySorted(nodeIndex::forEach, boundingBox, filter);
}
public List<Node<T>> getNodes(final List<Integer> nodeIds) {
final List<Node<T>> nodes = new ArrayList<>();
for (final int nodeId : nodeIds) {
final Node<T> node = getNode(nodeId);
if (node != null) {
nodes.add(node);
}
}
return nodes;
}
public List<Node<T>> getNodes(final Predicate<Node<T>> filter) {
if (filter == null) {
return getNodes();
} else {
final List<Node<T>> filteredNodes = Predicates.filter(getNodes(), filter);
return filteredNodes;
}
}
public List<Node<T>> getNodes(final Predicate<Node<T>> filter,
final Comparator<Node<T>> comparator) {
final List<Node<T>> targetNodes = getNodes(filter);
if (comparator != null) {
Collections.sort(targetNodes, comparator);
}
return targetNodes;
}
public List<Node<T>> getNodes(final Predicate<Node<T>> filter, final Geometry geometry,
final double maxDistance) {
final BoundingBox boundingBox = geometry.getBoundingBox().expand(maxDistance);
final Predicate<Node<T>> distanceFilter = (node) -> {
return filter.test(node) && node.distance(geometry) <= maxDistance;
};
return getNodes(boundingBox, distanceFilter, null);
}
public List<T> getObjects() {
final List<T> objects = new ArrayList<>();
for (final Edge<T> edge : getEdges()) {
if (edge != null && !edge.isRemoved()) {
final T object = edge.getObject();
objects.add(object);
}
}
return objects;
}
public GeometryFactory getPrecisionModel() {
return this.precisionModel;
}
/**
* Get the type name for the edge.
*
* @param edge The edge.
* @return The type name.
*/
public String getTypeName(final Edge<T> edge) {
final Object object = edge.getObject();
if (object == null) {
return null;
} else {
final String className = edge.getClass().getName();
return className;
}
}
public boolean hasEdge(final Edge<T> edge) {
final Node<T> fromNode = edge.getFromNode();
final Node<T> toNode = edge.getToNode();
return hasEdgeBetween(fromNode, toNode);
}
public boolean hasEdgeBetween(final Point fromPoint, final Point toPoint) {
final Node<T> fromNode = findNode(fromPoint);
if (fromNode == null) {
return false;
} else {
final Node<T> toNode = findNode(toPoint);
if (toNode == null) {
return false;
} else {
return fromNode.hasEdgeTo(toNode);
}
}
}
/**
* Merge the two edges at the node.
*
* @param node
* @param edge1
* @param edge2
*/
public Edge<T> merge(final Node<T> node, final Edge<T> edge1, final Edge<T> edge2) {
if (edge1 != edge2 && edge1.hasNode(node) && edge2.hasNode(node)) {
final Map<String, Object> attributes1 = edge1.getProperties();
final Map<String, Object> attributes2 = edge2.getProperties();
final T object1 = edge1.getObject();
final LineString line1 = edge1.getLine();
final LineString line2 = edge2.getLine();
final LineString newLine = line1.merge(node, line2);
final T mergedObject = clone(object1, newLine);
final Edge<T> newEdge = addEdge(mergedObject, newLine);
newEdge.setProperties(attributes2);
newEdge.setProperties(attributes1);
remove(edge1);
remove(edge2);
return newEdge;
} else {
return null;
}
}
/**
* Merge the two edges into a single edge, removing the old edges and node if
* required from the graph and adding a new edge to the graph.
*
* @param reversedEdges The list of edges that need to be reversed.
* @param node The node to remove.
* @param edge1 The first edge to merge.
* @param edge2 The second edge to merge.
* @return The new edge.
*/
public Edge<T> mergeEdges(final Edge<T> edge1, final Edge<T> edge2) {
final LineString line1 = edge1.getLine();
final LineString line2 = edge2.getLine();
final LineString newLine = line1.merge(line2);
final Edge<T> newEdge = replaceEdge(edge1, newLine);
remove(edge2);
return newEdge;
}
public void moveNode(final String typePath, final Node<Record> fromNode,
final Node<Record> toNode, final Point newPoint) {
if (!fromNode.isRemoved() && !toNode.isRemoved()) {
if (!fromNode.equals(toNode)) {
final List<Edge<Record>> edges = NodeProperties.getEdgesByType(fromNode, typePath);
for (final Edge<Record> edge : edges) {
if (!edge.isRemoved()) {
final LineString line = edge.getLine();
LineString newLine;
if (line.getPoint().equals(fromNode)) {
newLine = line.subLine(newPoint, 1, line.getVertexCount() - 1, null);
} else {
newLine = line.subLine(null, 0, line.getVertexCount() - 1, newPoint);
}
final Graph<Record> graph = edge.getGraph();
graph.replaceEdge(edge, newLine);
}
}
}
}
}
public boolean moveNodesToMidpoint(final String typePath, final Node<Record> node1,
final Node<Record> node2) {
final Point point1 = node1.get3dCoordinates(typePath);
final Point point2 = node2.get3dCoordinates(typePath);
final Graph<Record> graph = node1.getGraph();
final Point midPoint = LineSegmentUtil.midPoint(GeometryFactory.fixed(0, 1000.0, 1000.0, 1.0),
node2, node1);
final double x = midPoint.getX();
final double y = midPoint.getY();
final double z1 = point1.getZ();
final double z2 = point2.getZ();
double z;
if (z1 == 0 || !Double.isFinite(z1)) {
z = z2;
} else if (z2 == 0 || !Double.isFinite(z1)) {
z = z1;
} else {
z = Double.NaN;
}
final Point newPoint = new PointDoubleXYZ(x, y, z);
final Node<Record> newNode = graph.getNode(midPoint);
if (!Node.hasEdgesBetween(typePath, node1, newNode)
&& !Node.hasEdgesBetween(typePath, node2, newNode)) {
if (node1.equals(2, newNode)) {
moveNode(typePath, node2, node1, newPoint);
} else if (node2.equals(2, newNode)) {
moveNode(typePath, node1, node2, newPoint);
} else {
moveNode(typePath, node1, newNode, newPoint);
moveNode(typePath, node2, newNode, newPoint);
}
return true;
} else {
return false;
}
}
public boolean movePointsWithinTolerance(final Map<Point, Point> movedNodes,
final double maxDistance, final Node<T> node1) {
final Graph<T> graph1 = node1.getGraph();
List<Node<T>> nodes2 = getNodes(node1, maxDistance);
if (nodes2.isEmpty()) {
nodes2 = getNodes(node1, maxDistance * 2);
if (nodes2.size() == 1) {
final Node<T> node2 = nodes2.get(0);
if (graph1.findNode(node2) == null) {
final List<Edge<T>> inEdges = node2.getInEdges();
final List<Edge<T>> outEdges = node2.getOutEdges();
if (inEdges.size() == 1 && outEdges.size() == 1) {
final Edge<T> inEdge = inEdges.get(0);
if (inEdge.distancePoint(node1) < maxDistance) {
moveToMidpoint(movedNodes, graph1, node1, node2);
return true;
}
final Edge<T> outEdge = outEdges.get(0);
if (outEdge.distancePoint(node1) < maxDistance) {
moveToMidpoint(movedNodes, graph1, node1, node2);
return true;
}
}
}
}
} else if (nodes2.size() == 1) {
final Node<T> node2 = nodes2.get(0);
if (graph1.findNode(node2) == null) {
moveToMidpoint(movedNodes, graph1, node1, node2);
}
}
return true;
}
public void moveToMidpoint(final Map<Point, Point> movedNodes, final Graph<T> graph1,
final Node<T> node1, final Node<T> node2) {
final GeometryFactory precisionModel = graph1.getPrecisionModel();
final Point midPoint = LineSegmentUtil.midPoint(precisionModel, node1, node2);
if (!node1.equals(2, midPoint)) {
if (movedNodes != null) {
movedNodes.put(node1.newPoint2D(), midPoint);
}
node1.moveNode(midPoint);
}
if (!node2.equals(2, midPoint)) {
if (movedNodes != null) {
movedNodes.put(node2.newPoint2D(), midPoint);
}
node2.moveNode(midPoint);
}
}
public Edge<T> newEdge(final GeometryFactory geometryFactory, final T object,
final LineString points) {
final LineString newLine = geometryFactory.lineString(points);
final T newObject = clone(object, newLine);
final Edge<T> newEdge = addEdge(newObject, newLine);
return newEdge;
}
public void nodeMoved(final Node<T> node, final Node<T> newNode) {
}
public Iterable<Node<T>> nodes() {
return getNodes();
}
public void queryEdges(final EdgeVisitor<T> visitor) {
final BoundingBox env = visitor.getEnvelope();
final IdObjectIndex<Edge<T>> index = getEdgeIndex();
index.forEach(env, visitor);
}
public void queryEdges(final EdgeVisitor<T> visitor, final Consumer<Edge<T>> matchVisitor) {
visitor.setAction(matchVisitor);
queryEdges(visitor);
}
public void remove(final Edge<T> edge) {
if (!edge.isRemoved()) {
this.edgeListeners.edgeEvent(edge, null, EdgeEvent.EDGE_REMOVED, null);
final int edgeId = edge.getId();
this.edgeIds.remove(edge);
this.edgesById.remove(edgeId);
this.edgePropertiesById.remove(edgeId);
if (this.edgeLinesById != null) {
this.edgeLinesById.remove(edgeId);
}
this.edgeObjectsById.remove(edgeId);
if (this.edgeIndex != null) {
this.edgeIndex.remove(edge);
}
edge.removeInternal();
}
}
public void remove(final EdgeEventListener<T> listener) {
this.edgeListeners.remove(listener);
}
public void remove(final Node<T> node) {
if (!node.isRemoved()) {
final ArrayList<Edge<T>> edges = new ArrayList<>(node.getEdges());
for (final Edge<T> edge : edges) {
remove(edge);
}
this.nodeListeners.nodeEvent(node, null, null, NodeEvent.NODE_REMOVED, null);
final int nodeId = node.getId();
this.nodesById.remove(nodeId);
this.nodePropertiesById.remove(nodeId);
this.nodesIdsByPoint.remove(node);
if (this.nodeIndex != null) {
this.nodeIndex.remove(node);
}
node.remove();
}
}
public void remove(final NodeEventListener<T> listener) {
this.nodeListeners.remove(listener);
}
public List<Edge<T>> replaceEdge(final Edge<T> edge, final Geometry lines) {
if (!edge.isRemoved()) {
final List<Edge<T>> edges = new ArrayList<>();
final T object = edge.getObject();
remove(edge);
for (int i = 0; i < lines.getGeometryCount(); i++) {
final LineString line = (LineString)lines.getGeometry(i);
final T newObject = clone(object, line);
final Edge<T> newEdge = addEdge(newObject, line);
edges.add(newEdge);
}
return edges;
} else {
return Collections.emptyList();
}
}
public Edge<T> replaceEdge(final Edge<T> edge, final LineString line) {
if (!edge.isRemoved()) {
final T object = edge.getObject();
final T newObject = clone(object, line);
final Edge<T> newEdge = addEdge(newObject, line);
remove(edge);
return newEdge;
} else {
return null;
}
}
public List<Edge<T>> replaceEdge(final Edge<T> edge, final List<LineString> lines) {
if (!edge.isRemoved()) {
final List<Edge<T>> edges = new ArrayList<>();
final T object = edge.getObject();
remove(edge);
for (final LineString line : lines) {
final T newObject = clone(object, line);
final Edge<T> newEdge = addEdge(newObject, line);
edges.add(newEdge);
}
return edges;
} else {
return Collections.emptyList();
}
}
public void setGeometryFactory(final GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
setPrecisionModel(geometryFactory);
}
public void setMaxEdgesInMemory(final int maxEdgesInMemory) {
this.maxEdgesInMemory = maxEdgesInMemory;
}
public void setPrecisionModel(final GeometryFactory precisionModel) {
this.precisionModel = precisionModel;
}
public <V extends Point> List<Edge<T>> splitEdge(final Edge<T> edge, final Collection<V> nodes) {
return splitEdge(edge, nodes, 0.0);
}
public <V extends Point> List<Edge<T>> splitEdge(final Edge<T> edge,
final Collection<V> splitPoints, final double maxDistance) {
final Collection<V> nodes = new ArrayList<>(splitPoints);
if (edge.isRemoved()) {
return Collections.emptyList();
} else {
final LineString line = edge.getLine();
final LineString points = line;
final Set<Integer> splitVertices = new TreeSet<>();
final Set<Integer> splitIndexes = new TreeSet<>();
for (final Iterator<V> nodeIter = nodes.iterator(); nodeIter.hasNext();) {
final Point node = nodeIter.next();
final double distance = points.distanceVertex(0, node);
if (distance < maxDistance) {
nodeIter.remove();
}
}
final Map<Point, Double> nodeDistanceMap = new HashMap<>();
final Map<Point, Integer> nodeSegment = new HashMap<>();
for (int i = 1; i < points.getVertexCount() && !nodes.isEmpty(); i++) {
for (final Iterator<V> nodeIter = nodes.iterator(); nodeIter.hasNext();) {
final Point node = nodeIter.next();
final double nodeDistance = points.distanceVertex(i, node);
if (nodeDistance < maxDistance) {
if (i < points.getVertexCount() - 1) {
splitVertices.add(i);
splitIndexes.add(i);
}
nodeDistanceMap.remove(node);
nodeSegment.remove(node);
nodeIter.remove();
} else {
final int segmentIndex = i - 1;
final double x = node.getX();
final double y = node.getY();
final double x1 = points.getX(segmentIndex);
final double y1 = points.getY(segmentIndex);
final double x2 = points.getX(i);
final double y2 = points.getY(i);
final double segmentDistance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
if (segmentDistance == 0) {
nodeDistanceMap.put(node, segmentDistance);
nodeSegment.put(node, segmentIndex);
nodeIter.remove();
} else {
final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x,
y);
if (projectionFactor >= 0.0 && projectionFactor <= 1.0) {
final Double closestDistance = nodeDistanceMap.get(node);
if (closestDistance == null) {
nodeSegment.put(node, segmentIndex);
nodeDistanceMap.put(node, segmentDistance);
} else if (closestDistance.compareTo(segmentDistance) > 0) {
nodeSegment.put(node, segmentIndex);
nodeDistanceMap.put(node, segmentDistance);
}
}
}
}
}
}
final T object = edge.getObject();
final GeometryFactory geometryFactory = line.getGeometryFactory();
final Map<Integer, Set<Point>> segmentSplitNodes = new TreeMap<>();
for (final Entry<Point, Integer> entry : nodeSegment.entrySet()) {
final Point node = entry.getKey();
final Integer index = entry.getValue();
Set<Point> splitNodes = segmentSplitNodes.get(index);
if (splitNodes == null) {
final double x = points.getX(index);
final double y = points.getY(index);
splitNodes = new TreeSet<>(new CoordinatesDistanceComparator(x, y));
segmentSplitNodes.put(index, splitNodes);
splitIndexes.add(index);
}
splitNodes.add(node);
nodes.remove(node);
}
if (nodes.isEmpty()) {
final List<LineString> newLines = new ArrayList<>();
int startIndex = 0;
Point startPoint = null;
for (final Integer index : splitIndexes) {
if (splitVertices.contains(index)) {
final LineString newPoints = points.subLine(startPoint, startIndex,
index - startIndex + 1, null);
newLines.add(newPoints);
startPoint = null;
startIndex = index;
}
final Set<Point> splitNodes = segmentSplitNodes.get(index);
if (splitNodes != null) {
for (final Point splitPoint : splitNodes) {
final Node<T> node = getNode(splitPoint);
final String typePath = edge.getTypeName();
Point point = splitPoint;
double splitPointZ = splitPoint.getZ();
if (splitPointZ == 0 || Double.isNaN(splitPointZ)) {
if (splitPoint instanceof Node<?>) {
final Node<?> splitNode = (Node<?>)splitPoint;
point = splitNode.get3dCoordinates(typePath);
splitPointZ = point.getZ();
}
if (splitPointZ == 0 || Double.isNaN(splitPointZ)) {
point = node.get3dCoordinates(typePath);
}
if (splitPointZ == 0 || Double.isNaN(splitPointZ)) {
final Point p1 = points.getPoint(index);
final Point p2 = points.getPoint(index + 1);
final double z = LineSegmentUtil.getElevation(p1, p2, point);
point = new PointDoubleXYZ(point.getX(), point.getY(), z);
}
}
final LineString newPoints;
if (startIndex > index) {
newPoints = new LineStringDouble(points.getAxisCount(), startPoint, point);
} else {
newPoints = points.subLine(startPoint, startIndex, index - startIndex + 1, point);
}
newLines.add(newPoints);
startPoint = point;
startIndex = index + 1;
}
}
}
final LineString newPoints = points.subLine(startPoint, startIndex, points.getVertexCount(),
null);
newLines.add(newPoints);
if (newLines.size() > 1) {
final List<Edge<T>> newEdges = new ArrayList<>();
for (final LineString edgePoints : newLines) {
final Edge<T> newEdge = newEdge(geometryFactory, object, edgePoints);
newEdges.add(newEdge);
}
edge.remove();
return newEdges;
} else {
return Collections.singletonList(edge);
}
} else {
return Collections.singletonList(edge);
}
}
}
public List<Edge<T>> splitEdge(final Edge<T> edge, final Point point) {
if (!edge.isRemoved()) {
final LineString line = edge.getLine();
final List<LineString> lines = line.split(point);
if (lines.size() == 1) {
return Collections.singletonList(edge);
} else {
final List<Edge<T>> newEdges = replaceEdge(edge, lines);
return newEdges;
}
} else {
return Collections.emptyList();
}
}
public List<Edge<T>> splitEdge(final Edge<T> edge, final Point... nodes) {
return splitEdge(edge, Arrays.asList(nodes));
}
}