相關參考:node
MongoDB地理空間數據存儲及檢索github
我另外作了GEOS STRtree/Quadtree 空間檢索的性能,測試代碼和數據可見Spatial_Index_Testspa
關於SQLite的空間索引相關介紹能夠查看官方文檔 The SQLite R*Tree Module ,這裏只作簡單的介紹。
一、SQLite R *Tree模塊實現部分在其源代碼內(源碼下載頁面),無需另外合併。可是默認是沒有啓用的,啓用須要定義SQLITE_ENABLE_RTREE=1
宏再編譯。
二、SQLite R *Tree模塊採用虛擬表實現,每一個R *Tree索引都是一個虛擬表。對於這個表,其第一列必須是64位有符號整數類型,做爲主鍵。其它的列(2-12列)根據空間維度肯定,每一個維度包含一對(兩列),分別是該維度的最小和最大值。例如:一維R *Tree索引虛擬表包含3列,分別是Int64主鍵| 最小值| 最大值;二維R*Tree索引虛擬表包含5列,分別是Int64主鍵| 第一維最小值| 第一維最大值| 第二維最小值| 第二維最大值;三、四、5維R*Tree索引虛擬表列數狀況的以此論推,SQLite R *Tree實現不支持寬度超過5維的R *樹。
三、對於各個維度的最大最小值列,SQLite中可使用int32
或者float32
類型進行數據存儲。與其它常規表中的列不一樣,這裏存儲就是二進制類型的值,而不是轉換爲字符串。若是在插入數據的時候,使用了這二者以外的類型,則會進行隱式轉換。
-- 建立整型座標rtree索引虛擬表 CREATE VIRTUAL TABLE intrtree USING rtree_i32(id,x0,x1,y0,y1,z0,z1); -- 建立浮點型座標rtree索引虛擬表 CREATE VIRTUAL TABLE floatrtree USING rtree(id,x0,x1,y0,y1,z0,z1);
四、SQLite R *Tree中查詢並不限制查詢的維度必定要與所查詢的表中的維度一致,能夠僅查詢其中的某幾個維度(如3維空間僅查詢2個維度)。通常來講,約束(維度)越多,查詢的範圍框越小,速度越快。
五、默認狀況下使用float32
存儲座標值,當沒法精確表示傳入值時,下限座標向下舍入,上限座標向上舍入,所以邊界框可能略大於指定,但永遠不會變小。這在查詢某個範圍以外的數據時,可能會有極小的偏差。
六、對於3.24.0以前的版本,SQLite R *Tree索引虛擬表僅能存儲整數主鍵和座標值列,其它的信息須要另存於其它表中(經過主鍵進行關聯)。從3.24.0版本開始,SQLite R *Tree索引虛擬表能夠存儲任意類型數據的輔助列,輔助列必須以+
開頭,最多能夠存儲100個輔助列。
CREATE VIRTUAL TABLE demo_index2 USING rtree( id, -- 64位整型主鍵 minX, maxX, -- X方向最小最大值 minY, maxY, -- Y方向最小最大值 +objname TEXT, -- 輔助列 文本類型 +objtype TEXT, -- 輔助列 文本類型 +boundary BLOB -- 輔助列 二進制數據 );
七、能夠自定義R-Tree查詢,以便實現非矩形框碰撞。這須要經過sqlite3_rtree_query_callback
(新,3.8.5開始提供)或sqlite3_rtree_geometry_callback
(舊)註冊查詢SQL語句和匹配檢測回調。相關信息在SQLite網站上有詳細介紹。
八、一個SQLite R *Tree會附帶三個影子表,用於存儲數據,分別是虛擬表名_node
(存儲節點) 虛擬表名_parent
(存儲父節點) 虛擬表名_rowid
(存儲節點的rowid)。
九、可使用SELECT rtreecheck('虛擬表名')
來對R-Tree索引進行完整性和正確性檢查。
寫了一個簡單的測試程序來測試一下**R *Tree**樹的速度,結果仍是能夠的。(可用,並非最佳)
個人機器環境是:
Windows 10 1903 x64專業版
AMD 銳龍 2600X
DDR4 2400 8G
編譯器:VS2017 Native x64
使用本地文件的時候,十萬條數據插入時間大概在2秒之內,查詢一個5x5度大小的範圍,時間基本在0.07秒之內;使用內存模式時,插入時間大概在1.8秒之內,查詢一個5x5度大小的範圍,時間基本在0.04秒之內。
注意:編譯SQLite的時候要定義SQLITE_ENABLE_RTREE
宏,開啓RTree索引支持。
#include "sqlite/sqlite3.h" #include<time.h> #include <stdlib.h> #include <stdio.h> // 由於僅僅是進行一下試用測試,因此有些地方就沒有處理,包括close int main() { sqlite3* db = NULL; int rc = sqlite3_open(":memory:", &db); // int rc = sqlite3_open("D:/sqlite_rtree/test.db", &db); if (rc != SQLITE_OK) { return -1; } char* errmsg; // 建立RTree索引虛擬表 rc = sqlite3_exec(db, "CREATE VIRTUAL TABLE demo_index USING rtree(id,minX, maxX,minY, maxY,+axucol INTEGER NOT NULL)", NULL, NULL, &errmsg); if (rc != SQLITE_OK) { printf("%4d Error:%sn", __LINE__, errmsg); return -2; } // 開始計時 clock_t start = clock(); // 開啓事物 if (sqlite3_exec(db, "begin", NULL, NULL, &errmsg) != SQLITE_OK) { printf("%4d Error:%sn", __LINE__, errmsg); return -2; } // 生成十萬個大小在 邊長在[0.002,0.202]度大小之內的數據(0.2~22.5千米左右) srand(time(NULL)); // 初始化隨機數種子 sqlite3_stmt *pStmt = NULL; // 預處理SQL語句 if(sqlite3_prepare_v2(db, "INSERT INTO demo_index VALUES(?,?,?,?,?,?)", -1, &pStmt, NULL) != SQLITE_OK) { printf("%4d Error:%sn", __LINE__, errmsg); return -3; } // 逐個插入 for (int i = 0; i < 100000; ++i) { // 生成在經緯度範圍內的x,y double x0 = ((double)rand() / (double)RAND_MAX) * 360 - 180; double y0 = ((double)rand() / (double)RAND_MAX) * 180 - 90; double x1 = x0 + 0.002 + ((double)rand() / (double)RAND_MAX)*0.2; double y1 = y0 + 0.002 + ((double)rand() / (double)RAND_MAX)*0.2; // 綁定數據 sqlite3_bind_int64(pStmt, 1, i); sqlite3_bind_double(pStmt, 2, x0); sqlite3_bind_double(pStmt, 3, x1); sqlite3_bind_double(pStmt, 4, y0); sqlite3_bind_double(pStmt, 5, y1); sqlite3_bind_int(pStmt, 6, rand()%3); // 執行 sqlite3_step(pStmt); // 重置 sqlite3_reset(pStmt); } sqlite3_finalize(pStmt); //結束語句,釋放語句句柄 // 結束事物 if (sqlite3_exec(db, "commit", NULL, NULL, &errmsg) != SQLITE_OK){ printf("%4d Error:%sn", __LINE__, errmsg); return -2; } // 結束計時 clock_t end = clock(); double hs = (double)(end - start) * 1000 / CLOCKS_PER_SEC; printf("插入總耗時: %lf msn", hs); // 查詢 // select * from test where NOT(maxX<74.254915 OR minX>79.765758 OR maxY< 24.214285 OR minY>29.725129) AND auxcol==2 ORDER BY id; // 預處理SQL語句 pStmt = NULL; if (sqlite3_prepare_v2(db, "SELECT id,minX,minY,auxcol FROM demo_index WHERE NOT(maxX<? OR minX>? OR maxY<? OR minY>?) AND auxcol==1;", -1, &pStmt, NULL) != SQLITE_OK) { printf("%4d Error:%sn", __LINE__, errmsg); return -4; } //------------------------------------------------------------------------- // 輸入查詢的範圍框數據 puts("Input x0,x1,y0,y1:"); double x0, x1, y0, y1; scanf("%lf,%lf,%lf,%lf", &x0, &x1, &y0, &y1); printf("-----------[%lf,%lf,%lf,%lf]-------------n", x0, x1, y0, y1); // 開始計時 start = clock(); // 綁定查詢範圍數據 sqlite3_bind_double(pStmt, 1, x0); sqlite3_bind_double(pStmt, 2, x1); sqlite3_bind_double(pStmt, 3, y0); sqlite3_bind_double(pStmt, 4, y1); while (sqlite3_step(pStmt) == SQLITE_ROW) { int id = sqlite3_column_int(pStmt, 0); double x = sqlite3_column_double(pStmt, 1); double y = sqlite3_column_double(pStmt, 2); int auxcol = sqlite3_column_int(pStmt, 3); // 能夠把輸出去掉,減小對時間統計的影響 printf("%dt %lf,%lfn", id, x, y); } sqlite3_reset(pStmt); // 這裏只查詢一次能夠沒有,若是須要屢次使用這個查詢語句,則必須有,否則查出數據不對 sqlite3_finalize(pStmt); //結束語句,釋放語句句柄 // 結束計時 end = clock(); hs = (double)(end - start) * 1000 / CLOCKS_PER_SEC; printf("本次查詢總耗時: %lf msn", hs); sqlite3_close(db); system("pause"); return 0; }