edu.uci.ics.jung.visualization.spatial.SpatialRTree Maven / Gradle / Ivy
package edu.uci.ics.jung.visualization.spatial;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.graph.EndpointPair;
import com.google.common.graph.Network;
import edu.uci.ics.jung.layout.model.LayoutModel;
import edu.uci.ics.jung.layout.model.Point;
import edu.uci.ics.jung.layout.util.LayoutChangeListener;
import edu.uci.ics.jung.layout.util.LayoutEvent;
import edu.uci.ics.jung.layout.util.LayoutNetworkEvent;
import edu.uci.ics.jung.visualization.VisualizationModel;
import edu.uci.ics.jung.visualization.layout.BoundingRectangleCollector;
import edu.uci.ics.jung.visualization.layout.NetworkElementAccessor;
import edu.uci.ics.jung.visualization.layout.RadiusNetworkElementAccessor;
import edu.uci.ics.jung.visualization.spatial.rtree.LeafNode;
import edu.uci.ics.jung.visualization.spatial.rtree.Node;
import edu.uci.ics.jung.visualization.spatial.rtree.RTree;
import edu.uci.ics.jung.visualization.spatial.rtree.SplitterContext;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @param The Type for the elements managed by the RTree, Nodes or Edges
* @param The Type for the Nodes of the graph. May be the same as T
* @author Tom Nelson
*/
public abstract class SpatialRTree extends AbstractSpatial implements Spatial {
private static final Logger log = LoggerFactory.getLogger(SpatialRTree.class);
/** a container for the splitter functions to use, quadratic or R*Tree */
protected SplitterContext splitterContext;
/** the RTree to use. Add/Remove methods may change this to a new immutable RTree reference */
protected RTree rtree;
/** gathers the bounding rectangles of the elements managed by the RTree. Node or Edge shapes */
protected BoundingRectangleCollector boundingRectangleCollector;
/**
* create a new instance with the a LayoutModel and a style of splitter to use
*
* @param layoutModel
* @param splitterContext
*/
public SpatialRTree(LayoutModel layoutModel, SplitterContext splitterContext) {
super(layoutModel);
this.splitterContext = splitterContext;
}
/**
* gather the RTree nodes into a list for display as Paintables
*
* @param list
* @param tree
* @return
*/
protected abstract List collectGrids(List list, RTree tree);
/** @return the 2 dimensional area of interest for this class */
@Override
public Rectangle2D getLayoutArea() {
return rectangle;
}
/** @param bounds the new bounds for the data struture */
@Override
public void setBounds(Rectangle2D bounds) {
this.rectangle = bounds;
}
@Override
public List getGrid() {
if (gridCache == null) {
log.trace("getting Grid from tree size {}", rtree.count());
if (!isActive()) {
// just return the entire layout area
return Collections.singletonList(getLayoutArea());
}
List areas = Lists.newArrayList();
gridCache = collectGrids(areas, rtree);
log.trace("getGrid got {} and {}", areas.size(), gridCache.size());
return gridCache;
} else {
return gridCache;
}
}
@Override
public void clear() {
rtree = RTree.create();
}
/**
* @param x a point to search in the spatial structure
* @param y a point to search in the spatial structure
* @return a collection of the RTree LeafNodes that would contain the passed point
*/
@Override
public Set> getContainingLeafs(double x, double y) {
if (!isActive() || !rtree.getRoot().isPresent()) {
return Collections.emptySet();
}
Node theRoot = rtree.getRoot().get();
return theRoot.getContainingLeafs(Sets.newHashSet(), x, y);
}
@Override
public Set> getContainingLeafs(Point2D p) {
return getContainingLeafs(p.getX(), p.getY());
}
@Override
public LeafNode getContainingLeaf(Object element) {
if (!rtree.getRoot().isPresent()) {
return null; // nothing in this tree
}
Node theRoot = rtree.getRoot().get();
return theRoot.getContainingLeaf((T) element);
}
protected void recalculate(Collection elements) {
log.trace("start recalculate");
clear();
if (boundingRectangleCollector != null) {
for (T element : elements) {
rtree =
rtree.add(
splitterContext,
(T) element,
boundingRectangleCollector.getForElement((T) element));
log.trace("added {} got {} nodes in {}", element, rtree.count(), rtree);
}
} else {
log.trace("got no rectangles");
}
log.trace("end recalculate");
}
public static class Nodes extends SpatialRTree
implements Spatial, LayoutChangeListener {
private static final Logger log = LoggerFactory.getLogger(Nodes.class);
public Nodes(LayoutModel layoutModel, SplitterContext splitterContext) {
super(layoutModel, splitterContext);
rtree = rtree.create();
}
public Nodes(
VisualizationModel visualizationModel,
BoundingRectangleCollector.Nodes boundingRectangleCollector,
SplitterContext splitterContext) {
super(visualizationModel.getLayoutModel(), splitterContext);
this.boundingRectangleCollector = boundingRectangleCollector;
rtree = RTree.create();
}
/**
* @param shape the possibly non-rectangular area of interest
* @return all nodes that are contained within the passed Shape
*/
@Override
public Set getVisibleElements(Shape shape) {
if (!isActive() || !rtree.getRoot().isPresent()) {
return layoutModel.getGraph().nodes();
}
pickShapes.add(shape);
Node root = rtree.getRoot().get();
log.trace("out of nodes {}", layoutModel.getGraph().nodes());
Set visibleElements = Sets.newHashSet();
return root.getVisibleElements(visibleElements, shape);
}
/**
* update the position of the passed node
*
* @param element the element to update in the structure
* @param location the new location for the element
*/
@Override
public void update(N element, Point location) {
gridCache = null;
// do nothing if we are not active
if (isActive() && rtree.getRoot().isPresent()) {
// TreeNode root = rtree.getRoot().get();
LeafNode containingLeaf = getContainingLeaf(element);
Rectangle2D itsShape = boundingRectangleCollector.getForElement(element, location);
// if the shape does not enlarge the containingRTree, then only update what is in elements
// otherwise, remove this node and re-insert it
if (containingLeaf != null) {
if (containingLeaf.getBounds().contains(itsShape)) {
// the element did not move out of its LeafNode
// no RTree update required, just re-add the element to the map
// with the new Bounds value
// remove it from the map (so there is no overflow when it is added
containingLeaf.remove(element);
containingLeaf.add(splitterContext, element, itsShape);
} else {
// the element is outside of the previous containing LeafNode
// remmove the element from the tree and add it again so it will
// go to the correct LeafNode
rtree = rtree.remove(element);
rtree = rtree.add(splitterContext, element, itsShape);
}
} else {
// the element is new to the RTree
// just add it
rtree = rtree.add(splitterContext, element, itsShape);
}
}
}
@Override
public N getClosestElement(Point2D p) {
return getClosestElement(p.getX(), p.getY());
}
/**
* get the node that is closest to the passed (x,y)
*
* @param x
* @param y
* @return the node closest to x,y
*/
@Override
public N getClosestElement(double x, double y) {
if (!isActive() || !rtree.getRoot().isPresent()) {
// use the fallback NetworkNodeAccessor
return fallback.getNode(layoutModel, x, y);
}
double radius = layoutModel.getWidth() / 20;
N closest = null;
while (closest == null) {
double diameter = radius * 2;
Ellipse2D searchArea = new Ellipse2D.Double(x - radius, y - radius, diameter, diameter);
Collection nodes = getVisibleElements(searchArea);
closest = getClosest(nodes, x, y, radius);
// if i found a winner or
// if I have already considered all of the nodes in the graph
// (in the spatialtree) there is no reason to enlarge the
// area and try again
if (closest != null || nodes.size() >= layoutModel.getGraph().nodes().size()) {
break;
}
// double the search area size and try again
radius *= 2;
}
return closest;
}
protected List collectGrids(List list, RTree tree) {
if (tree.getRoot().isPresent()) {
Node root = tree.getRoot().get();
root.collectGrids(list);
}
return list;
}
/**
* rebuild the data structure // * // * @param elements the elements to insert into the data
* structure
*/
@Override
public void recalculate() {
gridCache = null;
log.trace(
"called recalculate while active:{} layout model relaxing:{}",
isActive(),
layoutModel.isRelaxing());
if (isActive()) {
log.trace("recalculate for nodes: {}", layoutModel.getGraph().nodes());
recalculate(layoutModel.getGraph().nodes());
} else {
log.trace("no recalculate when active: {}", isActive());
}
}
@Override
public void layoutChanged(LayoutEvent evt) {
this.update(evt.getNode(), evt.getLocation());
}
@Override
public void layoutChanged(LayoutNetworkEvent evt) {
update(evt.getNode(), evt.getLocation());
}
}
public static class Edges extends SpatialRTree
implements Spatial, LayoutChangeListener {
private static final Logger log = LoggerFactory.getLogger(Edges.class);
NetworkElementAccessor networkElementAccessor;
// Edges gets a VisualizationModel reference to access the Network and work with edges
VisualizationModel visualizationModel;
public Edges(
VisualizationModel visualizationModel,
BoundingRectangleCollector.Edges boundingRectangleCollector,
SplitterContext splitterContext) {
super(visualizationModel.getLayoutModel(), splitterContext);
this.visualizationModel = visualizationModel;
this.boundingRectangleCollector = boundingRectangleCollector;
networkElementAccessor = new RadiusNetworkElementAccessor(visualizationModel.getNetwork());
rtree = RTree.create();
recalculate();
}
/**
* @param shape the possibly non-rectangular area of interest
* @return all nodes that are contained within the passed Shape
*/
@Override
public Set getVisibleElements(Shape shape) {
if (!isActive() || !rtree.getRoot().isPresent()) {
log.trace("not relaxing so getting from the network");
return visualizationModel.getNetwork().edges();
}
pickShapes.add(shape);
Node root = rtree.getRoot().get();
Set visibleElements = Sets.newHashSet();
return root.getVisibleElements(visibleElements, shape);
}
/**
* update the position of the passed node
*
* @param element the element to update in the structure
* @param location the new location for the element
*/
@Override
public void update(E element, Point location) {
gridCache = null;
if (isActive()) {
// get the endpoints for this edge
Iterable nodes = visualizationModel.getNetwork().incidentNodes(element);
// there should be 2
N n1 = null;
N n2 = null;
Iterator iterator = nodes.iterator();
if (iterator.hasNext()) {
n1 = iterator.next();
}
if (iterator.hasNext()) {
n2 = iterator.next();
}
if (n2 == null) {
n2 = n1;
}
if (n1 != null && n2 != null) {
Rectangle2D itsShape =
boundingRectangleCollector.getForElement(
element, layoutModel.apply(n1), layoutModel.apply(n2));
LeafNode containingLeaf = getContainingLeaf(element);
// if the shape does not enlarge the containingRTree, then only update what is in elements
// map
// otherwise, remove this node and re-insert it
if (containingLeaf != null) {
if (containingLeaf.getBounds().contains(itsShape)) {
containingLeaf.remove(element);
containingLeaf.add(splitterContext, element, itsShape);
log.trace("{} changed in place", element);
} else {
containingLeaf.remove(element);
log.trace("rtree now size {}", rtree.count());
rtree = rtree.add(splitterContext, element, itsShape);
log.trace(
"added back {} with {} into rtree size {}", element, itsShape, rtree.count());
}
} else {
rtree = rtree.add(splitterContext, element, itsShape);
}
}
}
}
@Override
public void layoutChanged(LayoutEvent evt) {
// need to take care of edge changes
N node = evt.getNode();
Point p = evt.getLocation();
if (visualizationModel.getNetwork().nodes().contains(node)) {
Set edges = visualizationModel.getNetwork().incidentEdges(node);
for (E edge : edges) {
update(edge, p);
}
}
}
@Override
public void layoutChanged(LayoutNetworkEvent evt) {
N node = evt.getNode();
Point p = evt.getLocation();
if (visualizationModel.getNetwork().nodes().contains(node)) {
Set edges = visualizationModel.getNetwork().incidentEdges(node);
for (E edge : edges) {
update(edge, p);
}
}
}
/**
* get the element that is closest to the passed point
*
* @param p a point to search in the spatial structure
* @return the closest element
*/
@Override
public E getClosestElement(Point2D p) {
return getClosestElement(p.getX(), p.getY());
}
/**
* get the element that is closest to the passed (x,y)
*
* @param x coordinate to search for
* @param y coordinate to search for
* @return the element closest to x,y
*/
@Override
public E getClosestElement(double x, double y) {
if (!isActive() || !rtree.getRoot().isPresent()) {
// not active or empty
// use the fallback NetworkNodeAccessor
return networkElementAccessor.getEdge(layoutModel, x, y);
}
Node root = rtree.getRoot().get();
double radius = layoutModel.getWidth() / 20;
E closest = null;
while (closest == null) {
double diameter = radius * 2;
Ellipse2D searchArea = new Ellipse2D.Double(x - radius, y - radius, diameter, diameter);
Collection edges = getVisibleElements(searchArea);
closest = getClosestEdge(edges, x, y, radius);
// If i found a winner, break. also
// if I have already considered all of the nodes in the graph
// (in the spatialquadtree) there is no reason to enlarge the
// area and try again
if (closest != null || edges.size() >= layoutModel.getGraph().edges().size()) {
break;
}
// double the search area size and try again
radius *= 2;
}
return closest;
}
protected E getClosestEdge(Collection edges, double x, double y, double radius) {
// since I am comparing with distance squared, i need to square the radius
double radiusSq = radius * radius;
if (edges.size() > 0) {
double closestSoFar = Double.MAX_VALUE;
E winner = null;
double winningDistance = -1;
for (E edge : edges) {
// get the 2 endpoints
Network network = visualizationModel.getNetwork();
EndpointPair endpoints = network.incidentNodes(edge);
N u = endpoints.nodeU();
N v = endpoints.nodeV();
Point up = layoutModel.apply(u);
Point vp = layoutModel.apply(v);
// compute the distance between my point and a Line connecting u and v
Line2D line = new Line2D.Double(up.x, up.y, vp.x, vp.y);
double dist = line.ptSegDist(x, y);
// consider only edges that cross inside the search radius
// and are closer than previously found nodes
if (dist < radiusSq && dist < closestSoFar) {
closestSoFar = dist;
winner = edge;
winningDistance = dist;
}
}
if (log.isTraceEnabled()) {
log.trace("closest winner is {} at distance {}", winner, winningDistance);
}
return winner;
} else {
return null;
}
}
protected List collectGrids(List list, RTree tree) {
if (tree.getRoot().isPresent()) {
Node root = tree.getRoot().get();
root.collectGrids(list);
}
return list;
}
/** rebuild the data structure */
@Override
public void recalculate() {
gridCache = null;
log.trace(
"called recalculate while active:{} layout model relaxing:{}",
isActive(),
layoutModel.isRelaxing());
if (isActive()) {
log.trace("recalculate for edges: {}", visualizationModel.getNetwork().edges());
recalculate(visualizationModel.getNetwork().edges());
}
}
}
public String toString() {
return rtree.toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy