版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/MongChia1993/article/details/69941783
- 第一部分 空間數據的背景介紹
- 空間數據的建模
- 基于實體的模型(基于對象)Entity-based models (or object based)
- 常用的空間數據查詢方式
- 空間數據獲取的方法
- R樹
- 簡介
- R樹的數據結構
- 一個更具體的使用場景
- 一棵R樹滿足如下的性質:
- 結點的結構
- R樹的操作
-
- 第二部分 R樹的Java實現
第一部分 空間數據的背景介紹
空間數據的建模
基于實體的模型(基于對象)Entity-based models (or object based)
- 0-dimensional objects?: 一般使用點point來表示那些對于不需要使用到形狀信息的實體。
- 1-dimensional objects or linear objects: 用于表示一些路網的邊,一般用于表示道路road。 (polyline)
- 2-dimensional objects or surfacic objects: 用于表示有區域面積的實體。 (polygon)
常用的空間數據查詢方式
- 窗口查詢:給定一個查詢窗口(通常是一個矩形),返回與查詢窗口相重疊的物體。

點查詢:給定一個點,返回包含這個點的所有幾何圖形。
空間數據獲取的方法

表示一個點的數據:
public class Point{ public Float x; public Float y } 表示一個MBR的數據
public class MBR{ public Point BottomLeft; public Point TopRight; } - 如何判斷兩個MBR是否相交? >如果一個MBR的TopLeft或者BottomRight的(x,y)位于另一個MBR的xRange和yRangle里面,則說明這兩個MBR相交。

R樹
對于B/B+-Trees 由于它的線性特點,通常用來索引一維數據。(比它大的往一邊走,比它小的往一邊走,但只是在一個維度下進行比較)。
B樹是一棵平衡樹,它是把一維直線分為若干段線段,當我們查找滿足某個要求的點的時候,只要去查找它所屬的線段即可。這種思想其實就是先找一個大的空間,再逐步縮小所要查找的空間,最終在一個自己設定的最小不可分空間內找出滿足要求的解。一個典型的B樹查找如下:


要查找某一滿足條件的點,先去找到滿足條件的線段,然后遍歷所在線段上的點,即可找到答案。B樹是一種相對來說比較復雜的數據結構,尤其是在它的刪除與插入操作過程中,因為它涉及到了葉子結點的分解與合并。
簡介
B樹是解決低緯度數據(通常一維,也就是一個數據維度上進行比較),R樹很好的解決了這種高維空間搜索問題。它把B樹的思想很好的擴展到了多維空間,采用了B樹分割空間的思想(如果B樹在一維的線段進行分割,R樹就是在二維甚至多維度的空間),并在添加、刪除操作時采用合并、分解結點的方法,保證樹的平衡性。因此,R樹就是一棵用來存儲高維數據的平衡樹。

我們說過,B樹是采用切分線段來縮小數據查詢范圍的一種思想,我們又說了,R樹是b樹的多維版,以及R樹也采用了B樹的這一種分割的思想,那么,如果說線段的分割是一維的分割。那二維的分割就應該是區域的分割,而三維的就是幾何空間的分割了。要注意的是R樹并不只是二維空間數據的索引而已,它還可以索引三維甚至更高維。
一個三維的R樹

此外R樹還可以退化成一維,但是分割的線段存在重疊問題,效果不如Btree。

R樹的數據結構
如上所述,R樹是B樹在高維空間的擴展,是一棵平衡樹。每個R樹的葉子結點包含了多個指向不同數據的指針,這些數據可以是存放在硬盤中的,也可以是存在內存中。
根據R樹的這種數據結構,當我們需要進行一個高維空間查詢時,我們只需要遍歷少數幾個葉子結點所包含的指針(即縮小到某個區域下去進行查詢,還是采用縮小范圍的思想),查看這些指針指向的數據是否滿足要求即可。這種方式使我們不必遍歷所有數據即可獲得答案,效率顯著提高。下圖1是R樹的一個簡單實例:


解釋一下這張圖。
- 首先我們假設所有數據都是二維空間下的幾何形狀,圖中僅僅標志了R8,R9,R10區域中的數據,其他的葉子節點僅僅用MBB表示。為了實現R樹結構,我們用一個最小邊界矩形恰好框住這個不規則區域,這樣,我們就構造出了一個區域:R8。R8的特點很明顯,就是正正好好框住所有在此區域中的數據。其他實線包圍住的區域,如R9,R10,R11等都是同樣的道理。這樣一來,我們一共得到了12個最最基本的最小矩形。這些矩形都將被存儲在子結點中。
- 下一步操作就是進行高一層次的處理。我們發現R8,R9,R10三個矩形距離最為靠近,因此就可以用一個更大的矩形R3恰好框住這3個矩形。
- 同樣道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小邊界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住這些矩形。
用地圖的例子來解釋,就是所有的數據都是餐廳所對應的地點,先把相鄰的餐廳劃分到同一塊區域,劃分好所有餐廳之后,再把鄰近的區域劃分到更大的區域,劃分完畢后再次進行更高層次的劃分,直到劃分到只剩下兩個最大的區域為止。要查找的時候就方便了。
下面就可以把這些大大小小的矩形存入我們的R樹中去了。根結點存放的是兩個最大的矩形,這兩個最大的矩形框住了所有的剩余的矩形,當然也就框住了所有的數據。下一層的結點存放了次大的矩形,這些矩形縮小了范圍。每個葉子結點都是存放的最小的矩形,這些矩形中可能包含有n個數據。
以餐廳為例,假設我要查詢廣州市天河區天河城附近一公里的所有餐廳地址怎么辦?
- 打開地圖(也就是整個R樹),先選擇國內還是國外(也就是根結點)。
- 然后選擇華南地區(對應第一層結點),選擇廣州市(對應第二層結點),
- 再選擇天河區(對應第三層結點),
- 最后選擇天河城所在的那個區域(對應葉子結點,存放有最小矩形),遍歷所有在此區域內的結點,看是否滿足我們的要求即可。
R樹的查找規則跟查地圖很像吧?對應下圖:

一個更具體的使用場景
假設我們有一個地圖路網要進行道路的快速索引,那么我們可以將每一條路的最小MBB作為R樹的數據單元來進行構建R樹。

每一條路使用一個最小MBB來進行包裹,使它成為R樹的葉子結點(也就是那些數據結點)

(這里采用的是R樹的改進版本R*樹)然后對于建立起來的R樹在進行查找道路的使用就可以使用我們那種“縮小范圍”的查找思想。從上往下一層一層查找。

一棵R樹滿足如下的性質:
- 1. 除非它是根結點之外,所有葉子結點包含有m至M個記錄索引(條目)。作為根結點的葉子結點所具有的記錄個數可以少于m。通常,m=M/2。
- 2. 對于所有在葉子中存儲的記錄(條目),I是最小的可以在空間中完全覆蓋這些記錄所代表的點的矩形(注意:此處所說的“矩形”是可以擴展到高維空間的)。
- 3. 每一個非葉子結點擁有m至M個孩子結點,除非它是根結點。
- 4. 對于在非葉子結點上的每一個條目,i是最小的可以在空間上完全覆蓋這些條目所代表的點的矩形(同性質2)。
- 5. 所有葉子結點都位于同一層,因此R樹為平衡樹。
結點的結構
先來探究一下葉子結點的結構。葉子結點所保存的數據形式為:(I, tuple-identifier)。
其中,tuple-identifier表示的是一個存放于數據庫中的tuple,也就是一條記錄,它是n維的。I是一個n維空間的矩形,并可以恰好框住這個葉子結點中所有記錄代表的n維空間中的點。I=(I0,I1,…,In-1)。其結構如下圖所示:

Rectangle代表可以包裹E1,E2,E3,E4.E5的最小限度框。

R樹的操作
搜索
R樹的搜索操作很簡單,跟B樹上的搜索十分相似。它返回的結果是所有符合查找信息的記錄條目。而輸入是什么?輸入不僅僅是一個范圍了,它更可以看成是一個空間中的矩形。也就是說,我們輸入的是一個搜索矩形。
先給出偽代碼:
Function:Search
描述:假設T為一棵R樹的根結點,查找所有搜索矩形S覆蓋的記錄條目。
- S1:[查找子樹] 如果T是非葉子結點,如果T所對應的矩形與S有重合,那么檢查所有T中存儲的條目,對于所有這些條目,使用Search操作作用在每一個條目所指向的子樹的根結點上(即T結點的孩子結點)。
- S2:[查找葉子結點] 如果T是葉子結點,如果T所對應的矩形與S有重合,那么直接檢查S所指向的所有記錄條目。返回符合條件的記錄。
我們通過下圖來理解這個Search操作。

紅色查詢區域與P3子樹P4子樹相重疊,所以根據“縮小空間”的思想,只需要遍歷P3和P4所在子樹就行而無需遍歷P1,P2.
插入
插入操作存在的情況
R樹的插入操作也同B樹的插入操作類似。當新的數據記錄需要被添加入葉子結點時,若葉子結點溢出,那么我們需要對葉子結點進行分裂操作。顯然,葉子結點的插入操作會比搜索操作要復雜。插入操作需要一些輔助方法才能夠完成。
來看一下偽代碼:
【Function:Insert】
描述:將新的記錄條目E插入給定的R樹中。
- I1:[為新記錄找到合適插入的葉子結點]開始ChooseLeaf方法選擇葉子結點L以放置記錄E。
- I2:[添加新記錄至葉子結點] 如果L有足夠的空間來放置新的記錄條目,則向L中添加E。如果沒有足夠的空間,則進行SplitNode方法以獲得兩個結點L與LL,這兩個結點包含了所有原來葉子結點L中的條目與新條目E。
- I3:[將變換向上傳遞] 開始對結點L進行AdjustTree操作,如果進行了分裂操作,那么同時需要對LL進行AdjustTree操作。
- I4:[對樹進行增高操作] 如果結點分裂,且該分裂向上傳播導致了根結點的分裂,那么需要創建一個新的根結點,并且讓它的兩個孩子結點分別為原來那個根結點分裂后的兩個結點。
?
【Function:ChooseLeaf】
描述:選擇葉子結點以放置新條目E。
- CL1:[Initialize]設置N為根結點。
- CL2:[葉子結點的檢查] 如果N為葉子結點,則直接返回N。
- CL3:[選擇子樹] 如果N不是葉子結點,則遍歷N中的結點,找出添加E.I時擴張最小的結點,并把該結點定義為F。如果有多個這樣的結點,那么選擇面積最小的結點。
- CL4:[下降至葉子結點] 將N設為F,從CL2開始重復操作。
【Function:AdjustTree】
描述:葉子結點的改變向上傳遞至根結點以改變各個矩陣。在傳遞變換的過程中可能會產生結點的分裂。
- AT1:[初始化] 將N設為L。
- AT2:[檢驗是否完成] 如果N為根結點,則停止操作。
- AT3:[調整父結點條目的最小邊界矩形] 設P為N的父節點,EN為指向在父節點P中指向N的條目。調整EN.I以保證所有在N中的矩形都被恰好包圍。
- AT4:[向上傳遞結點分裂] 如果N有一個剛剛被分裂產生的結點NN,則創建一個指向NN的條目ENN。如果P有空間來存放ENN,則將ENN添加到P中。如果沒有,則對P進行SplitNode操作以得到P和PP。
- AT5:[升高至下一級] 如果N等于L且發生了分裂,則把NN置為PP。從AT2開始重復操作。
有足夠的空間插入的情況,由于插入的x所在的區域P2的數據條目仍然有足夠的空間容納條目x,且x的區域面積即MBR也位于區域P2之內,所以這種情況下,我們認為x擁有足夠的插入空間。

需要增大MBR的插入情況,由于插入的y所在的區域P2的數據條目仍然有足夠的空間容納條目y,但是y的區域面積即MBR并不完全位于P2的區域之內,因此我們在插入數據y后會導致P2區域的相應擴大。

需要進行分裂的插入情況,由于插入的w所在的區域P1的數據條目已經沒有足夠的空間容納條目w,因為假設我們定義R樹的階m=4,而區域P1已經容納了四個條目「A,B,C,K」了,插入w后孩子數為5,以及超過m=4了,所以要進行分類操作,來保證樹的平衡性。

采用分裂算法(下面會進行介紹)對結點(或者說區域)P2進行合理地分裂。使其分裂成P1(包含A,B)和P5(包含k,w)兩個結點。并且需要向上傳遞這種分裂。由于分裂之后原來根結點「P1,P2,P3,P4」變成了「P1,P2,P3,P,P5」,因此根結點的孩子數由4變成5,超過了階數m=4.所以根結點要(采用我們的分裂算法)進行分裂,分裂成Q1(包含P1,P5,P2)和Q2(包含P3,P4)兩個結點,由于此時分裂已經傳遞到根結點,所以生成新的根結點記錄Q1,Q2。

如何合理地分裂到兩個組

挑選種子有多個方法,這里Quadratic(二次方)方案,對于所有條目中的每一對E1和E2,計算可以包裹著E1,E2的最小限定框J=MBR(E1, E2) ,然后計算增量d= J-E1-E2.計算結束后選擇d最大的一對(即增量最大)。(這里為什么要這樣呢:之所以要挑選d最大的一對,是因為如果d較大,說明挑選出來的兩對條目基于對角線離得比較遠,這樣的好處就是分裂后的兩個分組可以盡量不重疊)


挑選出seed1和seed2之后,就將剩下的要分配的分裂條目分個這兩個seed使它們各稱為一個組。而這個分配的原則就是離誰比較“近”就和誰一組。這里的“近”指的是任何一個條目MBB--E和seed1,seed2分別計算可以包裹著E和seed1的最小限定框J1=MBR(E,seed1), 可以包裹著E和seed2的最小限定框J2=MBR(E,seed2)。再分別計算增量d1=J1-E-seed1,d2=J2-E-seed2。d1,d2哪個小就說明哪個“近”。

(-_-)!!!所以分裂的具體情況還是很復雜的,真想不懂這些大神怎么會想得到這些。除了上述Quadratic的分裂算法之外還有其他的分裂算法,如下圖中間圖,但是分裂的效果都不如R*樹(R樹的改進版)的算法好。


刪除
R樹的刪除操作與B樹的刪除操作會有所不同,不過同B樹一樣,會涉及到壓縮等操作。相信讀者看完以下的偽代碼之后會有所體會。R樹的刪除同樣是比較復雜的,需要用到一些輔助函數來完成整個操作。
偽代碼如下:
【Function:Delete】
描述:將一條記錄E從指定的R樹中刪除。
D1:[找到含有記錄的葉子結點] 使用FindLeaf方法找到包含有記錄E的葉子結點L。如果搜索失敗,則直接終止。
D2:[刪除記錄] 將E從L中刪除。
D3:[傳遞記錄] 對L使用CondenseTree操作
D4:[縮減樹] 當經過以上調整后,如果根結點只包含有一個孩子結點,則將這個唯一的孩子結點設為根結點。
【Function:FindLeaf】
描述:根結點為T,期望找到包含有記錄E的葉子結點。
FL1:[搜索子樹] 如果T不是葉子結點,則檢查每一條T中的條目F,找出與E所對應的矩形相重合的F(不必完全覆蓋)。對于所有滿足條件的F,對其指向的孩子結點進行FindLeaf操作,直到尋找到E或者所有條目均以被檢查過。
FL2:[搜索葉子結點以找到記錄] 如果T是葉子結點,那么檢查每一個條目是否有E存在,如果有則返回T。
【Function:CondenseTree】
描述:L為包含有被刪除條目的葉子結點。如果L的條目數過少(小于要求的最小值m),則必須將該葉子結點L從樹中刪除。經過這一刪除操作,L中的剩余條目必須重新插入樹中。此操作將一直重復直至到達根結點。同樣,調整在此修改樹的過程所經過的路徑上的所有結點對應的矩形大小。
CT1:[初始化] 令N為L。初始化一個用于存儲被刪除結點包含的條目的鏈表Q。
CT2:[找到父條目] 如果N為根結點,那么直接跳轉至CT6。否則令P為N 的父結點,令EN為P結點中存儲的指向N的條目。
CT3:[刪除下溢結點] 如果N含有條目數少于m,則從P中刪除EN,并把結點N中的條目添加入鏈表Q中。
CT4:[調整覆蓋矩形] 如果N沒有被刪除,則調整EN.I使得其對應矩形能夠恰好覆蓋N中的所有條目所對應的矩形。
CT5:[向上一層結點進行操作] 令N等于P,從CT2開始重復操作。
CT6:[重新插入孤立的條目] 所有在Q中的結點中的條目需要被重新插入。原來屬于葉子結點的條目可以使用Insert操作進行重新插入,而那些屬于非葉子結點的條目必須插入刪除之前所在層的結點,以確保它們所指向的子樹還處于相同的層。
R樹刪除記錄過程中的CondenseTree操作是不同于B樹的。我們知道,B樹刪除過程中,如果出現結點的記錄數少于半滿(即下溢)的情況,則直接把這些記錄與其他葉子的記錄“融合”,也就是說兩個相鄰結點合并。然而R樹卻是直接重新插入。
具體的例子

假設結點最大條目數為4,最小條目數為2。在這張圖中,我們的目標是刪除記錄c。首先使用FindLeaf操作找到c所處在的葉子結點的位置——R11。當c從R11刪除時,R11就只有一條記錄了,少于最小條目數2,出現下溢,此時要調用CondenseTree操作。這樣,c被刪除,R11剩余的條目——指向記錄d的指針——被插入鏈表Q。然后向更高一層的結點進行此操作。這樣R12會被插入鏈表中。原理是一樣的,在這里就不再贅述。

有一點需要解釋的是,我們發現這個刪除操作向上傳遞之后,根結點的條目R1也被插入了Q中,這樣根結點只剩下了R2。別著急,重新插入操作會有效的解決這個問題。我們插入R3,R12,d至它原來所處的層。這樣,我們發現根結點只有一個條目了,此時根據Inert中的操作,我們把這個根結點刪除,它的孩子結點,即R5,R6,R7,R3所在的結點被置為根結點。至此,刪除操作結束。
另一個例子再次理解刪除










第二部分 R樹的Java實現
UML

Point
package rtree; public class Point implements Cloneable { private float[] data; public Point(float[] data) { if (data == null) { throw new IllegalArgumentException("Coordinates cannot be null."); } if (data.length < 2) { throw new IllegalArgumentException("Point dimension should be greater than 1."); } this.data = new float[data.length]; System.arraycopy(data, 0, this.data, 0, data.length); } public Point(int[] data) { if (data == null) { throw new IllegalArgumentException("Coordinates cannot be null."); } if (data.length < 2) { throw new IllegalArgumentException("Point dimension should be greater than 1."); } this.data = new float[data.length]; for (int i = 0; i < data.length; i++) { this.data[i] = data[i]; } } @Override protected Object clone() { float[] copy = new float[data.length]; System.arraycopy(data, 0, copy, 0, data.length); return new Point(copy); } @Override public String toString() { StringBuffer sBuffer = new StringBuffer("("); for (int i = 0; i < data.length - 1; i++) { sBuffer.append(data[i]).append(","); } sBuffer.append(data[data.length - 1]).append(")"); return sBuffer.toString(); } public static void main(String[] args) { float[] test = { 1.2f, 2f, 34f }; Point point1 = new Point(test); System.out.println(point1); int[] test2 = { 1, 2, 3, 4 }; point1 = new Point(test2); System.out.println(point1); int[] test3 = { 11, 22 }; point1 = new Point(test3); System.out.println(point1); } public int getDimension() { return data.length; } public float getFloatCoordinate(int index) { return data[index]; } public int getIntCoordinate(int index) { return (int) data[index]; } @Override public boolean equals(Object obj) { if (obj instanceof Point) { Point point = (Point) obj; if (point.getDimension() != getDimension()) throw new IllegalArgumentException("Points must be of equal dimensions to be compared."); for (int i = 0; i < getDimension(); i++) { if (getFloatCoordinate(i) != point.getFloatCoordinate(i)) return false; } } if (!(obj instanceof Point)) return false; return true; } } Rectangle
package rtree; public class Rectangle implements Cloneable // 繼承克隆接口 { private Point low; private Point high; public Rectangle(Point p1, Point p2) { if (p1 == null || p2 == null) { throw new IllegalArgumentException("Points cannot be null."); } if (p1.getDimension() != p2.getDimension()) { throw new IllegalArgumentException("Points must be of same dimension."); } for (int i = 0; i < p1.getDimension(); i++) { if (p1.getFloatCoordinate(i) > p2.getFloatCoordinate(i)) { throw new IllegalArgumentException("坐標點為先左下角后右上角"); } } low = (Point) p1.clone(); high = (Point) p2.clone(); } public Point getLow() { return (Point) low.clone(); } public Point getHigh() { return high; } public Rectangle getUnionRectangle(Rectangle rectangle) { if (rectangle == null) throw new IllegalArgumentException("Rectangle cannot be null."); if (rectangle.getDimension() != getDimension()) { throw new IllegalArgumentException("Rectangle must be of same dimension."); } float[] min = new float[getDimension()]; float[] max = new float[getDimension()]; for (int i = 0; i < getDimension(); i++) { min[i] = Math.min(low.getFloatCoordinate(i), rectangle.low.getFloatCoordinate(i)); max[i] = Math.max(high.getFloatCoordinate(i), rectangle.high.getFloatCoordinate(i)); } return new Rectangle(new Point(min), new Point(max)); } public float getArea() { float area = 1; for (int i = 0; i < getDimension(); i++) { area *= high.getFloatCoordinate(i) - low.getFloatCoordinate(i); } return area; } public static Rectangle getUnionRectangle(Rectangle[] rectangles) { if (rectangles == null || rectangles.length == 0) throw new IllegalArgumentException("Rectangle array is empty."); Rectangle r0 = (Rectangle) rectangles[0].clone(); for (int i = 1; i < rectangles.length; i++) { r0 = r0.getUnionRectangle(rectangles[i]); } return r0; } @Override protected Object clone() { Point p1 = (Point) low.clone(); Point p2 = (Point) high.clone(); return new Rectangle(p1, p2); } @Override public String toString() { return "Rectangle Low:" + low + " High:" + high; } public static void main(String[] args) { float[] f1 = { 1.3f, 2.4f }; float[] f2 = { 3.4f, 4.5f }; Point p1 = new Point(f1); Point p2 = new Point(f2); Rectangle rectangle = new Rectangle(p1, p2); System.out.println(rectangle); float[] f_1 = { -2f, 0f }; float[] f_2 = { 0f, 2f }; float[] f_3 = { -2f, 1f }; float[] f_4 = { 3f, 3f }; float[] f_5 = { 1f, 0f }; float[] f_6 = { 2f, 4f }; p1 = new Point(f_1); p2 = new Point(f_2); Point p3 = new Point(f_3); Point p4 = new Point(f_4); Point p5 = new Point(f_5); Point p6 = new Point(f_6); Rectangle re1 = new Rectangle(p1, p2); Rectangle re2 = new Rectangle(p3, p4); Rectangle re3 = new Rectangle(p5, p6); System.out.println(re1.isIntersection(re2)); System.out.println(re1.isIntersection(re3)); System.out.println(re1.intersectingArea(re2)); System.out.println(re1.intersectingArea(re3)); } public float intersectingArea(Rectangle rectangle) { if (!isIntersection(rectangle)) { return 0; } float ret = 1; for (int i = 0; i < rectangle.getDimension(); i++) { float l1 = this.low.getFloatCoordinate(i); float h1 = this.high.getFloatCoordinate(i); float l2 = rectangle.low.getFloatCoordinate(i); float h2 = rectangle.high.getFloatCoordinate(i); if (l1 <= l2 && h1 <= h2) { ret *= (h1 - l1) - (l2 - l1); } else if (l1 >= l2 && h1 >= h2) { ret *= (h2 - l2) - (l1 - l2); } else if (l1 >= l2 && h1 <= h2) { ret *= h1 - l1; } else if (l1 <= l2 && h1 >= h2) { ret *= h2 - l2; } } return ret; } public boolean isIntersection(Rectangle rectangle) { if (rectangle == null) throw new IllegalArgumentException("Rectangle cannot be null."); if (rectangle.getDimension() != getDimension()) { throw new IllegalArgumentException("Rectangle cannot be null."); } for (int i = 0; i < getDimension(); i++) { if (low.getFloatCoordinate(i) > rectangle.high.getFloatCoordinate(i) || high.getFloatCoordinate(i) < rectangle.low.getFloatCoordinate(i)) { return false; } } return true; } private int getDimension() { return low.getDimension(); } public boolean enclosure(Rectangle rectangle) { if (rectangle == null) throw new IllegalArgumentException("Rectangle cannot be null."); if (rectangle.getDimension() != getDimension()) throw new IllegalArgumentException("Rectangle dimension is different from current dimension."); for (int i = 0; i < getDimension(); i++) { if (rectangle.low.getFloatCoordinate(i) < low.getFloatCoordinate(i) || rectangle.high.getFloatCoordinate(i) > high.getFloatCoordinate(i)) return false; } return true; } @Override public boolean equals(Object obj) { if (obj instanceof Rectangle) { Rectangle rectangle = (Rectangle) obj; if (low.equals(rectangle.getLow()) && high.equals(rectangle.getHigh())) return true; } return false; } } RTNode
package rtree; import java.util.List; import rtree.Constants; public abstract class RTNode { protected RTree rtree; protected int level; protected Rectangle[] datas; protected RTNode parent; protected int usedSpace; protected int insertIndex; protected int deleteIndex; public RTNode(RTree rtree, RTNode parent, int level) { this.rtree = rtree; this.parent = parent; this.level = level; datas = new Rectangle[rtree.getNodeCapacity() + 1]; usedSpace = 0; } public RTNode getParent() { return parent; } protected void addData(Rectangle rectangle) { if (usedSpace == rtree.getNodeCapacity()) { throw new IllegalArgumentException("Node is full."); } datas[usedSpace++] = rectangle; } protected void deleteData(int i) { if (datas[i + 1] != null) { System.arraycopy(datas, i + 1, datas, i, usedSpace - i - 1); datas[usedSpace - 1] = null; } else datas[i] = null; usedSpace--; } protected void condenseTree(List<RTNode> list) { if (isRoot()) { if (!isLeaf() && usedSpace == 1) { RTDirNode root = (RTDirNode) this; RTNode child = root.getChild(0); root.children.remove(this); child.parent = null; rtree.setRoot(child); } } else { RTNode parent = getParent(); int min = Math.round(rtree.getNodeCapacity() * rtree.getFillFactor()); if (usedSpace < min) { parent.deleteData(parent.deleteIndex); ((RTDirNode) parent).children.remove(this); this.parent = null; list.add(this); } else { parent.datas[parent.deleteIndex] = getNodeRectangle(); } parent.condenseTree(list); } } protected int[][] quadraticSplit(Rectangle rectangle) { if (rectangle == null) { throw new IllegalArgumentException("Rectangle cannot be null."); } datas[usedSpace] = rectangle; int total = usedSpace + 1; int[] mask = new int[total]; for (int i = 0; i < total; i++) { mask[i] = 1; } int c = total / 2 + 1; int minNodeSize = Math.round(rtree.getNodeCapacity() * rtree.getFillFactor()); if (minNodeSize < 2) minNodeSize = 2; int rem = total; int[] group1 = new int[c]; int[] group2 = new int[c]; int i1 = 0, i2 = 0; int[] seed = pickSeeds(); group1[i1++] = seed[0]; group2[i2++] = seed[1]; rem -= 2; mask[group1[0]] = -1; mask[group2[0]] = -1; while (rem > 0) { if (minNodeSize - i1 == rem) { for (int i = 0; i < total; i++) { if (mask[i] != -1) { group1[i1++] = i; mask[i] = -1; rem--; } } } else if (minNodeSize - i2 == rem) { for (int i = 0; i < total; i++) { if (mask[i] != -1) { group2[i2++] = i; mask[i] = -1; rem--; } } } else { Rectangle mbr1 = (Rectangle) datas[group1[0]].clone(); for (int i = 1; i < i1; i++) { mbr1 = mbr1.getUnionRectangle(datas[group1[i]]); } Rectangle mbr2 = (Rectangle) datas[group2[0]].clone(); for (int i = 1; i < i2; i++) { mbr2 = mbr2.getUnionRectangle(datas[group2[i]]); } double dif = Double.NEGATIVE_INFINITY; double areaDiff1 = 0, areaDiff2 = 0; int sel = -1; for (int i = 0; i < total; i++) { if (mask[i] != -1) { Rectangle a = mbr1.getUnionRectangle(datas[i]); areaDiff1 = a.getArea() - mbr1.getArea(); Rectangle b = mbr2.getUnionRectangle(datas[i]); areaDiff2 = b.getArea() - mbr2.getArea(); if (Math.abs(areaDiff1 - areaDiff2) > dif) { dif = Math.abs(areaDiff1 - areaDiff2); sel = i; } } } if (areaDiff1 < areaDiff2) { group1[i1++] = sel; } else if (areaDiff1 > areaDiff2) { group2[i2++] = sel; } else if (mbr1.getArea() < mbr2.getArea()) { group1[i1++] = sel; } else if (mbr1.getArea() > mbr2.getArea()) { group2[i2++] = sel; } else if (i1 < i2) { group1[i1++] = sel; } else if (i1 > i2) { group2[i2++] = sel; } else { group1[i1++] = sel; } mask[sel] = -1; rem--; } } int[][] ret = new int[2][]; ret[0] = new int[i1]; ret[1] = new int[i2]; for (int i = 0; i < i1; i++) { ret[0][i] = group1[i]; } for (int i = 0; i < i2; i++) { ret[1][i] = group2[i]; } return ret; } protected int[] pickSeeds() { double inefficiency = Double.NEGATIVE_INFINITY; int i1 = 0, i2 = 0; for (int i = 0; i < usedSpace; i++) { for (int j = i + 1; j <= usedSpace; j++) { Rectangle rectangle = datas[i].getUnionRectangle(datas[j]); double d = rectangle.getArea() - datas[i].getArea() - datas[j].getArea(); if (d > inefficiency) { inefficiency = d; i1 = i; i2 = j; } } } return new int[] { i1, i2 }; } public Rectangle getNodeRectangle() { if (usedSpace > 0) { Rectangle[] rectangles = new Rectangle[usedSpace]; System.arraycopy(datas, 0, rectangles, 0, usedSpace); return Rectangle.getUnionRectangle(rectangles); } else { return new Rectangle(new Point(new float[] { 0, 0 }), new Point(new float[] { 0, 0 })); } } public boolean isRoot() { return (parent == Constants.NULL); } public boolean isIndex() { return (level != 0); } public boolean isLeaf() { return (level == 0); } protected abstract RTDataNode chooseLeaf(Rectangle rectangle); protected abstract RTDataNode findLeaf(Rectangle rectangle); } RTDataNode
package rtree; import java.util.ArrayList; import java.util.List; import rtree.Constants; public class RTDataNode extends RTNode { public RTDataNode(RTree rTree, RTNode parent) { super(rTree, parent, 0); } public boolean insert(Rectangle rectangle) { if (usedSpace < rtree.getNodeCapacity()) { datas[usedSpace++] = rectangle; RTDirNode parent = (RTDirNode) getParent(); if (parent != null) parent.adjustTree(this, null); return true; } else { RTDataNode[] splitNodes = splitLeaf(rectangle); RTDataNode l = splitNodes[0]; RTDataNode ll = splitNodes[1]; if (isRoot()) { RTDirNode rDirNode = new RTDirNode(rtree, Constants.NULL, level + 1); rtree.setRoot(rDirNode); rDirNode.addData(l.getNodeRectangle()); rDirNode.addData(ll.getNodeRectangle()); ll.parent = rDirNode; l.parent = rDirNode; rDirNode.children.add(l); rDirNode.children.add(ll); } else { RTDirNode parentNode = (RTDirNode) getParent(); parentNode.adjustTree(l, ll); } } return true; } public RTDataNode[] splitLeaf(Rectangle rectangle) { int[][] group = null; switch (rtree.getTreeType()) { case Constants.RTREE_LINEAR: break; case Constants.RTREE_QUADRATIC: group = quadraticSplit(rectangle); break; case Constants.RTREE_EXPONENTIAL: break; case Constants.RSTAR: break; default: throw new IllegalArgumentException("Invalid tree type."); } RTDataNode l = new RTDataNode(rtree, parent); RTDataNode ll = new RTDataNode(rtree, parent); int[] group1 = group[0]; int[] group2 = group[1]; for (int i = 0; i < group1.length; i++) { l.addData(datas[group1[i]]); } for (int i = 0; i < group2.length; i++) { ll.addData(datas[group2[i]]); } return new RTDataNode[] { l, ll }; } @Override public RTDataNode chooseLeaf(Rectangle rectangle) { insertIndex = usedSpace; return this; } protected int delete(Rectangle rectangle) { for (int i = 0; i < usedSpace; i++) { if (datas[i].equals(rectangle)) { deleteData(i); List<RTNode> deleteEntriesList = new ArrayList<RTNode>(); condenseTree(deleteEntriesList); for (int j = 0; j < deleteEntriesList.size(); j++) { RTNode node = deleteEntriesList.get(j); if (node.isLeaf()) { for (int k = 0; k < node.usedSpace; k++) { rtree.insert(node.datas[k]); } } else { List<RTNode> traverseNodes = rtree.traversePostOrder(node); for (int index = 0; index < traverseNodes.size(); index++) { RTNode traverseNode = traverseNodes.get(index); if (traverseNode.isLeaf()) { for (int t = 0; t < traverseNode.usedSpace; t++) { rtree.insert(traverseNode.datas[t]); } } } } } return deleteIndex; } } return -1; } @Override protected RTDataNode findLeaf(Rectangle rectangle) { for (int i = 0; i < usedSpace; i++) { if (datas[i].enclosure(rectangle)) { deleteIndex = i; return this; } } return null; } } RTDirNode
package rtree; import java.util.ArrayList; import java.util.List; import rtree.Constants; public class RTDirNode extends RTNode { protected List<RTNode> children; public RTDirNode(RTree rtree, RTNode parent, int level) { super(rtree, parent, level); children = new ArrayList<RTNode>(); } public RTNode getChild(int index) { return children.get(index); } @Override public RTDataNode chooseLeaf(Rectangle rectangle) { int index; switch (rtree.getTreeType()) { case Constants.RTREE_LINEAR: case Constants.RTREE_QUADRATIC: case Constants.RTREE_EXPONENTIAL: index = findLeastEnlargement(rectangle); break; case Constants.RSTAR: if (level == 1) { index = findLeastOverlap(rectangle); } else { index = findLeastEnlargement(rectangle); } break; default: throw new IllegalStateException("Invalid tree type."); } insertIndex = index; return getChild(index).chooseLeaf(rectangle); } private int findLeastOverlap(Rectangle rectangle) { float overlap = Float.POSITIVE_INFINITY; int sel = -1; for (int i = 0; i < usedSpace; i++) { RTNode node = getChild(i); float ol = 0; for (int j = 0; j < node.datas.length; j++) { ol += rectangle.intersectingArea(node.datas[j]); } if (ol < overlap) { overlap = ol; sel = i; } else if (ol == overlap) { double area1 = datas[i].getUnionRectangle(rectangle).getArea() - datas[i].getArea(); double area2 = datas[sel].getUnionRectangle(rectangle).getArea() - datas[sel].getArea(); if (area1 == area2) { sel = (datas[sel].getArea() <= datas[i].getArea()) ? sel : i; } else { sel = (area1 < area2) ? i : sel; } } } return sel; } private int findLeastEnlargement(Rectangle rectangle) { double area = Double.POSITIVE_INFINITY; int sel = -1; for (int i = 0; i < usedSpace; i++) { double enlargement = datas[i].getUnionRectangle(rectangle).getArea() - datas[i].getArea(); if (enlargement < area) { area = enlargement; sel = i; } else if (enlargement == area) { sel = (datas[sel].getArea() < datas[i].getArea()) ? sel : i; } } return sel; } public void adjustTree(RTNode node1, RTNode node2) { datas[insertIndex] = node1.getNodeRectangle(); children.set(insertIndex, node1); if (node2 != null) { insert(node2); } else if (!isRoot()) { RTDirNode parent = (RTDirNode) getParent(); parent.adjustTree(this, null); } } protected boolean insert(RTNode node) { if (usedSpace < rtree.getNodeCapacity()) { datas[usedSpace++] = node.getNodeRectangle(); children.add(node); node.parent = this; RTDirNode parent = (RTDirNode) getParent(); if (parent != null) { parent.adjustTree(this, null); } return false; } else { RTDirNode[] a = splitIndex(node); RTDirNode n = a[0]; RTDirNode nn = a[1]; if (isRoot()) { RTDirNode newRoot = new RTDirNode(rtree, Constants.NULL, level + 1); newRoot.addData(n.getNodeRectangle()); newRoot.addData(nn.getNodeRectangle()); newRoot.children.add(n); newRoot.children.add(nn); n.parent = newRoot; nn.parent = newRoot; rtree.setRoot(newRoot); } else { RTDirNode p = (RTDirNode) getParent(); p.adjustTree(n, nn); } } return true; } private RTDirNode[] splitIndex(RTNode node) { int[][] group = null; switch (rtree.getTreeType()) { case Constants.RTREE_LINEAR: break; case Constants.RTREE_QUADRATIC: group = quadraticSplit(node.getNodeRectangle()); children.add(node); node.parent = this; break; case Constants.RTREE_EXPONENTIAL: break; case Constants.RSTAR: break; default: throw new IllegalStateException("Invalid tree type."); } RTDirNode index1 = new RTDirNode(rtree, parent, level); RTDirNode index2 = new RTDirNode(rtree, parent, level); int[] group1 = group[0]; int[] group2 = group[1]; for (int i = 0; i < group1.length; i++) { index1.addData(datas[group1[i]]); index1.children.add(this.children.get(group1[i])); this.children.get(group1[i]).parent = index1; } for (int i = 0; i < group2.length; i++) { index2.addData(datas[group2[i]]); index2.children.add(this.children.get(group2[i])); this.children.get(group2[i]).parent = index2; } return new RTDirNode[] { index1, index2 }; } @Override protected RTDataNode findLeaf(Rectangle rectangle) { for (int i = 0; i < usedSpace; i++) { if (datas[i].enclosure(rectangle)) { deleteIndex = i; RTDataNode leaf = children.get(i).findLeaf(rectangle); if (leaf != null) return leaf; } } return null; } } Rtree
package rtree; import java.util.ArrayList; import java.util.List; import rtree.Constants; public class RTree { private RTNode root; private int tree_type; private int nodeCapacity = -1; private float fillFactor = -1; private int dimension; public RTree(int capacity, float fillFactor, int type, int dimension) { this.fillFactor = fillFactor; tree_type = type; nodeCapacity = capacity; this.dimension = dimension; root = new RTDataNode(this, Constants.NULL); } public int getDimension() { return dimension; } public void setRoot(RTNode root) { this.root = root; } public float getFillFactor() { return fillFactor; } public int getNodeCapacity() { return nodeCapacity; } public int getTreeType() { return tree_type; } public boolean insert(Rectangle rectangle) { if (rectangle == null) throw new IllegalArgumentException("Rectangle cannot be null."); if (rectangle.getHigh().getDimension() != getDimension()) { throw new IllegalArgumentException("Rectangle dimension different than RTree dimension."); } RTDataNode leaf = root.chooseLeaf(rectangle); return leaf.insert(rectangle); } public int delete(Rectangle rectangle) { if (rectangle == null) { throw new IllegalArgumentException("Rectangle cannot be null."); } if (rectangle.getHigh().getDimension() != getDimension()) { throw new IllegalArgumentException("Rectangle dimension different than RTree dimension."); } RTDataNode leaf = root.findLeaf(rectangle); if (leaf != null) { return leaf.delete(rectangle); } return -1; } public List<RTNode> traversePostOrder(RTNode root) { if (root == null) throw new IllegalArgumentException("Node cannot be null."); List<RTNode> list = new ArrayList<RTNode>(); list.add(root); if (!root.isLeaf()) { for (int i = 0; i < root.usedSpace; i++) { List<RTNode> a = traversePostOrder(((RTDirNode) root).getChild(i)); for (int j = 0; j < a.size(); j++) { list.add(a.get(j)); } } } return list; } public static void main(String[] args) throws Exception { RTree tree = new RTree(4, 0.4f, Constants.RTREE_QUADRATIC, 2); float[] f = { 5, 30, 25, 35, 15, 38, 23, 50, 10, 23, 30, 28, 13, 10, 18, 15, 23, 10, 28, 20, 28, 30, 33, 40, 38, 13, 43, 30, 35, 37, 40, 43, 45, 8, 50, 50, 23, 55, 28, 70, 10, 65, 15, 70, 10, 58, 20, 63, }; for (int i = 0; i < f.length;) { Point p1 = new Point(new float[] { f[i++], f[i++] }); Point p2 = new Point(new float[] { f[i++], f[i++] }); final Rectangle rectangle = new Rectangle(p1, p2); tree.insert(rectangle); Rectangle[] rectangles = tree.root.datas; System.out.println("level:" + tree.root.level); for (int j = 0; j < rectangles.length; j++) System.out.println(rectangles[j]); } System.out.println("---------------------------------"); System.out.println("Insert finished."); System.out.println("---------------------------------"); System.out.println("Begin delete."); for (int i = 0; i < f.length;) { Point p1 = new Point(new float[] { f[i++], f[i++] }); Point p2 = new Point(new float[] { f[i++], f[i++] }); final Rectangle rectangle = new Rectangle(p1, p2); tree.delete(rectangle); Rectangle[] rectangles = tree.root.datas; System.out.println(tree.root.level); for (int j = 0; j < rectangles.length; j++) System.out.println(rectangles[j]); } System.out.println("---------------------------------"); System.out.println("Delete finished."); Rectangle[] rectangles = tree.root.datas; for (int i = 0; i < rectangles.length; i++) System.out.println(rectangles[i]); } } Constants
package rtree; import rtree.RTNode; public class Constants { public static final int MAX_NUMBER_OF_ENTRIES_IN_NODE = 20; public static final int MIN_NUMBER_OF_ENTRIES_IN_NODE = 8; public static final int RTDataNode_Dimension = 2; public static final int RTREE_LINEAR = 0; public static final int RTREE_QUADRATIC = 1; public static final int RTREE_EXPONENTIAL = 2; public static final int RSTAR = 3; public static final int NIL = -1; public static final RTNode NULL = null; } 參考文章:
1.從B樹、B+樹、B*樹談到R 樹?http://blog.csdn.net/v_JULY_v/article/details/6530142/
2.R樹的Java實現?http://blog.csdn.net/renyisheng/article/details/40347223
3.R樹wiki?https://en.wikipedia.org/wiki/R-tree
轉載于:https://www.cnblogs.com/ExMan/p/9674308.html
總結
以上是生活随笔為你收集整理的空间数据索引RTree完全解析及Java实现的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。