地理空間索引實現:z 曲線、希爾伯特曲線、四叉樹, 最鄰近幾何特徵查詢、範圍查詢

我的GIS/CS學習筆記:https://github.com/yunwei37/myClassNotes
<一個浙大GIS/CS小白的課程學習筆記 >
詳細代碼可在其中查看

空間索引

在談論空間索引之前,我們必須瞭解數據索引的概念:索引是爲了提高數據集的檢索效率。打個比喻,一本書的目錄就是這本書的內容的“索引”,我們查看感興趣的內容前,通過查看書的目錄去快速查找對應的內容,而不是一字一句地找我們感興趣的內容;就像這樣,事先構建的索引可以有效地加速查詢的速度。

然而,和一般的數據相比,有效地查詢地理空間數據是相當大的挑戰,因爲數據是二維的(有時候甚至更高),不能用如傳統的B+樹這樣標準的索引技術來加速查詢位置相關的數據。因此,我們就引入了空間索引技術來解決這個問題。

空間索引的定義:

依據空間實體的位置和形狀或空間實體之間的某種空間關係,按一定順序排列的一種數據結構,其中包含空間實體的概要信息,如對象的標識,最小邊界矩形及指向空間實體數據的指針

常見的空間索引技術有網格索引、四叉樹索引,空間填充曲線索引,以及最用於地理空間數據庫的R樹索引以及相關變體等等。

網格索引

網格索引的基本思想是將研究區域用橫豎線劃分大小相等和不等的網格,每個網格可視爲一個桶(bucket),構建時記錄落入每一個網格區域內的空間實體編號。

進行空間查詢時,先計算出查詢對象所在網格,再在該網格中快速查
詢所選空間實體

  • 網格索引優點:簡單,易於實現,具有良好的可擴展性;

  • 網格索引缺點:網格大小影響網格索引檢索性能

理想的情況下,網格大小是使網格索引記錄不至於過多,同時每個網格內的要素個數的均值與最大值儘可能地少。如要獲得較好的網格劃分,可以根據用戶的多次試驗來獲得經驗最佳值, 也可以通過建立地理要素的大小和空間分佈等特徵值來定量確定網格大小。

網格索引的實現這裏暫時沒有涉及。

空間填充曲線索引

常用的空間索引曲線有z曲線、希爾伯特曲線,其目的是在空間網格的基礎上降低空間維度,以便於在順序讀取的磁盤上存取信息。

空間填充曲線(space-filling curve)是一條連續曲線,自身沒有任何交叉;通過訪問所有單元格來填充包含均勻網格的四邊形。

  • z曲線
    在這裏插入圖片描述

  • 希爾伯特曲線
    在這裏插入圖片描述

Z曲線和Hilbert曲線共同特點:

  • 填充曲線值臨近的網格,其空間位置通常也相對臨近;
  • 任何一種空間排列都不能完全保證二維數據空間關係的維護(編號相鄰,空間位置可能很遠)

不同點:

  • Hilbert曲線的數據聚集特性更優,Z曲線的數據聚集特性較差
  • Hilbert曲線的映射過程較複雜,Z曲線的映射過程較簡單

z曲線實現:

Z-curve曲線的二維座標與Z值的相互轉換:

基於bit-shuffling思想,實現二維座標(coor)與Z值(value)的相互轉換。order爲Z-Curve的階數,order爲4時,平面網格大小爲(2^4, 2^4),即16x16。


/*
 * zorder - Calculate the index number (Z-value) of the point (represented by coordinate) in the Z-curve with given order
 */
void zorder(int order, int& value, int coor[2])
{
	// Calculate the z-order by bit shuffling
	value = 0;
	for (int i = 0; i < 2; ++i) {
		for (int j = 0; j < order; ++j) {
			// Task 1.1 zorder,修改以下代碼
			int mask = 1<<j;
			// Check whether the value in the position is 1
			if (coor[1-i] & mask)
				// Do bit shuffling
				value |= 1 << (i + j*2);
		}
	}
}

/*
 * izorder - Calculate the coordinate of the point with given Z-value and order
 */
void izorder(int order, int value, int coor[2])
{
	// Initialize the coordinate to zeros
	for (int i = 0; i < 2; ++i)
		coor[i] = 0;
	// Task 1.2 izoder
	// Write your code here
	for (int i = 0; i < 2; ++i) {
		for (int j = 0; j < order; ++j) {
			// Task 1.1 zorder,修改以下代碼
			int mask = 1 << j*2+i;
			// Check whether the value in the position is 1
			if (value & mask)
				// Do bit shuffling
				coor[1-i] |= 1 << j;
		}
	}
}


希爾伯特曲線實現

Hilbert Curve的二維座標與H值的相互轉換:

基於linear-quadtrees遞歸填充思想,實現二維座標(coor)到H值(value)的相互轉換。order爲Hilbert Curve的階數,order爲4時,平面網格大小爲(2^4, 2^4),即16x16。


/*
 * horder - Calculate the index number (H-value) of the point (represented by coordinate) in Hilbert curve with given order
 */
void horder(int order, int& value, int coor[2])
{
	// order = 1
	int num = int(pow(2, 1));
	int * hcurve = new int[num * num];
    hcurve[0] = 1;
    hcurve[1] = 2;
    hcurve[2] = 0;
    hcurve[3] = 3;

    for(int i = 2; i <= order; ++i) {
		int add = (int)pow(2, 2 * i - 2);		// the number of values in order - 1
		int blockLen = (int)pow(2, i - 1);
		int * temp = hcurve;

        num = int(pow(2, i));
		hcurve = new int[num * num];

		for (int j = 0; j < blockLen; ++j) {
			for (int k = 0; k < blockLen; ++k) {
				// Task 2.1 horder,修改以下四行代碼
				hcurve[j * num + k] = temp[j * blockLen + k] + add;
				hcurve[j * num + k + blockLen] = temp[j * blockLen + k] + add*2;
				hcurve[(j + blockLen) * num + k] = temp[(blockLen - k-1) * blockLen + (blockLen - j-1)] + 0;
				hcurve[(j + blockLen) * num + k + blockLen] = temp[k * blockLen + j] + add * 3;
			}
		}

		delete temp;
    }

	// Task 2.1 horder,修改以下一行代碼
	value = hcurve[num*(num - coor[1] - 1)+coor[0]];
	delete[] hcurve;
}

/*
 * ihorder - Calculate the coordinate of the point with given H-value and order
 */
void ihorder(int order, int value, int coor[2])
{
	// order = 1
	int num = int(pow(2, 1));
	int * hcurve = new int[num * num];
	hcurve[0] = 1;
	hcurve[1] = 2;
	hcurve[2] = 0;
	hcurve[3] = 3;
	
	for (int i = 2; i <= order; ++i) {
		int add = (int)pow(2, 2 * i - 2);		// the number of values in order - 1
		int blockLen = (int)pow(2, i - 1);
		int* temp = hcurve;

		num = int(pow(2, i));
		hcurve = new int[num * num];

		for (int j = 0; j < blockLen; ++j) {
			for (int k = 0; k < blockLen; ++k) {
				// Task 2.1 horder,修改以下四行代碼
				hcurve[j * num + k] = temp[j * blockLen + k] + add;
				hcurve[j * num + k + blockLen] = temp[j * blockLen + k] + add * 2;
				hcurve[(j + blockLen) * num + k] = temp[(blockLen - k - 1) * blockLen + (blockLen - j - 1)] + 0;
				hcurve[(j + blockLen) * num + k + blockLen] = temp[k * blockLen + j] + add * 3;
			}
		}

		delete temp;
	}
	for (int i = 0; i < 2; ++i)
		coor[i] = 0;
	int i = 0;
	for (; i < num * num; ++i) {
		if (value == hcurve[i])
			break;
	}
	coor[0] = i % num;
	coor[1] = num - 1 - i / num;
	delete[] hcurve;
}


四叉樹索引

四叉樹索引是在網格索引的思想基礎上,爲了實現要素真正被網格分割,同時保證桶內要素不超過一個量而提出的一種空間索引方法。

構造方法:

  1. 首先將整個數據空間分割成爲四個相等的矩陣,分別對應西北(NW),東北(NE),西南(SW),東南(SE)四個象限;
  2. 若每個象限內包含的要素不超過給定的桶量則停止,否則對超過桶量的矩形再按照同樣的方法進行劃分,直到桶量滿足要求或者不再減少爲止,最終形成一顆有層次的四叉樹。
    四叉樹
    四叉樹優缺點:
  • 與網格索引相比,四叉樹在一定程度上實現了地理要素真正被網格分割,保證了桶內要素不超過某個量,提高了檢索效率;
  • 對於海量數據,四叉樹的深度會很深,影響查詢效率
  • 可擴展性不如網格索引:當擴大區域時,需要重新劃分空間區域,重建四叉樹,當增加或刪除一個對象,可能導致深度加一或減一,葉節點也有可能重新定位。

四叉樹索引構建:

四叉樹創建輸入一組幾何特徵,將節點分裂爲四個子節點,每個特徵加到包圍盒重疊的子節點中(即一個特徵可能在多個節點中),刪除當前節點的幾何特徵記錄(即所有特徵只存儲在葉節點中),如果子節點的幾何特徵個數大於capacity,遞歸分裂子節點。


/*
 * QuadNode
 */
void QuadNode::split(size_t capacity)
{
	for (int i = 0; i < 4; ++i) {
		delete []nodes[i];
		nodes[i] = NULL;
	}
	
	if (features.size() > capacity) {
		double midx = (bbox.getMinX() + bbox.getMaxX()) / 2.0;
		double midy = (bbox.getMinY() + bbox.getMaxY()) / 2.0;
		Envelope e0(bbox.getMinX(), midx, midy, bbox.getMaxY());
		nodes[0] = new QuadNode(e0);
		Envelope e1(midx, bbox.getMaxX(), midy, bbox.getMaxY());
		nodes[1] = new QuadNode(e1);
		Envelope e2(midx, bbox.getMaxX(), bbox.getMinY(), midy);
		nodes[2] = new QuadNode(e2);
		Envelope e3(bbox.getMinX(), midx, bbox.getMinY(), midy);
		nodes[3] = new QuadNode(e3);

		for (Feature f : features) {
			for (int i = 0; i < 4; ++i) {
				if (nodes[i]->getEnvelope().intersect(f.getEnvelope())) {
					nodes[i]->add(f);
				}
			}
		}

		for (int i = 0; i < 4; ++i) {
			nodes[i]->split(capacity);
		}
		features.clear();
	}
}

/*
 * QuadTree
 */
bool QuadTree::constructQuadTree(vector<Feature>& features)
{
	if (features.empty())
		return false;

	// Task 5.1 construction
	// Write your code here

	Envelope e(DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX);
	for (Feature f : features) {
		e = e.unionEnvelope(f.getEnvelope());
	}
	root = new QuadNode(e);

	root->add(features);

	root->split(capacity);

	bbox = e; // 注意此行代碼需要更新爲features的包圍盒,或根節點的包圍盒
	//bbox = Envelope(-74.1, -73.8, 40.6, 40.8);
	return true;
}


效果:
四叉樹

四叉樹上的最鄰近幾何特徵查詢、範圍查詢:

最鄰近幾何特徵查詢:

最鄰近幾何特徵查詢(K-NN)輸入查詢點(x, y),返回與該點最鄰近的幾何特徵,存儲在feature。首先,通過pointInLeafNode查詢點(x, y)所在的葉節點,計算查詢點(x, y)與該葉節點內的幾何特徵包圍盒的最大距離的最小值minDist,即通過包圍盒而非原始幾何加速最小距離計算;然後,構造查詢區域 (x – minDist, x + minDist, y – minDist, y + minDist),查詢幾何特徵的包圍盒與該區域相交的幾何特徵(filter),再查詢與查詢點(x, y)距離最近的幾何特徵(refine)。

最鄰近幾何特徵查詢

範圍查詢:

區域查詢輸入區域rect,查詢與區域rect相交的幾何特徵,存儲在features。區域rect如果與當前節點的包圍盒bbox相交,遞歸遍歷四叉樹,查詢哪些幾何特徵的包圍盒和查詢區域相交(filter);再獲得可能和查詢區域相交的候選幾何特徵後,精確判斷幾何特徵是否與查詢區域相交(refine)。

效果:
區域查詢

具體代碼可在我的github項目中查看:

https://github.com/yunwei37/myClassNotes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章