Spatial Query
3.2 Grid-based Spatial Indexing
Spatial Query
一、介紹
空間索引(spatial index)是指依據空間對象的位置和形狀,按一定順序排列的一種數據結構,其中包含空間對象的概要信息如對象的標識、最小外接矩形(minimum bounding rectangle,MBR)及指向空間對象實體的指針。空間索引使空間操作能夠快速訪問操作對象,從而提高效率。目前普遍認爲,空間索引技術的採納與否以及空間索引的性能優劣直接影響地理信息系統(GIS)的整體性能。
空間索引技術可大致分爲:基於樹結構,基於網格劃分,混合型。這裏主要分析基於網格以及KD樹的空間索引。
1.網格索引法:
格網型空間索引的基本思想是將研究區域用橫豎線條劃分大小相等或不等的格網,記錄每一個格網所包含的空間實體。當用戶進行空間查詢時,首先計算出用戶查詢對象所在格網,然後再在該網格中快速查詢所選空間實體,這樣一來就大大地加速了空間索引的查詢速度。
把一幅圖的矩形地理範圍均等地劃分爲 m行n列,即規則地劃分二維數據空間,得到m×n個小矩形網格區域。每個網格區域爲一個索引項,並分配一個 動態存儲區,全部或部分落入該網格的空間對象的標識以及外接矩形存入該網格。
網格索引是一種多對多的索引,會導致冗餘,網格劃分得越細,搜索的精度就越高,當然冗餘也越大,耗費的磁盤空間和搜索時間也越長。網格法由於必須預先定義好網格大小,因此它不是一種動態的數據結構。適合點數據。網格索引搜索算法的時間複雜度爲o(N2)。
- KD-Tree法:
SIFT算法中做特徵點匹配的時候就會利用到k-d樹。而特徵點匹配實際上就是一個通過距離函數在高維矢量之間進行相似性檢索的問題。針對如何快速而準確地找到查詢點的近鄰,現在提出了很多高維空間索引結構和近似查詢的算法,k-d樹就是其中一種。
索引結構中相似性查詢有兩種基本的方式:一種是範圍查詢(range searches),另一種是K近鄰查詢(K-neighbor searches)。範圍查詢就是給定查詢點和查詢距離的閾值,從數據集中找出所有與查詢點距離小於閾值的數據;K近鄰查詢是給定查詢點及正整數K,從數據集中找到距離查詢點最近的K個數據,當K=1時,就是最近鄰查詢(nearest neighbor searches)。
特徵匹配算子大致可以分爲兩類。一類是線性掃描法,即將數據集中的點與查詢點逐一進行距離比較,也就是窮舉,缺點很明顯,就是沒有利用數據集本身蘊含的任何結構信息,搜索效率較低,第二類是建立數據索引,然後再進行快速匹配。因爲實際數據一般都會呈現出簇狀的聚類形態,通過設計有效的索引結構可以大大加快檢索的速度。索引樹屬於第二類,其基本思想就是對搜索空間進行層次劃分。根據劃分的空間是否有混疊可以分爲Clipping和Overlapping兩種。前者劃分空間沒有重疊,其代表就是k-d樹;後者劃分空間相互有交疊,其代表爲R樹。
二、問題描述
2.1具體任務
本次作業任務是空間檢索,給定一個GPS數據記錄文件,每條記錄包含經度、緯度等多個屬性,其中常用的就是經度、維度、地點的ID以及地點的類別屬性,要求採用合適的算法,實現三種檢索方法,分別是最鄰近算法(KNN),區域查詢以及半徑查詢。最鄰近算法是給定一個座標ID,查找距離該座標最近的某個地點。區域查詢是通過給定的矩形範圍四個角的經緯度,查詢該區域中某類別的座標地點。半徑查詢是給定一箇中心位置,以該位置爲圓心,進行圓形區域範圍檢索某類別的座標地點。
2.2程序輸入
本程序分爲最近距離搜索、區域搜索和半徑搜索。最近距離搜索的輸入爲:查詢地點的座標ID,最近的查詢類別編號。區域搜素的輸入爲:左上角經度,左上角緯度,右上角經度,右上角緯度,查找類別。半徑搜索的輸入爲:查找地點座標ID,查詢半徑以及查詢地點的類別編號。
2.3 程序輸出
本程序輸出的是經度座標、維度座標、名字和最近距離。
三、問題解答
3.1 數據預處理
本次程序輸入爲GPS數據記錄文件,每行記錄又分爲若干個屬性,根據題意,我們只需關注經度、緯度座標、地點ID以及座標類別屬性即可,原始數據文件部分記錄如圖3.1所示:
圖3.1 原始數據文件部分記錄示意圖
剛開始選擇的方案是與數據文件建立連接,從中抽取出需要的屬性對應的數據,形成新的數據文件或者是在存儲在內存數組中,存儲在內存中缺點是佔用內存,形成新的數據文件是用行號作爲索引,可能會多次多文件進行讀取,增大運行時間。後來經過研究發現,可以有兩種方式,在不佔用太大內存的情況下,將數據輸入數據文件,並在內存中建立數據的索引。分別時將對象序列化存入數據文件,將對象的索引以集合的方式在內存中建立索引,另一種是將數據存儲JSONObject,然後裝入JSONArray,寫入JSON文件,就可以以下標方式建立索引。通過運行時間比較發現,對象序列化方式建立文件和讀取的速度快。
3.2 Grid-based Spatial Indexing
3.2.1 搜索的特點
(1)將空間劃分爲不相交和均勻的網格
(2)在每個網格和網格點之間建立反向索引
3.2.2 範圍查詢
(1)查找與範圍查詢相關的網格。
(2)從網格中獲取點並確定範圍內的點。
3.2.3 最鄰近查詢
(1)計算的是歐式距離。
(2)道路網絡的距離是完全不同的。
最近的點在網格內部 最近的點在網格外部 快速近似
3.2.4 基於網格查詢的優缺點
優點:(1)易於實現和理解。
(2)能非常有效的處理範圍和最近的查詢。
缺點:(1)索引大小可能很大。
(2)難以處理不平衡的數據。
3.3 KD-Tree Spatial Indexing
3.3.1 搜索的特點
圖中的行編號表示對應節點出現的樹的級別。
Kd-Tree,即K-dimensional tree,是一棵二叉樹,樹中存儲的是一些K維數據。在一個K維數據集合上構建一棵Kd-Tree代表了對該K維數據集合構成的K維空間的一個劃分,即樹中的每個結點就對應了一個K維的超矩形區域(Hyperrectangle)。
3.2.2 範圍查詢
(1) 判斷結點在不在範圍之內,如果包含於範圍之內就進入子樹查詢。
(2)通過建立的樹節點比較可以節省查詢時間,提高查詢效率。
由於kd-tree每一層都是對平面的劃分,我們考慮其孫子輩節點.查詢只會對那些與其相交的節點遞歸查詢,因此只需要判斷相交區域數目就行了。
3.2.3最鄰近查詢
從根節點開始遞歸的查找,根據p在節點的左邊還是右邊,決定遞歸方向
若到達葉節點,則將其作爲當前最優節點
回溯:
(1) 若當前節點比當前最優點更優,則將其作爲當前最優節點
(2) 判斷左子樹是否存在最優點,若有則遞歸下去
當根節點搜索完畢,則查找結束。
具體實現的時候需要說明的是,可以用一個優先隊列存儲最優的k個節點,這樣每次比對回溯節點是否比當前最優點更優的時候,就只需用當前最優點中裏p最遠的節點來比對,而這個工作對於優先隊列來說是O(1)的。
四、代碼實現
4.1 程序總流程
網格法空間搜索:
(1)獲取原始點座標並將其寫入到文件中,主要包括讀文件和寫文件兩種操作。
(2)將所選區域等分爲格狀,讀取目標物經緯度,查找網格內部與它最近的點的位置。
(3)若搜尋無果,則擴大搜索範圍,採用遞歸調用的方法逐漸搜尋與他最近的點。
KD-Tree空間搜索:
1.最鄰近算法
k_close(p,o,k,)//查詢點p,樹當前節點o,近鄰數目k
從根節點開始遞歸的查找,根據p在節點的左邊還是右邊,決定遞歸方向
若到達葉節點,則將其作爲當前最優節點
回溯:
(1) 若當前節點比當前最優點更優,則將其作爲當前最優節點
(2) 判斷左子樹是否存在最優點,若有則遞歸下去
當根節點搜索完畢,則查找結束。
2.範圍查找
if v 是葉子
報告並return
if lc(左子樹) 包含於 搜索區域
報告lc
else 左子樹與搜索區域相交
遞歸搜索左子樹
if rc(右子樹) 包含於 搜索區域
else 右子樹與搜索區域相交
遞歸搜索右子樹
報告rc
4.2 具體代碼實現
https://download.csdn.net/download/asd2479745295/10675084
五、程序運行結果
5.1 網格法空間搜索:
5.1.1最鄰近算法
5.1.2範圍查找算法
5.1.3圓範圍查找
5.2 KD-Tree空間搜索
5.2.1最鄰近算法查找
5.2.2範圍查找
5.2.3半徑查找
5.3 索引文件建立時間對比
抽取的數據放入txt需要頻繁讀取,所以考慮了兩種建立索引的方式,一種是將數據放入對象,然後序列化寫入文件,另一種是將數據存入JSON對象,然後寫入JSON文件。
部分代碼
public class UtilZ
{
/**
* 讀取指定文件指定行號的內容
* @param sourceFile 文件
* @param lineNumber 行號
* @return 字符串內容
*/
public String readAppointedLineNumber(File sourceFile, int lineNumber)
throws IOException {
FileReader in = new FileReader(sourceFile);
LineNumberReader reader = new LineNumberReader(in);
String s = "";
int lines = 0;
while (s != null) {
lines++;
s = reader.readLine();
if((lines - lineNumber) == 0) {
// System.out.println(s);
return s;
}
}
reader.close();
in.close();
return s;
}
/**
* 字符串模糊匹配
* @param adressName 匹配的字符串 比如ATM
* @param text 查詢的內容 比如中國銀行ATM機
* @return 字符串內容
*/
private static boolean match(String adressName,String text){
Pattern pattern = Pattern.compile("("+adressName+")");
Matcher matcher = pattern.matcher(text);
if(matcher.find()){
// System.out.println("匹配到了:"+matcher.group(1));
return true;
}
// System.out.println("沒有匹配到");
return false;
}
/**
* 根據兩點經緯度獲得兩點距離
* @param lat 經度
* @param lon 緯度
* @return 兩點距離
*/
public static double Geodist(double lat1, double lon1, double lat2, double lon2)
{
double radLat1 = Rad(lat1);
double radLat2 = Rad(lat2);
double delta_lon = Rad(lon2 - lon1);
double top_1 = Math.cos(radLat2) * Math.sin(delta_lon);
double top_2 = Math.cos(radLat1) * Math.sin(radLat2) - Math.sin(radLat1) * Math.cos(radLat2) * Math.cos(delta_lon);
double top = Math.sqrt(top_1 * top_1 + top_2 * top_2);
double bottom = Math.sin(radLat1) * Math.sin(radLat2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.cos(delta_lon);
double delta_sigma = Math.atan2(top, bottom);
double distance = delta_sigma * 6378137.0;
return distance;
}
public static double Rad(double d)
{
return d * Math.PI / 180.0;
}
/**
* 根據某點經緯度和半徑,獲取圓周內最大最小經緯度
* @param lat 經度
* @param lon 緯度
* @param 半徑
* @return 數組minLat, minLng, maxLat, maxLng
*/
public static double[] GetAround(double lat, double lon, int raidus)
{
Double latitude = lat;
Double longitude = lon;
Double degree = (24901 * 1609) / 360.0;
double raidusMile = raidus;
Double dpmLat = 1 / degree;
Double radiusLat = dpmLat * raidusMile;
Double minLat = latitude - radiusLat;
Double maxLat = latitude + radiusLat;
Double mpdLng = degree * Math.cos(latitude * (3.14159265 / 180));
Double dpmLng = 1 / mpdLng;
Double radiusLng = dpmLng * raidusMile;
Double maxLng = longitude - radiusLng;
Double minLng = longitude + radiusLng;
return new double[] { minLat, minLng, maxLat, maxLng };
}
/**
* 求一個一維數組的方差
* @param data 數據
*
* @return 方差
*/
public static double variance(ArrayList<double[]> data,int dimention){
double vsum = 0;
double sum = 0;
for(double[] d:data){
sum+=d[dimention];
vsum+=d[dimention]*d[dimention];
}
int n = data.size();
return vsum/n-Math.pow(sum/n, 2);
}
/**
* 遞歸實現快速排序算法
* @param data 數據
* @param low 低位置
* @param high 高位置
* @return 方差
*/
public static void QuickSort(double[] data,int low,int high)
{
// TODO 自動生成的方法存根
if(low<high)
{
int middle=GetMiddle(data,low,high);
QuickSort(data,low,middle-1);
QuickSort(data,middle+1,high);
}
}
//將數組拆分
public static int GetMiddle(double[] data, int low, int high)
{
// TODO 自動生成的方法存根
//將數組位置最小的元素賦值給中軸
double temp=data[low];
while(low<high)
{
while(low < high && data[high] >= temp)
{
high--;
}
data[low] = data[high];//比中軸小的記錄移到低位置
while(low < high && data[low] <= temp)
{
low++;
}
data[high] = data[low] ; //比中軸大的記錄移到高位置
}
data[low] = temp ; //將中軸元素放入中軸
return low;
}
/**
* 求矩形外一點到矩形最小的距離
* @param input 輸入的點的數據
* @param max 橫縱座標的最大值
* @param min 橫縱座標的最小值
* @return 最小距離
*/
public static double minP_RDistance(double []input,double []max,double min[])
{
double point_x=input[0];
double point_y=input[1];
double max_x=max[0];
double max_y=max[1];
double min_x=min[0];
double min_y=min[1];
double mindistance=0;
//如果在矩形的左右兩邊
if(point_y>min_y&&point_y<max_y)
{
if(point_x>max_x)
{
mindistance=Geodist(point_x,point_y, max_x, point_y);
}
else if(point_x<min_x)
{
mindistance=Geodist(point_x,point_y, min_x, point_y);
}
}
else if(point_x>min_x&&point_x<max_x)
{
if(point_y>max_y)
{
mindistance=Geodist(point_x,point_y, point_x, max_y);
}
else if(point_y<min_y)
{
mindistance=Geodist(point_x,point_y, point_x, min_y);
}
}//在四個角上
else
{ //左上
if(point_x<min_x&&point_y>max_y)
{
mindistance=Geodist(point_x,point_y,min_x,max_y);
}//左下
else if(point_x<min_x&&point_y<min_y)
{
mindistance=Geodist(point_x,point_y,min_x,min_y);
}//右上
else if(point_x>max_x&&point_y>max_y)
{
mindistance=Geodist(point_x,point_y,max_x,max_y);
}//右下
else if(point_x>max_x&&point_y<min_y)
{
mindistance=Geodist(point_x,point_y,max_x,min_y);
}
}
return mindistance;
}
}