/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g2d.routing.spatial; import java.util.ArrayList; import java.util.Collection; import java.util.Random; import org.simantics.g2d.routing.algorithm1.Rectangle; /* * 2-dimensional R-tree */ public class RTree { public static final int MAX_ENTRIES = 7; public static final int MIN_ENTRIES = 3; public static final int MAX_ENTRIES_AFTER_SPLIT = MAX_ENTRIES - MIN_ENTRIES + 1; static interface INode { void search(Rectangle rect, IRectangleProcedure proc); boolean intersects(Rectangle rect); Split add(Rectangle rect, Object value); double[] getRectangles(); Object[] getChildren(); void setCount(int count); Rectangle findBounds(); } static class Split { double oldX0, oldY0, oldX1, oldY1; double newX0, newY0, newX1, newY1; INode newNode; public Split(double oldX0, double oldY0, double oldX1, double oldY1, double newX0, double newY0, double newX1, double newY1, INode newNode) { this.oldX0 = oldX0; this.oldY0 = oldY0; this.oldX1 = oldX1; this.oldY1 = oldY1; this.newX0 = newX0; this.newY0 = newY0; this.newX1 = newX1; this.newY1 = newY1; this.newNode = newNode; } } class Node implements INode { double[] rectangles = new double[MAX_ENTRIES*4]; INode[] children = new INode[MAX_ENTRIES]; int count = 0; public void search(Rectangle rect, IRectangleProcedure proc) { double x0 = rect.x0; double y0 = rect.y0; double x1 = rect.x1; double y1 = rect.y1; for(int i=0,ad=0;i= rectangles[ad] && x0 <= rectangles[ad+2] && y1 >= rectangles[ad+1] && y0 <= rectangles[ad+3]) children[i].search(rect, proc); } public boolean intersects(Rectangle rect) { double x0 = rect.x0; double y0 = rect.y0; double x1 = rect.x1; double y1 = rect.y1; for(int i=0,ad=0;i= rectangles[ad] && x0 <= rectangles[ad+2] && y1 >= rectangles[ad+1] && y0 <= rectangles[ad+3]) if(children[i].intersects(rect)) return true; return false; } @Override public Split add(Rectangle rect, Object value) { double x0 = rect.x0; double y0 = rect.y0; double x1 = rect.x1; double y1 = rect.y1; int bestId = -1; double bestArea = 0.0; double bestAddition = Double.POSITIVE_INFINITY; for(int i=0,ad=0;i x1b) x1b = x1; if(y1 > y1b) y1b = y1; double addition = (x1b-x0b)*(y1b-y0b) - area; if(addition < bestAddition || (addition==bestAddition && area < bestArea)) { bestId = i; bestArea = area; bestAddition = addition; } } Split split = children[bestId].add(rect, value); if(split == null) { if(bestAddition > 0.0) { int ad = bestId * 4; rectangles[ad] = Math.min(rectangles[ad], x0); ++ad; rectangles[ad] = Math.min(rectangles[ad], y0); ++ad; rectangles[ad] = Math.max(rectangles[ad], x1); ++ad; rectangles[ad] = Math.max(rectangles[ad], y1); } } else { int ad = bestId * 4; rectangles[ad] = split.oldX0; ++ad; rectangles[ad] = split.oldY0; ++ad; rectangles[ad] = split.oldX1; ++ad; rectangles[ad] = split.oldY1; if(count < children.length) { ad = count * 4; rectangles[ad] = split.newX0; ++ad; rectangles[ad] = split.newY0; ++ad; rectangles[ad] = split.newX1; ++ad; rectangles[ad] = split.newY1; children[count++] = split.newNode; } else { Node newNode = new Node(); findNodeSeeds(rectangles, children, new Rectangle(split.newX0, split.newY0, split.newX1, split.newY1), split.newNode, newNode.rectangles, newNode.children); return splitNode(this, newNode); } } return null; } @Override public Object[] getChildren() { return children; } @Override public double[] getRectangles() { return rectangles; } @Override public void setCount(int count) { this.count = count; } @Override public Rectangle findBounds() { double x0 = Double.POSITIVE_INFINITY; double y0 = Double.POSITIVE_INFINITY; double x1 = Double.NEGATIVE_INFINITY; double y1 = Double.NEGATIVE_INFINITY; for(int i=0;i= rectangles[ad] && x0 <= rectangles[ad+2] && y1 >= rectangles[ad+1] && y0 <= rectangles[ad+3]) proc.call( rectangles[ad], rectangles[ad+1], rectangles[ad+2], rectangles[ad+3], children[i]); } public boolean intersects(Rectangle rect) { double x0 = rect.x0; double y0 = rect.y0; double x1 = rect.x1; double y1 = rect.y1; for(int i=0,ad=0;i= rectangles[ad] && x0 <= rectangles[ad+2] && y1 >= rectangles[ad+1] && y0 <= rectangles[ad+3]) return true; return false; } @Override public Split add(Rectangle rect, Object value) { if(count < children.length) { int ad = count*4; rectangles[ad++] = rect.x0; rectangles[ad++] = rect.y0; rectangles[ad++] = rect.x1; rectangles[ad] = rect.y1; children[count++] = value; return null; } else { Leaf newLeaf = new Leaf(); findNodeSeeds(rectangles, children, rect, value, newLeaf.rectangles, newLeaf.children); return splitNode(this, newLeaf); } } @Override public Object[] getChildren() { return children; } @Override public double[] getRectangles() { return rectangles; } @Override public void setCount(int count) { this.count = count; } @Override public Rectangle findBounds() { double x0 = Double.POSITIVE_INFINITY; double y0 = Double.POSITIVE_INFINITY; double x1 = Double.NEGATIVE_INFINITY; double y1 = Double.NEGATIVE_INFINITY; for(int i=0;i bestDiff) { bestI = i; bestDiff = diff; bestToB = db < da || (db==da && bCount < aCount); } } if(bestToB) { rotate2(bRectangles, bValues, bCount, aRectangles, aValues, firstUnassigned, bestI); x0b = Math.min(x0b, bRectangles[4*bCount]); y0b = Math.min(y0b, bRectangles[4*bCount+1]); x1b = Math.max(x1b, bRectangles[4*bCount+2]); y1b = Math.max(y1b, bRectangles[4*bCount+3]); ++bCount; ++firstUnassigned; if(bCount == MAX_ENTRIES_AFTER_SPLIT) { while(firstUnassigned < aValues.length) { move(aRectangles, aValues, aCount, firstUnassigned); x0a = Math.min(x0a, aRectangles[4*aCount]); y0a = Math.min(y0a, aRectangles[4*aCount+1]); x1a = Math.max(x1a, aRectangles[4*aCount+2]); y1a = Math.max(y1a, aRectangles[4*aCount+3]); ++aCount; ++firstUnassigned; } break loop; } } else { //System.out.println(aRectangles.length + " " + aValues.length + " " + aCount + " " + bCount + " " + firstUnassigned + " " + bestI); rotate(aRectangles, aValues, aCount, firstUnassigned, bestI); x0a = Math.min(x0a, aRectangles[4*aCount]); y0a = Math.min(y0a, aRectangles[4*aCount+1]); x1a = Math.max(x1a, aRectangles[4*aCount+2]); y1a = Math.max(y1a, aRectangles[4*aCount+3]); ++aCount; ++firstUnassigned; if(aCount == MAX_ENTRIES_AFTER_SPLIT) { while(firstUnassigned < aValues.length) { move2(bRectangles, bValues, bCount, aRectangles, aValues, firstUnassigned); x0b = Math.min(x0b, bRectangles[4*bCount]); y0b = Math.min(y0b, bRectangles[4*bCount+1]); x1b = Math.max(x1b, bRectangles[4*bCount+2]); y1b = Math.max(y1b, bRectangles[4*bCount+3]); ++bCount; ++firstUnassigned; } break loop; } } } a.setCount(aCount); b.setCount(bCount); return new Split(x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b, b); } static void move(double[] array, Object[] oArray, int a, int b) { assert(a != b); oArray[a] = oArray[b]; oArray[b] = null; a *= 4; b *= 4; for(int i=0;i<4;++i) array[a++] = array[b++]; } static void move2(double[] array, Object[] oArray, int a, double[] array2, Object[] oArray2, int b) { oArray[a] = oArray2[b]; oArray2[b] = null; a *= 4; b *= 4; for(int i=0;i<4;++i) array[a++] = array2[b++]; } static void rotate(double[] array, Object[] oArray, int a, int b, int c) { if(a==b) { if(b==c) return; { Object temp = oArray[a]; oArray[a] = oArray[c]; oArray[c] = temp; } a *= 4; c *= 4; for(int i=0;i<4;++i) { double temp = array[a]; array[a] = array[c]; array[c] = temp; ++a; ++c; } } else if(b==c) { { Object temp = oArray[a]; oArray[a] = oArray[c]; oArray[c] = temp; } a *= 4; c *= 4; for(int i=0;i<4;++i) { double temp = array[a]; array[a] = array[c]; array[c] = temp; ++a; ++c; } } else { { Object temp = oArray[b]; oArray[b] = oArray[a]; oArray[a] = oArray[c]; oArray[c] = temp; } a *= 4; b *= 4; c *= 4; for(int i=0;i<4;++i) { double temp = array[b]; array[b] = array[a]; array[a] = array[c]; array[c] = temp; ++a; ++b; ++c; } } } static void rotate2(double[] array, Object[] oArray, int a, double[] array2, Object[] oArray2, int b, int c) { { oArray[a] = oArray2[c]; oArray2[c] = oArray2[b]; oArray2[b] = null; } a *= 4; b *= 4; c *= 4; for(int i=0;i<4;++i) { array[a] = array2[c]; array2[c] = array2[b]; array2[b] = 0.0; ++a; ++b; ++c; } } static void findNodeSeeds(double[] rectangles, Object[] values, Rectangle rect, Object value, double[] newRectangles, Object[] newValues) { int bestI = -1; int bestJ = -1; double bestInefficiency = Double.NEGATIVE_INFINITY; for(int j=0;j<=rectangles.length;j+=4) { double x0b, y0b, x1b, y1b; if(j < rectangles.length) { x0b = rectangles[j]; y0b = rectangles[j+1]; x1b = rectangles[j+2]; y1b = rectangles[j+3]; } else { x0b = rect.x0; y0b = rect.y0; x1b = rect.x1; y1b = rect.y1; } double areaB = (x1b-x0b) * (y1b-y0b); for(int i=0;i bestInefficiency) { bestI = i-4; bestJ = j; bestInefficiency = inefficiency; } } } if(bestJ==rectangles.length) { newRectangles[0] = rect.x0; newRectangles[1] = rect.y0; newRectangles[2] = rect.x1; newRectangles[3] = rect.y1; newValues[0] = value; } else { newValues[0] = values[bestJ/4]; values[bestJ/4] = value; newRectangles[0] = rectangles[bestJ++]; newRectangles[1] = rectangles[bestJ++]; newRectangles[2] = rectangles[bestJ++]; newRectangles[3] = rectangles[bestJ]; bestJ -= 3; rectangles[bestJ++] = rect.x0; rectangles[bestJ++] = rect.y0; rectangles[bestJ++] = rect.x1; rectangles[bestJ] = rect.y1; } if(bestI > 0) { Object temp2 = values[0]; values[0] = values[bestI/4]; values[bestI/4] = temp2; double temp; temp = rectangles[0]; rectangles[0] = rectangles[bestI]; rectangles[bestI++] = temp; temp = rectangles[1]; rectangles[1] = rectangles[bestI]; rectangles[bestI++] = temp; temp = rectangles[2]; rectangles[2] = rectangles[bestI]; rectangles[bestI++] = temp; temp = rectangles[3]; rectangles[3] = rectangles[bestI]; rectangles[bestI++] = temp; } } INode root = new Leaf(); public void search(Rectangle rect, IRectangleProcedure proc) { root.search(rect, proc); } public boolean intersects(Rectangle rect) { return root.intersects(rect); } public void add(Rectangle rect, Object obj) { Split split = root.add(rect, obj); if(split != null) { Node node = new Node(); node.rectangles[0] = split.oldX0; node.rectangles[1] = split.oldY0; node.rectangles[2] = split.oldX1; node.rectangles[3] = split.oldY1; node.rectangles[4] = split.newX0; node.rectangles[5] = split.newY0; node.rectangles[6] = split.newX1; node.rectangles[7] = split.newY1; node.children[0] = root; node.children[1] = split.newNode; node.count = 2; root = node; } } static Random random = new Random(); public static Rectangle randomRectangle() { double x = random.nextDouble(); double y = random.nextDouble(); double w = random.nextDouble()*0.1; double h = random.nextDouble()*0.1; return new Rectangle(x, y, x+w, y+h); } public static void main(String[] args) { Collection rects = new ArrayList(); for(int i=0;i<1000;++i) rects.add(randomRectangle()); RTree tree = new RTree(); for(Rectangle rect : rects) { tree.add(rect, rect); tree.root.findBounds(); } for(int i=0;i<100;++i) { final Rectangle q = randomRectangle(); int iCount = 0; for(Rectangle rect : rects) if(q.intersects(rect)) ++iCount; final int[] iCount2 = new int[] { 0 }; tree.search(q, new IRectangleProcedure() { @Override public void call(double x0, double y0, double x1, double y1, Object value) { ++iCount2[0]; Rectangle r = (Rectangle)value; if(!r.intersects(q)) System.out.println("Not really intersecting!"); if( x0 != r.x0 ||y0 != r.y0 ||x1 != r.x1 ||y1 != r.y1) System.out.println("Value and key differ!" + "(" + x0 + " " + y0 + " " + x1 + " " + y1 + ") " + "(" + r.x0 + " " + r.y0 + " " + r.x1 + " " + r.y1 + ")"); } }); if(iCount != iCount2[0]) System.out.println("Different iCount: " + iCount + " " + iCount2[0]); } } }