Skip to content

Instantly share code, notes, and snippets.

Created December 18, 2014 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/672d190b844410c1aa0e to your computer and use it in GitHub Desktop.
Save anonymous/672d190b844410c1aa0e to your computer and use it in GitHub Desktop.
RTree test for 200k drawable nodes
package incubator.rtree;
import incubator.swing.scroll2.TestDragViewPortView;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.function.Consumer;
/**
* Implementation of an arbitrary-dimension RTree. Based on R-Trees: A Dynamic
* Index Structure for Spatial Searching (Antonn Guttmann, 1984)
* <p>
* This class is not thread-safe.
* <p>
* Copyright 2010 Russ Weeks rweeks@newbrightidea.com Licensed under the GNU
* LGPL License details here: http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @param <T> the type of entry to store in this RTree.
*/
public class RTree<T> {
public enum SeedPicker {LINEAR, QUADRATIC}
private final int maxEntries;
private final int minEntries;
private final int numDims;
private final float[] pointDims;
private final SeedPicker seedPicker;
private Node root;
private volatile int size;
/**
* Creates a new RTree.
*
* @param maxEntries maximum number of entries per node
* @param minEntries minimum number of entries per node (except for the root node)
* @param numDims the number of dimensions of the RTree.
*/
public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) {
assert (minEntries <= (maxEntries / 2));
this.numDims = numDims;
this.maxEntries = maxEntries;
this.minEntries = minEntries;
this.seedPicker = seedPicker;
pointDims = new float[numDims];
root = buildRoot(true);
}
public RTree(int maxEntries, int minEntries, int numDims) {
this(maxEntries, minEntries, numDims, SeedPicker.LINEAR);
}
private Node buildRoot(boolean asLeaf) {
float[] initCoords = new float[numDims];
float[] initDimensions = new float[numDims];
for (int i = 0; i < this.numDims; i++) {
initCoords[i] = (float) Math.sqrt(Float.MAX_VALUE);
initDimensions[i] = -2.0f * (float) Math.sqrt(Float.MAX_VALUE);
}
return new Node(initCoords, initDimensions, asLeaf);
}
/**
* Builds a new RTree using default parameters: maximum 50 entries per node
* minimum 2 entries per node 2 dimensions
*/
public RTree() {
this(50, 2, 2, SeedPicker.LINEAR);
}
/**
* @return the maximum number of entries per node
*/
public int getMaxEntries() {
return maxEntries;
}
/**
* @return the minimum number of entries per node for all nodes except the
* root.
*/
public int getMinEntries() {
return minEntries;
}
/**
* @return the number of dimensions of the tree
*/
public int getNumDims() {
return numDims;
}
/**
* @return the number of items in this tree.
*/
public int size() {
return size;
}
/**
* Searches the RTree for objects overlapping with the given rectangle.
*
* @param coords the corner of the rectangle that is the lower bound of every
* dimension (eg. the top-left corner)
* @param dimensions the dimensions of the rectangle.
* @return a list of objects whose rectangles overlap with the given
* rectangle.
*/
public List<T> search(float[] coords, float[] dimensions) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
LinkedList<T> results = new LinkedList<T>();
search(coords, dimensions, root, results);
return results;
}
private void search(float[] coords, float[] dimensions, Node n,
LinkedList<T> results) {
if (n.leaf) {
for (Node e : n.children) {
if (isOverlap(coords, dimensions, e.coords, e.dimensions)) {
results.add(((Entry) e).entry);
}
}
} else {
for (Node c : n.children) {
if (isOverlap(coords, dimensions, c.coords, c.dimensions)) {
search(coords, dimensions, c, results);
}
}
}
}
private abstract static class Box {
int x;
int y;
int w;
int h;
String name;
Color color;
int id;
public Box(int x, int y, int dx, int dy, String format) {
this.x = x;
this.y = y;
this.w = dx;
this.h = dy;
this.name = format;
color = new Color(((int) (Math.random() * Integer.MAX_VALUE)));
}
public Rectangle toRectangle() {
return new Rectangle(x, y, w, h);
}
public abstract void paintTo(Graphics2D g);
}
public static void main(String... args) {
RTree<Box> stringRTree = new RTree<>();
ArrayList<Box> boxes = new ArrayList<>();
int maxX = 1000;
int maxY = 10000;
int dx = 10;
int dy = 5;
int count = 0;
for (int x = 0; x < maxX; x += dx) {
for (int y = 0; y < maxY; y += dy) {
Box box = new Box(x, y, dx, dy, String.format("[%d, %d]", x, y)) {
@Override
public void paintTo(Graphics2D g2d) {
int arc = 3;
g2d.setPaint(color);
g2d.fillRoundRect(x, y, w, h, arc, arc);
}
};
box.id = count;
put(stringRTree, x, y, dx, dy, box);
boxes.add(box);
count++;
}
}
System.out.println("count = " + count);
System.out.println(stringRTree.search(new float[]{2f, 2f}, new float[]{10f, 10f}));
Consumer[] title1 = new Consumer[1];
PerformanceTestPanel vis = new PerformanceTestPanel(maxX, maxY, stringRTree, boxes, (ms) -> {
title1[0].accept(ms);
});
vis.setUseRTree(true);
vis.setBounds(0, 0, maxX, maxY);
JScrollPane scrollPane1 = new JScrollPane(vis);
scrollPane1.setPreferredSize(new Dimension(300, 300));
Consumer[] title2 = new Consumer[1];
PerformanceTestPanel vis2 = new PerformanceTestPanel(maxX, maxY, stringRTree, boxes, (ms) -> {
title2[0].accept(ms);
});
vis2.setUseRTree(false);
vis2.setBounds(0, 0, maxX, maxY);
JScrollPane scrollPane2 = new JScrollPane(vis2);
scrollPane2.setPreferredSize(new Dimension(300, 300));
addClickFinder(stringRTree, vis);
addClickFinder(stringRTree, vis2);
new TestDragViewPortView.MouseDragger().makeDraggable(vis);
new TestDragViewPortView.MouseDragger().makeDraggable(vis2);
new Thread(
() -> {
JDialog jDialog = showInDialog(scrollPane1, "R-Tree optimized");
title1[0] = (ms) -> jDialog.setTitle("R-Tree optimized" + " - " + "ms: " + String.valueOf((Long) ms / 1e6d) + ", FPS: " + toFps(ms));
}
).start();
new Thread(
() -> {
JDialog unoptimized = showInDialog(scrollPane2, "unoptimized");
title2[0] = (ms) -> unoptimized.setTitle("unoptimized" + " - " + "ms: " + String.valueOf((Long) ms / 1e6d) + ", FPS: " + toFps(ms));
}
).start();
}
private static void addClickFinder(final RTree<Box> stringRTree, PerformanceTestPanel vis) {
MouseAdapter adapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
List<Box> result = search(stringRTree, new Rectangle(e.getPoint()));
for (Box box : result) {
stringRTree.delete(new float[]{box.x, box.y}, new float[]{box.w, box.h}, box);
int grow = 5;
box.w += 2 * grow;
box.h += 2 * grow;
box.y -= grow;
box.x -= grow;
put(stringRTree, box.x, box.y, box.w, box.h, box);
vis.repaint(box.toRectangle());
}
vis.redispatchToParent(e);
}
@Override
public void mouseDragged(MouseEvent e) {
vis.redispatchToParent(e);
}
@Override
public void mousePressed(MouseEvent e) {
vis.redispatchToParent(e);
}
@Override
public void mouseReleased(MouseEvent e) {
vis.redispatchToParent(e);
}
};
vis.addMouseListener(adapter);
vis.addMouseMotionListener(adapter);
}
public static class MouseDragger extends MouseAdapter {
private Point startPoint;
private Component draggedObject;
private JViewport viewport;
@Override
public void mousePressed(MouseEvent e) {
draggedObject = (Component) e.getSource();
viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, draggedObject);
if (viewport != null) {
startPoint = SwingUtilities.convertPoint(draggedObject, e.getPoint(), viewport);
draggedObject.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (viewport == null) {
return;
}
Point location = SwingUtilities.convertPoint(draggedObject, e.getPoint(), viewport);
Point newPosition = viewport.getViewPosition();
newPosition.translate(startPoint.x - location.x, startPoint.y - location.y);
newPosition.x = Math.min(newPosition.x, draggedObject.getWidth() - viewport.getExtentSize().width);
newPosition.y = Math.min(newPosition.y, draggedObject.getHeight() - viewport.getExtentSize().height);
newPosition.x = Math.max(newPosition.x, 0);
newPosition.y = Math.max(newPosition.y, 0);
viewport.setViewPosition(newPosition);
startPoint = location;
}
@Override
public void mouseReleased(MouseEvent e) {
draggedObject.setCursor(Cursor.getDefaultCursor());
startPoint = null;
draggedObject = null;
viewport = null;
}
public void makeDraggable(Component component) {
component.addMouseListener(this);
component.addMouseMotionListener(this);
}
}
private static String toFps(Object ms) {
return "" + ((int) (1d / ((((Long) ms)) / 1e9d)));
}
private static JDialog showInDialog(Object scrollPane, String title) {
JOptionPane jOptionPane = new JOptionPane(scrollPane, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION);
JDialog dialog = jOptionPane.createDialog(title);
dialog.setModal(false);
dialog.setVisible(true);
return dialog;
}
public static <T> void put(RTree<T> tree, float x, float y, float w, float h, T args) {
tree.insert(new float[]{x, y}, new float[]{w, h}, args);
}
public static <T> List<T> search(RTree<T> tree, float x, float y, float w, float h) {
return tree.search(new float[]{x, y}, new float[]{w, h});
}
public static <T> List<T> search(RTree<T> tree, Rectangle r) {
return search(tree, r.x, r.y, r.width + 1, r.height + 1);
}
/**
* Deletes the entry associated with the given rectangle from the RTree
*
* @param coords the corner of the rectangle that is the lower bound in every
* dimension
* @param dimensions the dimensions of the rectangle
* @param entry the entry to delete
* @return true iff the entry was deleted from the RTree.
*/
public boolean delete(float[] coords, float[] dimensions, T entry) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
Node l = findLeaf(root, coords, dimensions, entry);
if (l == null) {
System.out.println("WTF?");
findLeaf(root, coords, dimensions, entry);
}
assert (l != null) : "Could not find leaf for entry to delete";
assert (l.leaf) : "Entry is not found at leaf?!?";
ListIterator<Node> li = l.children.listIterator();
T removed = null;
while (li.hasNext()) {
@SuppressWarnings("unchecked")
Entry e = (Entry) li.next();
if (e.entry.equals(entry)) {
removed = e.entry;
li.remove();
break;
}
}
if (removed != null) {
condenseTree(l);
size--;
}
if (size == 0) {
root = buildRoot(true);
}
return (removed != null);
}
public boolean delete(float[] coords, T entry) {
return delete(coords, pointDims, entry);
}
private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) {
if (n.leaf) {
for (Node c : n.children) {
if (((Entry) c).entry.equals(entry)) {
return n;
}
}
return null;
} else {
for (Node c : n.children) {
if (isOverlap(c.coords, c.dimensions, coords, dimensions)) {
Node result = findLeaf(c, coords, dimensions, entry);
if (result != null) {
return result;
}
}
}
return null;
}
}
private void condenseTree(Node n) {
Set<Node> q = new HashSet<Node>();
while (n != root) {
if (n.leaf && (n.children.size() < minEntries)) {
q.addAll(n.children);
n.parent.children.remove(n);
} else if (!n.leaf && (n.children.size() < minEntries)) {
// probably a more efficient way to do this...
LinkedList<Node> toVisit = new LinkedList<Node>(n.children);
while (!toVisit.isEmpty()) {
Node c = toVisit.pop();
if (c.leaf) {
q.addAll(c.children);
} else {
toVisit.addAll(c.children);
}
}
n.parent.children.remove(n);
} else {
tighten(n);
}
n = n.parent;
}
if (root.children.size() == 0) {
root = buildRoot(true);
} else if ((root.children.size() == 1) && (!root.leaf)) {
root = root.children.get(0);
root.parent = null;
} else {
tighten(root);
}
for (Node ne : q) {
@SuppressWarnings("unchecked")
Entry e = (Entry) ne;
insert(e.coords, e.dimensions, e.entry);
}
size -= q.size();
}
/**
* Empties the RTree
*/
public void clear() {
root = buildRoot(true);
// let the GC take care of the rest.
}
/**
* Inserts the given entry into the RTree, associated with the given
* rectangle.
*
* @param coords the corner of the rectangle that is the lower bound in every
* dimension
* @param dimensions the dimensions of the rectangle
* @param entry the entry to insert
*/
public void insert(float[] coords, float[] dimensions, T entry) {
assert (coords.length == numDims);
assert (dimensions.length == numDims);
Entry e = new Entry(coords, dimensions, entry);
Node l = chooseLeaf(root, e);
l.children.add(e);
size++;
e.parent = l;
if (l.children.size() > maxEntries) {
Node[] splits = splitNode(l);
adjustTree(splits[0], splits[1]);
} else {
adjustTree(l, null);
}
}
/**
* Convenience method for inserting a point
*
* @param coords
* @param entry
*/
public void insert(float[] coords, T entry) {
insert(coords, pointDims, entry);
}
private void adjustTree(Node n, Node nn) {
if (n == root) {
if (nn != null) {
// build new root and add children.
root = buildRoot(false);
root.children.add(n);
n.parent = root;
root.children.add(nn);
nn.parent = root;
}
tighten(root);
return;
}
tighten(n);
if (nn != null) {
tighten(nn);
if (n.parent.children.size() > maxEntries) {
Node[] splits = splitNode(n.parent);
adjustTree(splits[0], splits[1]);
}
}
if (n.parent != null) {
adjustTree(n.parent, null);
}
}
private Node[] splitNode(Node n) {
// TODO: this class probably calls "tighten" a little too often.
// For instance the call at the end of the "while (!cc.isEmpty())" loop
// could be modified and inlined because it's only adjusting for the addition
// of a single node. Left as-is for now for readability.
@SuppressWarnings("unchecked")
Node[] nn = new RTree.Node[]
{n, new Node(n.coords, n.dimensions, n.leaf)};
nn[1].parent = n.parent;
if (nn[1].parent != null) {
nn[1].parent.children.add(nn[1]);
}
LinkedList<Node> cc = new LinkedList<Node>(n.children);
n.children.clear();
Node[] ss = seedPicker == SeedPicker.LINEAR ? lPickSeeds(cc) : qPickSeeds(cc);
nn[0].children.add(ss[0]);
nn[1].children.add(ss[1]);
tighten(nn);
while (!cc.isEmpty()) {
if ((nn[0].children.size() >= minEntries)
&& (nn[1].children.size() + cc.size() == minEntries)) {
nn[1].children.addAll(cc);
cc.clear();
tighten(nn); // Not sure this is required.
return nn;
} else if ((nn[1].children.size() >= minEntries)
&& (nn[0].children.size() + cc.size() == minEntries)) {
nn[0].children.addAll(cc);
cc.clear();
tighten(nn); // Not sure this is required.
return nn;
}
Node c = seedPicker == SeedPicker.LINEAR ? lPickNext(cc) : qPickNext(cc, nn);
Node preferred;
float e0 = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c);
float e1 = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c);
if (e0 < e1) {
preferred = nn[0];
} else if (e0 > e1) {
preferred = nn[1];
} else {
float a0 = getArea(nn[0].dimensions);
float a1 = getArea(nn[1].dimensions);
if (a0 < a1) {
preferred = nn[0];
} else if (e0 > a1) {
preferred = nn[1];
} else {
if (nn[0].children.size() < nn[1].children.size()) {
preferred = nn[0];
} else if (nn[0].children.size() > nn[1].children.size()) {
preferred = nn[1];
} else {
preferred = nn[(int) Math.round(Math.random())];
}
}
}
preferred.children.add(c);
tighten(preferred);
}
return nn;
}
// Implementation of Quadratic PickSeeds
private RTree<T>.Node[] qPickSeeds(LinkedList<Node> nn) {
@SuppressWarnings("unchecked")
RTree<T>.Node[] bestPair = new RTree.Node[2];
float maxWaste = -1.0f * Float.MAX_VALUE;
for (Node n1 : nn) {
for (Node n2 : nn) {
if (n1 == n2) continue;
float n1a = getArea(n1.dimensions);
float n2a = getArea(n2.dimensions);
float ja = 1.0f;
for (int i = 0; i < numDims; i++) {
float jc0 = Math.min(n1.coords[i], n2.coords[i]);
float jc1 = Math.max(n1.coords[i] + n1.dimensions[i], n2.coords[i] + n2.dimensions[i]);
ja *= (jc1 - jc0);
}
float waste = ja - n1a - n2a;
if (waste > maxWaste) {
maxWaste = waste;
bestPair[0] = n1;
bestPair[1] = n2;
}
}
}
nn.remove(bestPair[0]);
nn.remove(bestPair[1]);
return bestPair;
}
/**
* Implementation of QuadraticPickNext
*
* @param cc the children to be divided between the new nodes, one item will be removed from this list.
* @param nn the candidate nodes for the children to be added to.
*/
private Node qPickNext(LinkedList<Node> cc, Node[] nn) {
float maxDiff = -1.0f * Float.MAX_VALUE;
Node nextC = null;
for (Node c : cc) {
float n0Exp = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c);
float n1Exp = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c);
float diff = Math.abs(n1Exp - n0Exp);
if (diff > maxDiff) {
maxDiff = diff;
nextC = c;
}
}
assert (nextC != null) : "No node selected from qPickNext";
cc.remove(nextC);
return nextC;
}
// Implementation of LinearPickSeeds
private RTree<T>.Node[] lPickSeeds(LinkedList<Node> nn) {
@SuppressWarnings("unchecked")
RTree<T>.Node[] bestPair = new RTree.Node[2];
boolean foundBestPair = false;
float bestSep = 0.0f;
for (int i = 0; i < numDims; i++) {
float dimLb = Float.MAX_VALUE, dimMinUb = Float.MAX_VALUE;
float dimUb = -1.0f * Float.MAX_VALUE, dimMaxLb = -1.0f * Float.MAX_VALUE;
Node nMaxLb = null, nMinUb = null;
for (Node n : nn) {
if (n.coords[i] < dimLb) {
dimLb = n.coords[i];
}
if (n.dimensions[i] + n.coords[i] > dimUb) {
dimUb = n.dimensions[i] + n.coords[i];
}
if (n.coords[i] > dimMaxLb) {
dimMaxLb = n.coords[i];
nMaxLb = n;
}
if (n.dimensions[i] + n.coords[i] < dimMinUb) {
dimMinUb = n.dimensions[i] + n.coords[i];
nMinUb = n;
}
}
float sep = (nMaxLb == nMinUb) ? -1.0f :
Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb));
if (sep >= bestSep) {
bestPair[0] = nMaxLb;
bestPair[1] = nMinUb;
bestSep = sep;
foundBestPair = true;
}
}
// In the degenerate case where all points are the same, the above
// algorithm does not find a best pair. Just pick the first 2
// children.
if (!foundBestPair) {
bestPair = new RTree.Node[]{nn.get(0), nn.get(1)};
}
nn.remove(bestPair[0]);
nn.remove(bestPair[1]);
return bestPair;
}
/**
* Implementation of LinearPickNext
*
* @param cc the children to be divided between the new nodes, one item will be removed from this list.
*/
private Node lPickNext(LinkedList<Node> cc) {
return cc.pop();
}
private void tighten(Node... nodes) {
assert (nodes.length >= 1) : "Pass some nodes to tighten!";
for (Node n : nodes) {
assert (n.children.size() > 0) : "tighten() called on empty node!";
float[] minCoords = new float[numDims];
float[] maxCoords = new float[numDims];
for (int i = 0; i < numDims; i++) {
minCoords[i] = Float.MAX_VALUE;
maxCoords[i] = Float.MIN_VALUE;
for (Node c : n.children) {
// we may have bulk-added a bunch of children to a node (eg. in
// splitNode)
// so here we just enforce the child->parent relationship.
c.parent = n;
if (c.coords[i] < minCoords[i]) {
minCoords[i] = c.coords[i];
}
if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) {
maxCoords[i] = (c.coords[i] + c.dimensions[i]);
}
}
}
for (int i = 0; i < numDims; i++) {
// Convert max coords to dimensions
maxCoords[i] -= minCoords[i];
}
System.arraycopy(minCoords, 0, n.coords, 0, numDims);
System.arraycopy(maxCoords, 0, n.dimensions, 0, numDims);
}
}
private RTree<T>.Node chooseLeaf(RTree<T>.Node n, RTree<T>.Entry e) {
if (n.leaf) {
return n;
}
float minInc = Float.MAX_VALUE;
Node next = null;
for (RTree<T>.Node c : n.children) {
float inc = getRequiredExpansion(c.coords, c.dimensions, e);
if (inc < minInc) {
minInc = inc;
next = c;
} else if (inc == minInc) {
float curArea = 1.0f;
float thisArea = 1.0f;
for (int i = 0; i < c.dimensions.length; i++) {
curArea *= next.dimensions[i];
thisArea *= c.dimensions[i];
}
if (thisArea < curArea) {
next = c;
}
}
}
return chooseLeaf(next, e);
}
/**
* Returns the increase in area necessary for the given rectangle to cover the
* given entry.
*/
private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) {
float area = getArea(dimensions);
float[] deltas = new float[dimensions.length];
for (int i = 0; i < deltas.length; i++) {
if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) {
deltas[i] = e.coords[i] + e.dimensions[i] - coords[i] - dimensions[i];
} else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) {
deltas[i] = coords[i] - e.coords[i];
}
}
float expanded = 1.0f;
for (int i = 0; i < dimensions.length; i++) {
expanded *= dimensions[i] + deltas[i];
}
return (expanded - area);
}
private float getArea(float[] dimensions) {
float area = 1.0f;
for (int i = 0; i < dimensions.length; i++) {
area *= dimensions[i];
}
return area;
}
private boolean isOverlap(float[] scoords, float[] sdimensions,
float[] coords, float[] dimensions) {
final float FUDGE_FACTOR = 1.001f;
for (int i = 0; i < scoords.length; i++) {
boolean overlapInThisDimension = false;
if (scoords[i] == coords[i]) {
overlapInThisDimension = true;
} else if (scoords[i] < coords[i]) {
if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) {
overlapInThisDimension = true;
}
} else if (scoords[i] > coords[i]) {
if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) {
overlapInThisDimension = true;
}
}
if (!overlapInThisDimension) {
return false;
}
}
return true;
}
private static class PerformanceTestPanel extends JPanel {
private final int maxX;
private final int maxY;
private final RTree<Box> stringRTree;
private final ArrayList<Box> boxes;
boolean useRTree;
Consumer<Long> listener;
public PerformanceTestPanel(int maxX, int maxY, RTree<Box> stringRTree, ArrayList<Box> boxes, Consumer<Long> listener) {
this.maxX = maxX;
this.maxY = maxY;
this.stringRTree = stringRTree;
this.boxes = boxes;
this.listener = listener;
useRTree = true;
}
public void redispatchToParent(MouseEvent e) {
this.getParent().dispatchEvent(e);
}
public void setUseRTree(boolean useRTree) {
this.useRTree = useRTree;
}
@Override
public Dimension getSize(Dimension rv) {
return new Dimension(maxX, maxY);
}
@Override
public Dimension getPreferredSize() {
return getSize();
}
@Override
protected void paintComponent(Graphics g) {
long start = System.nanoTime();
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Rectangle clipBounds = g.getClipBounds();
//Rectangle clipBounds = getBounds();
List<Box> search = search(stringRTree, clipBounds);
//System.out.println("search.size() = " + search.size());
int arc = 3;
List<Box> toDraw = useRTree ? search : boxes;
if (useRTree) {
toDraw = new ArrayList<>(search);
Collections.sort(toDraw, Comparator.comparing((b) -> b.id));
} else {
toDraw = boxes;
}
for (Box box : toDraw) {
//g2d.setClip(box.x, box.y, box.w, box.h);
//g2d.drawString(box.name, box.x, box.y);
box.paintTo(g2d);
}
long duration = System.nanoTime() - start;
listener.accept(duration);
}
}
private static class MyScrollingAdapter extends MouseAdapter {
private JComponent scrollComponent;
private boolean dragging;
Optional<Point> previous = Optional.empty();
public MyScrollingAdapter(JComponent scrollComponent) {
this.scrollComponent = scrollComponent;
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println("RTree.mousePressed");
startDragging(e);
}
private void startDragging(MouseEvent e) {
dragging = true;
previous = Optional.of(e.getPoint());
}
private void stopDragging() {
dragging = false;
previous = Optional.empty();
}
@Override
public void mouseDragged(MouseEvent e) {
System.out.println("RTree.mouseDragged");
if (!dragging) {
return;
}
if (previous.isPresent()) {
Point current = e.getPoint();
Rectangle visible = scrollComponent.getVisibleRect();
int dx = current.x - previous.get().x;
int dy = current.y - previous.get().y;
System.out.println(String.format("Delta = [%d, %d]", dx, dy));
visible.translate(-dx, -dy);
scrollComponent.scrollRectToVisible(visible);
}
previous = Optional.of(e.getPoint());
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("RTree.mouseReleased");
stopDragging();
}
}
private class Node {
final float[] coords;
final float[] dimensions;
final LinkedList<Node> children;
final boolean leaf;
Node parent;
private Node(float[] coords, float[] dimensions, boolean leaf) {
this.coords = new float[coords.length];
this.dimensions = new float[dimensions.length];
System.arraycopy(coords, 0, this.coords, 0, coords.length);
System.arraycopy(dimensions, 0, this.dimensions, 0, dimensions.length);
this.leaf = leaf;
children = new LinkedList<Node>();
}
}
private class Entry extends Node {
final T entry;
public Entry(float[] coords, float[] dimensions, T entry) {
// an entry isn't actually a leaf (its parent is a leaf)
// but all the algorithms should stop at the first leaf they encounter,
// so this little hack shouldn't be a problem.
super(coords, dimensions, true);
this.entry = entry;
}
public String toString() {
return "Entry: " + entry;
}
}
// The methods below this point can be used to create an HTML rendering
// of the RTree. Maybe useful for debugging?
private static final int elemWidth = 150;
private static final int elemHeight = 120;
String visualize() {
int ubDepth = (int) Math.ceil(Math.log(size) / Math.log(minEntries)) * elemHeight;
int ubWidth = size * elemWidth;
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
pw.println("<html><head></head><body>");
visualize(root, pw, 0, 0, ubWidth, ubDepth);
pw.println("</body>");
pw.flush();
return sw.toString();
}
private void visualize(Node n, java.io.PrintWriter pw, int x0, int y0, int w, int h) {
pw.printf("<div style=\"position:absolute; left: %d; top: %d; width: %d; height: %d; border: 1px dashed\">\n",
x0, y0, w, h);
pw.println("<pre>");
pw.println("Node: " + n.toString() + " (root==" + (n == root) + ") \n");
pw.println("Coords: " + Arrays.toString(n.coords) + "\n");
pw.println("Dimensions: " + Arrays.toString(n.dimensions) + "\n");
pw.println("# Children: " + ((n.children == null) ? 0 : n.children.size()) + "\n");
pw.println("isLeaf: " + n.leaf + "\n");
pw.println("</pre>");
int numChildren = (n.children == null) ? 0 : n.children.size();
for (int i = 0; i < numChildren; i++) {
visualize(n.children.get(i), pw, (int) (x0 + (i * w / (float) numChildren)),
y0 + elemHeight, (int) (w / (float) numChildren), h - elemHeight);
}
pw.println("</div>");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment