【轉】在 SQL Server 2005 中使用表值函數來實現空間數據庫

本文說明了如何使用 C# 和表值函數將空間搜索函數(“鄰近點的點”和“多邊形內的點”)添加到 Microsoft SQL Server 2005。使用此庫可以在不編寫任何特殊代碼的情況下向應用程序中添加空間搜索。此庫實現了來自約翰霍普金絲大學的公共域 C# 分層三角網格 (HTM) 算法。該 C# 庫使用一組標量值函數和表值函數連接到 SQL Server 2005。這些函數起空間索引的作用。

您可以下載本文檔的 Microsoft Word 版本。

本頁內容

簡介 簡介
表值函數:主要概念 表值函數:主要概念
使用表值函數添加空間索引 使用表值函數添加空間索引
數據集 數據集
典型查詢 典型查詢
結束語 結束語
附錄 A:參考資料 附錄 A:參考資料
附錄 B:基本 HTM 例程 附錄 B:基本 HTM 例程

簡介

空間數據搜索常用於商業和科學應用程序。結合在針對天文領域構建 SkyServer (http://skyserver.sdss.org/)方面的努力,我們開發了一種空間搜索系統。SkyServer 是一個幾千 GB 大小的數據庫,其中收錄了大約 3 億個天體對象。天文學家需要對該數據庫進行的許多查詢都會涉及到空間搜索。典型的查詢包括:“鄰近此點的對象有哪些”、“此區域內包含哪些對象”以及“哪些區域與此區域有重疊”?

在本文中,我們向天文學家的赤經/赤緯 (ra/dec) 天球(天空)網格添加了緯度/經度 (lat/lon) 地球網格。這兩種網格大致相同,但並非精確對應,傳統順序 lat-lon 與 dec-ra 相對應。這一順序上的顛倒迫使我們必須明確座標系。我們將格林尼治子午線-赤道大地座標系稱爲 LatLon 座標系。該庫支持三種座標系:

  • 格林尼治緯度-經度,稱爲 LatLon。

  • 天文赤經-赤緯,稱爲 J2000。

  • 笛卡爾 (x, y, z),稱爲笛卡爾。

天文學家使用弧度分作爲標準的距離度量。由於一海里爲一弧度分,因此距離轉換非常簡單。其他許多概念也極爲相似。爲了說明這些問題,本文將演示如何使用此空間庫針對兩個 USGS 數據集構建空間索引,這兩個數據集是:美國城市和美國流量計。本文使用這些索引和一些空間函數提供了若干示例,來說明如何搜索鄰近某一點的城市、如何查找鄰近某一城市的流量計以及如何查找某個州(多邊形區域)內的流量計或城市。

我們認爲此方法具有通用性。可以向幾乎所有應用程序中添加空間數據核心架構和空間數據函數以便進行空間查詢。這些概念也適用於其他的多維索引方案。例如,這些技術可用於搜索顏色空間或其他任何低維度度量空間。

表值函數:主要概念

關係代數的主要概念是,每個關係運算符均使用一個或多個關係,並生成一個輸出關係。SQL 是對這一概念的語法修飾,使您可以定義關係(數據定義語言)和用選擇-插入-更新-刪除語法來操作關係。

定義自己的標量函數使您可以對關係數據庫進行擴展:您可以發送郵件,可以執行命令腳本,還可以計算非標準標量和聚合值,例如 tax()median()

但是,如果您可以創建表,則可以成爲關係引擎的一部分:既是關係表的生成者,也是其使用者。這就是 OLEDB 的概念,此概念允許任何數據源生成數據流。這也是 SQL Server 2000 表值函數蘊含的概念。

在 Transact-SQL 中實現表值函數很簡單:

create function t_sql_tvfPoints()
returns @points table (x float, y float)
as begin
insert @points values(1,2);
insert @points values(3,4);
return;
end

如果可以完全在 Transact-SQL 中執行函數,這樣做就可以了。但是,在 SQL Server 2000 中,要在 Transact-SQL 外部實現 OLEDB 數據源或表值函數確實非常困難。

而 SQL Server 2005 集成了公共語言運行庫,可以容易地創建表值函數。您可以創建列表、數組或任意 IEnumerable 對象(可以對其進行 foreach 操作的任意對象),然後將其轉換爲表。

[SqlFunction(TableDefinition   = "x float, y float" ,
FillRowMethodName = "FillPair")]
public static IEnumerable csPoints( )
{
int[,] points = { { 1, 2 }, { 3, 4 } };
return (IEnumerable) points;
}

在 Visual Studio 中編譯這段代碼,然後單擊 Deploy(部署)。表值函數將被安裝到數據庫中。

使用表值函數添加空間索引

對於索引,人們存在許多困惑。事實上,索引非常簡單:它們只是帶有一些特殊屬性的表:

  • SQL Server 僅有一種關聯(按值)索引:B 樹。B 樹可以具有多字段鍵,但最常選擇的是第一個字段。

  • 從概念上說,B 樹索引是由 B 樹鍵字段、基表鍵字段以及您要添加到索引的任何包含字段所組成的表。

  • B 樹索引將根據索引鍵(例如郵政編碼或客戶 ID)來排序,以便按該鍵進行查找或順序掃描會很快。

  • 索引通常比基表小,只包含最重要的屬性,以便與檢查整個表相比,在索引中進行查找所涉及的字節數小得多。通常情況下,索引非常小,以致能完全裝在主內存中,從而省去更多的磁盤存取。

  • 當您執行索引查找時,可能僅僅是搜索索引(基表的垂直部分),也可能是先搜索索引,再利用基表主鍵將符合要求的索引逐行加入基表中(書籤查找)。

中心思想是,空間索引可爲您提供一小部分數據。索引會告訴您查找的位置,並通常附帶一些有用的搜索信息(專家將其稱爲包含列或覆蓋列)。索引的選擇性表明了初始縮減的程度(圖 1 所示的粗略子集)。找到相應的子集後,將仔細檢查該子集的每個成員並捨棄假正值。圖 1 中的菱形框指明瞭該過程。好的索引只含有少量的假正值。在整篇文章中,我們都將使用圖 1 中的說法(粗略子集和仔細檢查)。

圖 1

B 樹和表值函數可以如下組合,以使您可以構建自己的空間索引來生成粗略子集:

  • 創建一個函數,以生成將相關數據聚集在一起的鍵。例如,如果項目 A 和 B 相關,則 A 和 B 的鍵在 B 樹鍵空間中應該是鄰近的。

  • 創建一個表值函數,以便在給出了對所需子集的說明後,返回包含所有相關值的鍵範圍(“覆蓋”)列表。

您無法始終讓每個鍵與其所有相關項鄰近,因爲鍵是在一個維度中排序的,而相關項是在二維或更高維數的空間中與其鄰近。但是可以儘量這樣做。假正值與正確答案的比率衡量了您所採取的方式的好壞。

標準方法是,找到某一條空間填充曲線,並使鍵空間沿該曲線穿過。例如,使用標準墨卡托地圖,可以將西北中的每個人分配給西北鍵範圍,將東南中的每個人分配給東南鍵範圍。圖 2 顯示了第二種順序空間填充曲線,它橫穿所有這些象限,按順序分配鍵。西北-西南象限中的每個人都具有鍵前綴 nwsw。如果您的區域與圖 2 中所示的圓圈類似,就可以知道您的鍵範圍

   key between 'nwsw' and 'nwse'

圖 2

此搜索空間佔整個表的八分之一,並且含有大約 75% 的假正值(由圓圈外但位於兩個方框內的區域表示)。改進不大,但傳達了一種概念。更好的索引要使用更精細的單元格分區。如果單元格足夠精細,則聚合區域中的假正值就會非常少。要詳細查看空間填充曲線和空間分區樹,您可以參閱 Hanan Samet [Samet] 的書籍。

現在,我們要定義一種空間填充曲線:分層三角網格 (HTM),它特別適用於球面。地球是圓的,天球也是圓的,因此,這種球面系統對於地理學家和天文學家來說非常方便。我們可以對任何度量空間做一些類似的事情。空間填充曲線提供了一些鍵來作爲空間索引的基礎。那麼,如果某人具有所需的區域時,我們的表值函數將爲其提供一組適當的鍵範圍供查找(圖 1 中的粗略篩選)。這些鍵範圍將覆蓋帶有球面三角形的區域(稱爲 trixel),這與圖 2 中覆蓋圓圈的兩個方框非常相似。搜索函數只需查看這些 trixel 的鍵範圍內的所有對象,以確定它們是否符合要求(圖 1 中的仔細檢查)。

我們可以用一個具體的例子進行說明,假設有一個對象表

create table Object (objID bigint primary key,
lat   float, -- Latitude
lon   float, -- Longitude
HtmID bigint) -- The HTM key

和一個距離函數 dbo.fDistanceLatLon(lat1, lon1, lat2, lon2),該函數可計算出兩點之間的海里(弧度分)數。進一步假設以下表值函數給出了位於某個 lat-lon 點的定長半徑範圍內的 HtmID 點的鍵範圍列表。

define function
fHtmCoverCircleLatLon(@lat float, @lon float, @radius float)
returns @TrixelTable table(HtmIdStart bigint, HtmIdEnd bigint)

然後,以下查詢會查找舊金山 (lat,lon) = (37.8,-122.4) 周圍 40 海里範圍內的點。

select O.ObjID, dbo.fDistanceLatLon(O.lat,O.lon, 37.8, -122.4)
from fHtmCoverCircleLatLon(37.8, -122.4, 40) as TrixelTable
join Object O
on O.HtmID between TrixelTable.HtmIdStart        -- coarse test
and TrixelTable.HtmIdEnd
where dbo.fDistanceLatLon(lat,lon,37.8, -122.4) < 40   -- careful test

現在,我們必須定義 HTM 鍵生成函數、距離函數和 HTM 覆蓋函數。下一步我們將以兩組美國地質空間數據集爲例執行這些操作。如果您不相信其中包含的對象達數十億,請訪問 http://skyserver.sdss.org/ 並瀏覽一下該網站。該網站使用相同的代碼來對幾千 GB 的天文數據庫進行空間查找。

本文主要講述如何使用 SQL 表值函數和像 HTM 這樣的空間填充曲線來構建空間索引。同樣地,我們將 HTM 代碼本身當作一種在別處 [Szalay] 存檔的黑色方框,我們只關注如何使其在 SQL 應用程序內適合我們的需要。

數據集

美國地質勘探局 (USGS) 收集併發布了美國的相關數據。圖 3 顯示了由 USGS 維護的 18000 臺用於測量河流水流量和級別的流量計。USGS 還發布了 23000 個地點的地名及其人口的列表。

圖 3

USGS 居民點(23000 個城市)

USGS 在 1993 年發佈了地點名稱及其某些屬性的列表。USGS 網站上有一些更新的列表,但由於它們是按州來分段的,因此很難獲得一個全國範圍的列表。舊的列表足以能夠演示空間索引。數據格式如下:

create table Place(
PlaceName   varchar(100) not null, -- City name
State       char(2)      not null, -- 2 char state code
Population  int          not null, -- Number of residents (1990)
Households  int          not null, -- Number of homes (1990)
LandArea    int          not null, -- Area in sqare KM
WaterArea   int          not null, -- Water area within land area
Lat         float        not null, -- Latitude in decimal degrees
Lon         float        not null, -- Longitude decimal degrees
HtmID       bigint       not null primary key --spatial index key
)

爲了加快名稱查找,我們添加了一個名稱索引,但數據是按空間鍵聚集在一起的。鄰近的對象共同位於聚集 B 樹中,並因此位於相同或相鄰的磁盤頁上。

create index Place_Name on Place(PlaceName)

可以從 USGS 網站下載除 HtmID 數據以外的其他所有數據。可以用 SQL Server 2005 數據導入嚮導來導入數據(我們已在示例數據庫中進行了此操作)。HtmID 字段是根據 LatLon 按以下方法計算出的:

update Place set HtmID = dbo.fHtmLatLon(lat, lon)

USGS 流量計(17000 臺儀器)

USGS 自 1854 起一直在維護河流流量記錄。到 2000 年 1 月 1 日,他們已累積了超過 43 萬年的測量數據。大約有六千個活動的測量站處於活動狀態,而有大約四千個處於聯機狀態。http://waterdata.usgs.gov/nwis/rt 中對這些流量計進行了詳細介紹。有個 NOAA 站點以非常便利的方式顯示了來自幾百個最普遍的測量站的數據:http://weather.gov/rivers_tab.php

我們的數據庫只包含美國大陸的測量站(見圖 3)。關島、阿拉斯加、夏威夷、波多黎各和維京羣島也有測量站,但不包含在此數據庫內。流量計測量站表爲:

create table Station (
StationName   varchar(100) not null,     -- USGS Station Name
State         char(2)      not null,     -- State location
Lat           float        not null,     -- Latitude in Decimal
Lon           float        not null,     -- Longitude in Decimal
DrainageArea  float        not null,     -- Drainage Area (km2)
FirstYear     int          not null,     -- First Year operation
YearsRecorded int          not null,     -- Record years (at Y2k)
IsActive      bit          not null,     -- Was it active at Y2k?
IsRealTime    bit          not null,     -- On Internet at Y2K?
StationNumber int          not null,     -- USGS Station Number
HtmID         bigint       not null,     -- HTM spatial key
-- (based on lat/lon)
primary key(htmID, StationNumber) )

如上所述,HtmID 字段是根據 LatLon 按以下方法計算出的:

update Station set HtmID = dbo.fHtmLatLon(lat, lon)

由於一個位置有多達 18 個測量站,因此主鍵必須包括測量站編號以便區分它們。但是,在 B 樹中,HTM 鍵將所有鄰近的測量站聚集在一起。爲了加快查找,我們添加了測量站編號和名稱索引:

create index Station_Name   on Station(StationName)
create index Station_Number on Station(StationNumber)

空間索引表

現在,我們就可以創建自己的空間索引了。我們本可以將字段添加到基表,但要使存儲過程對多個不同的表均有效,我們發現,只需將所有對象加入到一個空間索引中即可。您可以選擇 (type,HtmID) 作爲鍵來隔離不同類型的對象;我們選擇了 (HtmID, key) 作爲鍵,以便將所有類型(城市和流量計)的鄰近對象聚集在一起。該空間索引爲:

create table SpatialIndex (
HtmID   bigint   not null , -- HTM spatial key (based on lat/lon)
Lat     float    not null , -- Latitude in Decimal
Lon     float    not null , -- Longitude in Decimal
x       float    not null , -- Cartesian coordinates,
y       float    not null , -- derived from lat-lon
z       float    not null , --,
Type    char(1)  not null , -- Place (P) or gauge (G)
ObjID   bigint   not null , -- Object ID in table
primary key (HtmID, ObjID) )

此主題後面將對笛卡爾座標進行說明。至於現在,我們只需要知道,fHtmCenterPoint(HtmID) 函數將返回該 HTM 三角形中心點的笛卡爾 (x,y,z) 單位向量。這就是該 HTM 的極限點,因爲此中心被細分爲無窮個小的 trixel。

SpatialIndex 表將根據 Place 和 Station 表的數據來填充,如下所示:

insert SpatialIndex
select   P.HtmID, Lat, Lon, XYZ.x, XYZ.y, XYZ.z,
'P' as type, P. HtmID as ObjID
From   Place P cross apply fHtmLatLonToXyz(P.lat, P.lon)XYZ
insert SpatialIndex
select S.HtmID, Lat, Lon, XYZ.x, XYZ.y, XYZ.z,
'S' as type, S.StationNumber as ObjID
from   Station S cross apply fHtmLatLonToXyz(S.lat, S.lon) XYZ

爲了清理數據庫,應執行以下命令:

DBCC INDEXDEFRAG    (spatial  , Station, 1)
DBCC INDEXDEFRAG    (spatial  , Station, Station_Name)
DBCC INDEXDEFRAG    (spatial  , Station, Station_Number)
DBCC INDEXDEFRAG    (spatial  , Place,   1)
DBCC INDEXDEFRAG    (spatial  , Place,   Place_Name)
DBCC INDEXDEFRAG    (spatial  , SpatialIndex, 1)
DBCC SHRINKDATABASE (spatial  , 1  ) - 1 percent spare space

題外話:笛卡爾座標

您可以選擇跳過此部分。此部分對於使用該庫並不是必需的。HTM 代碼必須依靠一種技巧來避開球面幾何問題:它從球面的二維表面移到了三維空間。這就使得“在多邊形內”和“在點附近”查詢的檢查進行得非常快。

球面上的每個 lat/lon 點都可以表示爲三維空間中的一個單位向量 v = (x,y,z)。北極和南極(90° 和 -90°)的單位向量分別爲 v = (0,0,1) 和 v = (0,0,-1)。Z 代表旋轉軸,XZ 平面代表本初(格林尼治)子午線,其經度爲 0° 或 180°。正式的定義爲:

x = cos(lat)cos(lon)
y =cos(lat)sin(lon)
z = sin(lat)

這些笛卡爾座標的使用方法如下:給定單位球面上的兩點 p1=(x1,y1,z1) 和 p2 = (x2,y2,z2),則它們的點乘積 p1&p2 = x1*x2+y1*y2+z1*z2 就是這兩點所代表的單位向量之間的角度的餘弦值。它是一個距離度量。圖 4 顯示了笛卡爾座標如何使得對“多邊形內的點”和“鄰近點的點”的檢查快速進行。每個 lat/lon 點均具有一個對應的 (x,y,z) 單位向量。

圖 4

如果我們要查找 p1 點周圍 45 海里(弧度分)範圍內的點,則它與 p1 點最多成 45/60 度。這些點與 p1 的點乘積將小於 d=acos(radians(45/60)。該“鄰近”檢查即變爲 { p2 | p2&p1 < d},它將很快進行。在圖 5 中,每個大圓圈或小圓圈都是一個平面與該圓圈的交集,如果某一點與平面法向量的點乘積小於 cos(?¨)(其中 2?¨ 是該圓圈的弧度角直徑),則該點就在圓圈內部。

圖 5

笛卡爾座標還可以使得對“多邊形內的點”的檢查快速進行。所有的多邊形都具有大圓邊或小圓邊。這些邊沿着與球面交叉的某個平面分佈。因此,可以通過與該平面垂直的單位向量 v 及其位移來定義這些邊。例如,赤道就是向量 v = (0,0,1),且位移爲零。緯度 60° 由向量 v = (0,0,1) 及位移 0.5 來定義,而圍繞巴爾的摩市的 60° 緯度圈由向量 v = (0.179195, -0.752798, 0.633392) 及 0.5 個位移來定義。對於地點 p2,如果 p2&v < 0.5,則該地點就位於巴爾的摩市 60° 緯度圈內。同樣,可以通過計算出三個這種點乘積來確定某個點位於 HTM 三角形內部還是外部。這是 HTM 代碼如此有效和快捷的主要原因之一。

我們實現了若干輔助過程來進行從 LatLon 到笛卡爾座標的轉換。




本文稍後將用到這些函數,它們存檔在 API spec and Intellisense [Fekete] 中。

在此,該庫的默認設置爲 21 級深度的 HTM 鍵(第一級將球面分成八個表面區域,隨後每一級將球面三角形分成四個子三角形)。從表 1 中可看出,21 級深度的 trixel 相當小。最多可以將代碼修改爲 31 級深度,這是因爲不能超出 64 位表示法所佔用的位數。

在表 1 中,每個 HTM 級別都會細分球面。對於每個級別,此表均以度數、弧度分、弧度秒和米四種單位的平方形式表示了面積。trixel 列顯示了一些特徵大小:默認的 21 級深度的 trixel 大約爲 0.3 平方弧度秒。USGS 數據在每 12 級深度的 trixel 具有大約半個對象。

表 1

HTM 深度

面積

對象/trixel

 

deg 2

arc min 2

arc sec 2

earth m 2

trixel

SDSS

USGS

球面

41253

148,510,800

534,638,880,000

5.E+14

     

0

5157

18,563,850

66,829,860,000

6E+13

 

3E+8

 

1

1289

4,640,963

16,707,465,000

2E+13

 

8E+7

 

2

322

1,160,241

4,176,866,250

4E+12

 

2E+7

 

3

81

290,060

1,044,216,563

1E+12

 

5E+6

 

4

20

72,515

261,054,141

2E+11

 

1E+6

30,000

5

5

18,129

65,263,535

6E+10

 

3E+5

7,500

6

1

4,532

16,315,884

2E+10

1 deg2

73242

1,875

7

3E-1

1,133

4,078,971

4E+9

 

18311

468

8

8E-2

283

1,019,743

1E+9

 

4578

117

9

2E-2

71

254,936

2E+8

 

1144

29

10

5E-3

18

63,734

6E+7

 

286

7

11

1E-3

4

15,933

2E+7

 

72

2

12

3E-4

1

3,983

4E+6

1 amin2

18

0.5

13

8E-5

3E-1

996

943816

 

4

0.1

14

2E-5

7E-2

249

235954

 

1

 

15

5E-6

2E-2

62

58989

 

0.3

 

16

1E-6

4E-3

16

14747

 

.

 

17

3E-7

1E-3

4

3687

     

18

8E-8

3E-4

1

922

     

19

2E-8

7E-5

2E-1

230

1 asec2

   

20

5E-9

2E-5

6E-2

58

1 km2

   

21

1E-9

4E-6

2E-2

14

     

22

3E-10

1E-6

4E-3

4

     

23

7E-11

3E-7

9E-4

1

1 m2

   

24

2E-11

7E-8

2E-4

2E-1

     

25

5E-12

2E-8

6E-5

6E-2

     

26

1E-12

4E-9

1E-5

1E-2

     

典型查詢

現在,您應當可以執行一些查詢了。

查詢 1:查找鄰近點的點:查找鄰近某一地點的城鎮

最常見的查詢是查找鄰近某個特定地點或點的所有地點。考慮以下查詢,“查找馬里蘭州巴爾的摩市周圍 100 海里內的所有城鎮”。通過下面的方法來得到覆蓋巴爾的摩市周圍 100 海里(100 弧度分)範圍的 HTM 三角形

select * -- find a HTM cover 100 NM around Baltimore
from fHtmCoverCircleLatLon(39.3, -76.6, 100)

它將返回表 2 中所示的 Trixel 表。即,fHtmCoverCircleLatLon() 函數將返回“覆蓋”該圓圈(在本例中,是單個 trixel)的一組 HTM 三角形。該圓圈內所有對象的 HTM 鍵也位於這些三角形中之一內。現在,我們需要檢查所有這些三角形並捨棄假正值(圖 1 中的仔細檢查)。我們將按照與巴爾的摩市的距離對答案集進行排序,因此,如果我們需要找出最近的地點,只需選擇 TOP 1 WHERE distance > 0 即可(我們要從中排除巴爾的摩市本身)。

declare @lat float, @lon float
select @lat = lat, @lon = lon
from Place
where Place.PlaceName = 'Baltimore'
and State = 'MD'
select ObjID, dbo.fDistanceLatLon(@lat,@lon, lat, lon) as distance
from SpatialIndex join fHtmCoverCircleLatLon(@lat, @lon, 100)
On HtmID between HtmIdStart and HtmIdEnd           -- coarse test
and type = 'P'
and dbo.fDistanceLatLon(@lat,@lon, lat, lon) < 100 -- careful test
order by distance asc

表 2. 巴爾的摩市 HTM 覆蓋緯度圈

HtmIdStart

HtmIdEnd

14023068221440

14027363188735

此覆蓋聯接將返回 2928 行(粗略檢查);其中 1122 行在 100 航空英里以內(仔細檢查)。它給出了 61% 的假正值:全部操作在 9 毫秒內完成。

由於這些是常見的任務,因此具有針對它們的標準函數:

fHtmNearbyLatLon(type, lat, lon, radius)
fHtmNearestLatLon(type, lat, lon)

這樣,上述查詢就變爲

select ObjID, distance
from fHtmNearestLatLon('P', 39.3, -76.61)

查詢 2:查找某個方框內的地點

在顯示正方形的地圖或窗口時,應用程序通常需要查找某個正方形視區內的所有對象。科羅拉多州幾乎完全是正方形的,它的西北角點爲 (41°N-109°3'W),西南角點爲 (37°N-102° 3'E)。該州的中心點爲 (39°N, -105°33'E),因此可以用以該點爲中心的圓圈覆蓋該正方形。

declare @radius float
set @radius = dbo.fDistanceLatLon(41,-109.55,37,-102.05)/2
select *
from Station
where StationNumber in (
select ObjID
from fHtmCoverCircleLatLon(39, -105.55, @radius) join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
and lat between 37 and 41
and lon between -109.05 and -102.048
and type = 'S')
OPTION (FORCE ORDER)

本例在大約 46 毫秒內返回了 1030 個流量計。科羅拉多州的其他五個流量計正好在其邊界上,散佈於距離南緯 37°緯度圈不超過一海里的範圍內。如果將南緯從 37° 調整到 36.98°,則其他這五個測量站就會出現。(GIS 系統和天文應用程序通常需要某一區域周圍具有緩衝區。此 HTM 代碼包含對緩衝區的支持,在實際的應用程序經常會用到緩衝區。請查看參考資料 [Szalay] 以瞭解具體操作方式。)此覆蓋圓圈返回了 36 個三角形。與 SpatialIndex 表的聯接返回了 1975 個流量計。其中包含 47% 的假正值。下一節將演示如何通過使用 HTM 區域指定多邊形覆蓋而非圓圈覆蓋對此進行改進。

FORCE ORDER 子句比較麻煩:如果缺少該子句,查詢的運行時間會長十倍,因爲優化器會將空間索引作爲外部表進行嵌套循環聯接。如果這些表更大(包含數百萬行),優化程序有可能會採取其他計劃,但我們不能指望它。優化程序不可能無需提示就能針對上一部分中的所有查詢選擇正確的計劃。

查詢 3:查找某個多邊形內的地點

HTM 代碼允許我們將此區域指定爲圓圈、矩形、凸球面或這些區域的組合。特別地,HTM 庫允許使用下面的線性語法來指定區域:

circleSpec  := 'CIRCLE LATLON '      lat lon radius
|  'CIRCLE J2000 '       ra  dec radius
|  'CIRCLE [CARTESIAN ]' x y z   radius
rectSpec    := 'RECT LATLON '        { lat lon }2
|  'RECT J2000 '         { ra  dec }2
|  'RECT [CARTESIAN ]'   { x y z   }2
hullSpec    := 'CHULL LATLON '       { lon lat }3+
|  'CHULL J2000 '        { ra dec  }3+
|  'CHULL [CARTESIAN ]'  { x y z   }3+
convexSpec  := 'CONVEX ' [ 'CARTESIAN '] { x y z D }*
areaSpec    := rectSpec | circleSpec | hullSpec | convexSpec
regionSpec  := 'REGION ' {areaSpec}* | areaSpec

下面給出了區域指定示例:

  • 圓圈。指定一個點和大小爲 1.75 海里(弧度分)的半徑。

    'CIRCLE LATLON 39.3 -76.61 100'
        'CIRCLE CARTESIAN 0.1792 -0.7528 0.6334 100'
        
  • 矩形。指定用來定義最小和最大 lat,lon 的兩個角點。經度座標以折回方式確定,即 lonmin=358.0 和 lonmax=2.0,這是一個四度寬的範圍。緯度必須介於北極和南極之間。矩形邊是緯度或經度保持不變的直線,而非凹形和凸形的大圓邊。

    'RECT LATLON 37 -109.55  41 -102.05'
        
  • 凹形。指定用來定義凸球面三個或更多個點,且該凸球面的邊用大圓圈將相鄰的點連接起來。這些點必須位於單個半球內,否則會發生錯誤。這些點的順序無關緊要。

    'CHULL LATLON 37 -109.55 41 -109.55 41 -102.051 37 -102.05'
        
  • 凸形。以笛卡爾向量 (x,y,z) 及該向量單位長度的分量的形式指定任意多個(包括零個)約束。

    'CONVEX   -0.17886 -0.63204 -0.75401 0.00000
        -0.97797  0.20865 -0.00015 0.00000
        0.16409  0.57987  0.79801 0.00000
        0.94235 -0.33463  0.00000 0.00000'
        
  • 區域。區域是零個或更多個圓圈、矩形、凹形和凸形區域的組合。

    'REGION CONVEX 0.7 0.7 0.0 -0.5 CIRCLE LATLON 18.2 -22.4 1.75'
        

可以這些區域描述中的任意一個應用於 fHtmCoverRegion() 例程,以返回一個 trixel 表,用來描述覆蓋該區域的一組 trixel(三角形區域)。用於此科羅拉多州查詢的較簡單的代碼爲:

select S.*
from (select ObjID
from fHtmCoverRegion('RECT LATLON 37 -109.55  41 -102.05')
loop join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
and lat between 37 and 41
and lon between -109.05 and -102.048
and type = 'S') as G
join Station S on G.objID = S.StationNumber
OPTION (FORCE ORDER)

有必要使用這種不尋常的查詢格式來準確告訴優化器聯接的執行順序(以使“強制順序”選項正確運行)。很難以這種方式修改優化器,直到表值函數具有統計信息爲止,估計它們會非常耗費資源。您必須強制使它們進入內部循環聯接。

此查詢將返回 1030 個流量計,而有 1365 個來自覆蓋範圍的候選項,因此包含 25% 的假正值。請注意,矩形覆蓋優於圓形覆蓋,因爲後者包含 61% 的假正值。對於非矩形形狀的州,可以使用多邊形語法,但本文僅講述表值函數,而不講述 HTM 算法。您可以在項目中以及項目的相關文檔中查看 HTM 代碼。

可以轉換爲凸球面進行類似的查詢。

select S.*
from (select ObjID
from fHtmCoverRegion(
'CHULL LATLON 37 -109.55 41 -109.55 41 -102.05 37 -102.05')
loop join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
and lat between 37 and 41
and lon between -109.05 and -102.048
and type = 'S') as G
join Station S on G.objID = S.StationNumber
OPTION (FORCE ORDER)

此查詢將返回 1030 個流量計,而有 1193 個來自覆蓋範圍的候選項,因此包含 14% 的假正值。在本例中,凸球面覆蓋比同等的矩形覆蓋更好。

查詢 4:高級主題:複雜區域

前面的示例給出了用於區域的語法,並對“鄰近點的點”和“矩形內的點”搜索進行了論述。區域可能會十分複雜。它們是多個凸形區域的布爾組合。我們無法在此詳細解釋區域的概念,但伴隨項目中的 HTM 庫包含對區域進行布爾組合、簡化區域、計算區域頂點和計算區域面積的邏輯,還包含許多其他特性。[Fekete]、[Gray] 和 [Szalay] 中介紹了這些概念。

爲了初步理解這些概念,我們以猶他州爲例。它的邊界可用兩個矩形來近似地定義:

declare @utahRegion varchar(max)
set @utahRegion = 'region '
+ 'rect latlon 37 -114.0475  41 -109.0475 ' -- Main part
+ 'rect latlon 41 -114.0475  42 -111.01  '  -- Ogden and Salt Lake.

現在,我們可以用以下查詢來查找猶他州中的所有流量計:

select S.*
from (
select ObjID
from fHtmCoverRegion(@utahRegion)
loop join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
and (((     lat between 37        and      41)  -- Careful test
and (lon between -114.0475 and -109.04)) -- Are we inside
or ((   lat between 41        and      42)  -- one of the two
and (lon between -114.0475 and -111.01)) -- boxes?
)
and type = 'S' ) as G
join Station S on G.objID = S.StationNumber
OPTION (FORCE ORDER)

覆蓋返回了 38 個 trixel。聯接返回了 775 個測量站。仔細檢查找到了猶他州中的 670 個測量站,另外有懷俄名州的兩個測量站正好位於接壤處(14% 的假正值)。

大多數州需要使用複雜得多的區域。例如,近似地將加利福尼亞州的邊界連起來的區域爲:

declare @californiaRegion varchar(max)
set @californiaRegion = 'region '
+ 'rect latlon 39    -125 '    -- Nortwest corner
+   '42    -120 '    -- Center of Lake Tahoe
+ 'chull latlon 39    -124 '    -- Pt. Arena
+ '39    -120 '    -- Lake Tahoe.
+ '35    -114.6 '  -- Start Colorado River
+ '34.3  -114.1 '  -- Lake Havasu
+ '32.74 -114.5 '  -- Yuma
+ '32.53 -117.1 '  -- San Diego
+ '33.2  -119.5 '  -- San Nicholas Is
+ '34    -120.5 '  -- San Miguel Is.
+ '34.57 -120.65 ' -- Pt. Arguelo
+ '36.3  -121.9 '  -- Pt. Sur
+ '36.6  -122.0 '  -- Monterey
+ '38    -123.03 ' -- Pt. Rayes
select stationNumber
from fHtmCoverRegion(@californiaRegion)
loop join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
/* and <careful test> */
and type = 'S'
join Station S on objID = S.StationNumber
OPTION (FORCE ORDER)

覆蓋返回了 108 個 trixel,一共包含 2132 個測量站。其中,1928 個位於加利福尼亞州內,因此假正值比率大約爲 5%。但是仔細檢查並非小事。

針對地點而非測量站進行的相同查詢(包括仔細檢查)類似於以下代碼:

select *
from Place
where HtmID in
(select distinct SI.objID
from fHtmCoverRegion(@californiaRegion)
loop join SpatialIndex SI
on SI.HtmID between HtmIdStart and HtmIdEnd
and SI.type = 'P'
join place P on SI.objID = P.HtmID
cross join fHtmRegionToTable(@californiaRegion) Poly
group by SI.objID, Poly.convexID
having min(SI.x*Poly.x + SI.y*Poly.y + SI.z*Poly.z - Poly.d) >= 0
)
OPTION (FORCE ORDER)

此查詢使用加利福尼亞州的凸形半空間表示法和 [Gray] 中介紹的技術來快速檢查某個點是否位於加利福尼亞州凸球面內。它返回了 885 個地點,其中 7 個位於與加利福尼亞州毗鄰的亞利桑那州(多邊形近似於加利福尼亞州的邊界)。它在 1GHz 的處理器上運行了 0.249 秒。如果不用 OPTION(FORCE ORDER) 子句,其運行速度將變慢,需要花費 247 秒。

由於此要求十分常見,而且代碼極具技巧性,因此我們添加了 fHtmRegionObjects(Region,Type) 過程,用來從空間索引中返回對象 ID。由於此過程封裝了前面所示的兩個技巧性代碼,因此這兩個針對加利福尼亞州的查詢變爲了:

select *                 -- Get all the California River Stations
from Station
where stationNumber in   -- that are inside the region
(select ObjID
from fHtmRegionObjects(@californiaRegion,'S'))
select *                 -- Get all the California cities
from Place
where HtmID in           -- that are inside the region
(select ObjID
from fHtmRegionObjects(@californiaRegion,'P'))

針對科羅拉多州和猶他州的查詢也可以使用此例程來簡化。

結束語

在此所述的 HTM 空間索引庫本身是有趣而又有用的。基於球面爲“多邊形內的點”查詢索引數據是一種便利的方法。但是,該庫還作爲一個示例很好地說明了如何通過添加以諸如 C#、C++、Visual Basic 或 Java 之類的語言進行實際計算的類庫來擴充 SQL Server 和其他數據庫系統。實現功能強大的表值函數和標量函數並將這些查詢及其永久數據集成到數據庫中的能力是一種非常強大的擴充機制,它將在保證對象關係數據庫的基礎上傳遞。這僅僅是個開頭。在接下來的十年中,編程語言和數據庫查詢語言有可能獲得更好的數據集成方式。這對於應用程序開發人員來說將是一件好事。

詳細信息請參見:

http://msdn.microsoft.com/sql/

項目編輯:Susanne Bonney

附錄 A:參考資料

  • [Gray]“There Goes the Neighborhood:Relational Algebra for Spatial Data Search”。Jim Gray、Alexander S. Szalay、Gyorgy Fekete、Wil O'Mullane、Maria A. Nieto-Santisteban、Aniruddha R. Thakar、Gerd Heber、Arnold H. Rots,MSR-TR-2004-32,2004 年 4 月

  • [Szalay]“Indexing the Sphere with the Hierarchical Triangular Mesh”。Alexander S. Szalay、Jim Gray、George Fekete、Peter Z. Kunszt、Peter Kukol、Aniruddha R. Thakar,Microsoft SQL Server 2005 Samples。

  • [Fekete]“SQL SERVER 2005 HTM Interface Release 4”。George Fekete、Jim Gray、Alexander S. Szalay,2005 年 5 月 15 日,Microsoft SQL Server 2005 Samples。

  • [Samet1]“Applications of Spatial Data Structures:Computer Graphics, Image Processing, and GIS”。Hanan Samet,Addison-Wesley, Reading, MA, 1990。ISBN0-201-50300-0。

  • [Samet2]“The Design and Analysis of Spatial Data Structures”。Hanan Samet,Addison-Wesley, Reading, MA, 1990。ISBN 0-201-50255-0。

附錄 B:基本 HTM 例程

本節將介紹 HTM 例程。附帶文檔 [Szalay] 中含有用於每個例程的手冊頁,並且這些例程本身帶有批註以支持 IntelliSense。

在下面,lat 和 lon 以十進制度數表示(南部和西部緯度爲負),距離以海里(弧度分)表示。

HTM 庫版本:fHtmVersion() 將返回版本字符串

例程將返回一個 nvarchar(max) 字符串來給出 HTM 庫版本。

使用示例:

   print dbo.fHtmVersion()

返回的內容類似以下結果:

   'C# HTM.DLL V.1.0.0 1 August 2005'

生成 HTM 鍵:fHtmLatLon (lat, lon) 將返回 HtmID

例程將返回該 LatLon 點的 21 級深度的 HTM ID。

使用示例:

   update Place set HtmID = dbo.fHtmLatLon(Lat,Lon)

還有 fHtmXyz()fHtmEq() 函數可供天文學家使用。

LatLon 到 XYZ:fHtmLatLonToXyz (lat,lon) 將返回點 (x, y, z)

例程將返回該 Lat Lon 點的笛卡爾座標。

使用示例(這是標識函數):

Select LatLon.lat, LatLon.lon-360
from fHtmLatLonToXyz(37.4,-122.4) as XYZ cross apply
fHtmXyzToLatLon(XYZ.x, XYZ.y, XYZ.z) as LatLon

還有 fHtmEqToXyz() 函數可供天文學家使用。

XYZ 到 LatLon:fHtmXyzToLatLon (x,y,z) 將返回點 (lat, lon)

例程將返回該 Lat Lon 點的笛卡爾座標。

使用示例(這是標識函數):

Select LatLon.lat, LatLon.lon-360
from fHtmLatLonToXyz(37.4,-122.4) as XYZ cross apply
fHtmXyzToLatLon(XYZ.x, XYZ.y, XYZ.z) as LatLon

還有 fHtmXyzToEq() 函數可供天文學家使用。

查看 HTM 鍵:fHtmToString (HtmID) 將返回 HTM 字符串

如果給定了 HtmID,則例程將返回一個 nvarchar(32),其形式爲 [N|S]t1t2t3...tn,其中每個三角數字 ti 均爲 {0,1,2,3} 格式,用以說明三角網格中該深度的 HTM trixel。

使用示例:

print 'SQL Server development is at: ' +
dbo.fHtmToString(dbo.fHtmLatLon(47.646,-122.123))

返回結果:'N132130231002222332302'。

還有 fHtmXyz()fHtmEq() 函數可供天文學家使用。

HTM trixel 中心點:fHtmToCenterpoint(HtmId) 將返回點 (x, y, z)

返回由 HtmID 指定的 HTM trixel 的笛卡爾中心點。

使用示例:

select * from fHtmToCenterPoint(dbo.fHtmLatLon(47.646,-122.123))

HTM trixel 角點:fHtmToCornerpoints(HtmId) 將返回點 (x, y, z)

返回由 HtmID 指定的 HTM trixel 的三個笛卡爾角點。

使用示例:

select * from fHtmToCornerPoints(dbo.fHtmLatLon(47.646,-122.123))

計算距離:fDistanceLatLon(lat1, lon1, lat2, lon2) 將返回距離

以海里(弧度分)爲單位計算兩點之間的距離。

使用示例:

declare @lat float, @lon float
select @lat = lat, @lon = lon
from  Place
where PlaceName = 'Baltimore' and State = 'MD'
select PlaceName,
dbo.fDistanceLatLon(@lat,@lon, lat,  lon) as distance
from Place

還有 fDistanceXyz()fDistanceEq() 函數可供天文學家使用。

下面的例程可返回一個用作空間索引的表。所返回空間索引表的數據定義爲:

SpatialIndexTable table (
HtmID   bigint   not null , -- HTM spatial key (based on lat/lon)
Lat     float    not null , -- Latitude in Decimal
Lon     float    not null , -- Longitude in Decimal
x       float    not null , -- Cartesian coordinates,
y       float    not null , -- derived from lat-lon
z       float    not null , --,
Type    char(1)  not null , -- place (P) or gauge (G)
ObjID   bigint   not null , -- object ID in table
distance float   not null , -- distance in arc minutes to object
primary key (HtmID, ObjID) )

查找鄰近的對象:fHtmNearbyLatLon(type, lat, lon, radius) 將返回空間索引表

返回半徑範圍內特定類型的對象列表及它們到給定點的距離。該列表將按對象的位置由近到遠排列。

使用示例:

   select distance, Place.*
from fHtmNearbyLatLon('P', 39.3, -76.6, 10) I join Place
on I.objID = Place.HtmID
order by distance

還有 fHtmGetNearbyEq()fHtmGetNearbyXYZ() 函數可供天文學家使用。

查找最近的對象:fHtmNearestLatLon(type, lat, lon) 將返回空間索引表

返回包含距離該點最近的特定類型對象的列表。

使用示例:

   select distance, Place.*
from fHtmNearestLatLon('P', 39.3, -76.6) I join Place
on I.objID = Place.HtmID

還有 fHtmGetNearbyEq()fHtmGetNearbyXYZ() 函數可供天文學家使用。

下面的例程將返回一個表,用以說明覆蓋所需區域的一組 trixel(HIM 三角形)的 HtmIdStart 和 HtmIdEnd。表的定義爲:

TrixelTable table (
HtmIdStart   bigint not null , -- min HtmID in trixel
HtmIdEnd     bigint not null   -- max HtmID in trixel
)

圓圈區域 HTM 覆蓋:fHtmCoverCircleLatLon(lat, lon, radius) 將返回 trixel 表

返回覆蓋指定圓圈的 trixel 表。

使用示例:

declare @answer nvarchar(max)
declare @lat float, @lon float
select @lat = lat, @lon = lon
from Place
where Place.PlaceName = 'Baltimore'
and State = 'MD'
set @answer = ' using fHtmCoverCircleLatLon() it finds:      '
select @answer = @answer
+ cast(P.placeName as varchar(max)) + ', '
+ str( dbo.fDistanceLatLon(@lat,@lon, I.lat, I.lon) ,4,2)
+ '  arc minutes distant.'
from SpatialIndex I join fHtmCoverCircleLatLon(@lat, @lon, 5)
On HtmID between HtmIdStart and HtmIdEnd -- coarse test
and type = 'P'                           -- it is a place
and dbo.fDistanceLatLon(@lat,@lon, lat, lon)
between 0.1 and 5  -- careful test
join Place P on I.objID = P.HtmID
order by dbo.fDistanceLatLon(@lat,@lon, I.lat, I.lon) asc
print 'The city within 5 arc minutes of Baltimore is: '
+ 'Lansdowne-Baltimore Highlands, 4.37 arc minutes away'

還有 fHtmCoverCircleEq() 函數可供天文學家使用。

HTM 覆蓋的常規區域指定:fHtmCoverRegion(region) 將返回 trixel 表

返回覆蓋指定區域的 trixel 表(本主題前面對區域進行了介紹)。

select S.*
from  (select ObjID
from fHtmCoverRegion('RECT LATLON 37 -109.55  41 -102.05')
loop join SpatialIndex
on HtmID between HtmIdStart and HtmIdEnd
and lat between 37 and 41
and lon between -109.05 and -102.048
and type = 'S') as G
join Station S on G.objID = S.StationNumber
OPTION (FORCE ORDER)

常規區域指定:fHtmRegionToNormalFormString(region) 將返回區域字符串

返回格式爲 REGION {CONVEX {x y z d}* }* 的字符串,其中已從每個凸形刪除多餘的半空間;如 [Fekete] 中所述,凸形已被簡化。

print dbo.fHtmToNormalForm('RECT LATLON 37 -109.55  41 -102.05')

下面的例程將返回一個表,用以說明覆蓋所需區域的一組 trixel(HIM 三角形)的 HtmIdStart 和 HtmIdEnd。表的定義爲:

RegionTable (convexID     bigint not null , -- ID of the convex, 0,1,...
halfSpaceID bigint not null     -- ID of the halfspace
-- within convex, 0,1,2,
x           float  not null     -- Cartesian coordinates of
y           float  not null     -- unit-normal-vector of
z           float  not null     -- halfspace plane
d           float  not null     -- displacement of halfspace
)                               -- along unit vector [-1..1]

將區域字符串轉換爲表:fHtmRegionToTable(region) 將返回區域表

返回一個表,用以將區域作爲凸形組合來說明,其中每個凸形均爲 x,y,z,d 半空間的交集。如 [Fekete] 中所述,凸形已被簡化。本文第 4 節介紹了此函數的用法。

select *
from dbo.fHtmToNormalForm('RECT LATLON 37 -109.55  41 -102.05')

查找區域內的點:fHtmRegionObjects(region, type) 將返回對象表

返回一個表,其中包含空間索引中具有指定類型且位於區域內的對象的對象 ID。

select *            -- find Colorado places.
from Places join
where HtmID in
select objID
from dbo. fHtmRegionObjects('RECT LATLON 37 -109.55  41 -102.05','P')

常規區域診斷:fHtmRegionError(region ) 將返回消息

如果區域定義有效,則返回“OK”;否則,返回描述區域定義問題的診斷信息,並且後跟區域的語法定義。

print dbo.fHtmRegionError ('RECT LATLON 37 -109.55 41 -102.05')

轉到原英文頁面

返回頁首 返回頁首

轉載說明
作者:來自微軟MSDN相關文章
網址:本文引用自http://msdn2.microsoft.com/zh-cn/library/aa964138.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章