【IT168 技术文档】介绍
随着价格的不断下降,基于GPS的设备逐渐在大众消费者中普及,人们对地理位置的重要性认识越来越深刻,商业企业,如物流和制造公司对位置的依赖性就很强,对企业的发展也很关键。
要存储位置数据也相当简单,只需要将位置的经纬度值存储起来即可,使用简单的浮点字段就可以存储这些信息,但依靠这种数据类型只能实现一些简单的应用,如果你想创建更高级的应用,或使用几何算法来定位数据点,则必须自己动手编写代码,例如,如何在10英里范围内快速找出所有存储的位置。在这种情况下,如果数据库本身可以为你做一些工作,那不是更好吗?这也是SQL Server 2008的目的之一,在SQL Server 2008中,新增了基于位置的数据类型,具有地理空间特性。接下来将了解到这个新的数据类型是如何工作的。
两个类型,两个模型
SQL Server 2008支持两个类似但不同的数据类型:geometry和geography。如果事情简单,这两个数据类型都可以存储x和y值,并支持基于这些值的计算。分成两个不同的数据类型是因为geometry是基于平面进行计算的,而geography是基于地球的实际形状计算的。它们之间的差异实际上是很大的,例如计算最短路线时,基于平面的计算结果和基于圆的的计算结果可能相差很大。我们以航线为例,如图1所示,两站之间基于平面的距离和基于圆的距离可能相差十万八千里。实际上,SQL Server 2008可以根据多个不同的圆度和坐标进行计算,这种支持是必要的,因为不同的国家地球形状略有不同,计算方法略有差异,如果不小心,这些差异即使可能很微小,但最终也会影响到你的结果。

图 1 哪条线路是最短的?
SQL Server 2008引入了一个空间参考标识符(Spatial Reference Identifiers,简称SRID)的概念,当你使用geometry和geography这两个数据类型时就必须使用它。对于最简单的geometry数据类型,可以忽略SRID值,或设置为0;对于geography数据类型,你就必须明确设置SRID值。SQL Server一般使用WGS 84坐标系(World Geodetic System 1984),它给SRID赋予了一个魔法值4326,所有支持的SRID可以从Master数据库中的sys.spatial_reference_systems视图查询到,如图2所示。

图 2 SQL Server支持许多不同的SRID
如今,不同坐标系的重要性已经降低了,WGS 84坐标系目前正用于全球的GPS系统,因此,大部分在线地图网站都是基于相同的参照系统。
重温欧几里得几何
如果你上数学课时没有打瞌睡,你一定会回忆得起欧几里得几何和最简单的直角坐标系,如图3所示,在这个坐标系中,起点的坐标是(0,0),x轴是从左往右递增,y轴是从下向上递增,无论使用哪种单位,计算出来的距离都是一样的。

图 3 经典的直角坐标系
SQL Server的geometry数据类型在这种模型下可以工作得很好,SQL Server内部将其当作.Net类型,因此你可以在应用程序中直接使用相同的类型,这个类型定义在Microsoft.SqlServer.Types.dll中,这个动态库默认位于C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies。
如果你想将这些数据类型真正用起来,看看图4的例子就会有一点感觉,这个示例程序很简单,上面是数据库中存储的数值,下面是在地图上对这些点位的标注,此外,它可以检查一个点是否在某个多边形范围内。

图 4 示例应用程序
按下最顶端的按钮,它可以将一个几何点保存到数据库中,为了存储这些数据,首先需要创建表:
[ID] [int] IDENTITY(1,1) NOT NULL,
[Points] [geometry] NOT NULL,
[Description] [nvarchar](50) NULL
)
有了这个表,你就可以使用下面的C#代码将值存储到该字段中了:
...
private void geometryAddPointButton_Click(
object sender, EventArgs e)
{
int x = int.Parse(geometryXTextBox.Text);
int y = int.Parse(geometryYTextBox.Text);
SqlGeometry geom = SqlGeometry.Point(x, y, 0);
// add to database
string sql = "INSERT INTO [geometrytest] " +
"([points], [description]) VALUES " +
"(@points, @description)";
StorePointIntoDatabase(geom, sql, "geometry",
"My first geometry point");
}
首先,代码从用户界面读取x和y坐标,然后利用x和y值构造一个SqlGeometry实例,SqlGeometry类型位于Microsoft.SqlServer.Types命名空间中,那么如何使用类的Point方法构造一个点对象呢,最后一步,构造一个SQL insert语句,真正的数据库访问由StorePointIntoDatabase方法完成,而它又是这样实现的:
object geoPoint, string sql,
string udtTypeName, string description)
{
SqlConnection conn = GetConnection();
try
{
SqlCommand cmd = new SqlCommand(
sql, conn);
SqlParameter param = cmd.Parameters.
AddWithValue("@points", geoPoint);
param.UdtTypeName = udtTypeName;
cmd.Parameters.AddWithValue(
"@description", description);
conn.Open();
int rows = cmd.ExecuteNonQuery();
MessageBox.Show("Added " + rows +
" row(s) to the database.");
cmd.Dispose();
return rows;
}
finally
{
conn.Dispose();
}
}
如果你之前曾经使用过SQL Server,上面的代码你应该很熟悉。首先,打开一个SQL Server数据库连接(关于打开数据库连接的细节在这里并不重要),然后使用给定的insert语句构造一个SqlCommand对象。
注意Parameters.AddWithValue调用是如何指定参数值的,在其它大多数参数类型中,单独调用AddWithValue就足够了,但新的地理空间类型是当作UDF类型(.Net类型)的,参数对象的UdtTypeName属性必需设置,对于geometry类型,它设为geometry,对于geography类型就设为geography。
现在你应该知道空间数据是如何添加到SQL Server 2008的数据库表中的了,现在你需要知道如何从这些表中读取空间数据,假设你的数据库表名是GeometryTest,这个表有一个id值,你可能需要根据给定的id值进行检索,在程序代码,可能需要实现下面的代码:
{
SqlConnection conn = GetConnection();
try
{
string sql = "SELECT [points] " +
"FROM [geometrytest] " +
"WHERE ([id] = @id)";
SqlCommand cmd = new SqlCommand(
sql, conn);
cmd.Parameters.AddWithValue("@id", id);
conn.Open();
object geometryPoint = cmd.ExecuteScalar();
if ((geometryPoint != null) &&
(geometryPoint is SqlGeometry)) {
return (SqlGeometry)geometryPoint;
}
else return null;
}
finally
{
conn.Dispose();
}
}
这里实现得非常简单,你只需要一个SQL Server连接,一个命令对象和一条简单的SQL语句,然后你就可以设定参数值读取结果。
在地图上绘制点
现在你已经知道如何从数据库读取一个点的值,接下来可以做更有趣的事情了,如在地图上绘制该点。在web上,有许多地图服务提供商,如谷歌,微软等。
如果你采用微软的方案,必应地图提供了一个选择,当必应显示你的地图时,它使用类似下面的URL创建图像:
在这个例子中,查询字符串cp后指定了经纬度值-119.5和37.8,有了这个信息,你可以使用下面的代码构造URL:
SqlGeography geog = ReadPointFromDatabase(id);
string bingMapsFormat =
"http://www.bing.com/maps/default.aspx?" +
"v=2&FORM=LMLTCC&cp={0}~{1}&style=r&lvl=8";
string bingMapsUrl = string.Format(
bingMapsFormat, geog.Lat, geog.Long);
webBrowser1.Navigate(bingMapsUrl);
MessageBox.Show("Point " + geog.Lat +
", " + geog.Long + " plotted!");
这里的ReadPointFromDatabase方法可以从数据库中根据id检索SqlGeography对象,接下来的代码构造了必要的URL值,然后使用一个简单的WinForms WebBrowser组件显示包含了地图的网页,如图4所示。
当然,更好的实现应该使用真实的地图控件,并在地图顶部增加地图操作按钮,但对于许多简单的应用,能够以某点为中心显示地图已经足够了。
多边形计算实例
到目前为止,我们已经讨论了geometry和geography类型,但仅讨论了单点情况,虽然二维空间的点非常有用,但如果能存储一条线,或一个多边形在数据库中不是更有用吗?好消息是你可以做到!
还是以SqlGeometry为例,使用静态Point方法,你可以将一个点存储到对象中,但同一个类中也包含创建线和多边形的方法,如图5所示。因此每个SqlGeometry对象(或一个SqlGeography对象)可以存储一到多个点,你可以使用这些点形成任何你想要的对象。

图 5 SqlGeometry类的方法
例如,假设你要存储一条直线,这需要两个点:起点和终点,你可以使用两个字段来分别存储起点和终点,但SQL Server 2008允许你在一个geometry数据类型中存储这两个点,你可能会开始思考在一个表字段中存储一到多个点会有什么好处,其中一个原因就是存储效率,另一个就是计算,是的,SQL Server和对应的.Net对象类型允许你执行计算。
你可能想用存储在数据库中的多个点组成一个多边形,如一个正方形,然后你可能需要确定另一个点是否也在这个正方形范围内,解决起来也很简单,可以使用C#代码实现,也可以直接通过SQL Server的功能实现。
除了判断点是否存在等简单应用外,SQL Server也支持更多的高级特性,例如,你可以计算区域,找出对象的中心位置,检查多边形是否闭合,或者找出多边形上的点数。这为你提供了无限可能,特别要提出的是你既可以在代码中使用SqlGeometry和SqlGeography对象实现,又可以直接在存储过程的函数中实现。
我们还是以正方形为例,我们的目标是监测某个点是否在该正方形范围内,首先使用直角坐标系((0,0), (0,10), (10,10), (10,0))定义一个正方形,然后确定点(5,5)是否在该正方形内,如图6所示,你如何使用SqlGeometry .Net类实现这个测试呢?

图 6 这个点在正方形内吗?
首先你需要定义你的正方形,在.Net中你可以使用StringBuilder组件,或者使用SqlGeometryBuilder对象构建几何对象,下面是使用这个类的例子:
builder.SetSrid(0);
builder.BeginGeometry(OpenGisGeometryType.Polygon);
builder.BeginFigure(0, 0);
builder.AddLine(0, 10);
builder.AddLine(10, 10);
builder.AddLine(10, 0);
builder.AddLine(0,0);
builder.EndFigure();
builder.EndGeometry();
在这里,SqlGeometryBuilder(Microsoft.SqlServer.Types命名空间的一部分)使用四个点构造了一个正方形,但因为你可以在多个不同的对象中存储不受数量限制的点,你必须在开始和结束时指定一个数字,在此之前,你需要指定空间参考id(SRID),以及你使用BeginGeometry方法创建的对象类型,虽然SRID的值可以忽略,但SRID必须指定,因此0是非常好的选择。
现在已经定义好正方形,现在该检查那个点是否在正方形内了,这可以使用SqlGeometry对象的STContains方法实现,但首先需要创建一个几何对象的实例,可以使用ConstructedGeometry属性完成,如:
SqlGeometry point = SqlGeometry.Point(5, 5, 0);
SqlBoolean contains = square.STContains(point);
if (contains.Value)
{
MessageBox.Show("Contains point!");
}
你可能已猜到,上面的代码会显示一条消息,显示点(5,5)在该正方形内,实际上这一点是正方形的中心点。
小结
SQL Server 2008支持许多有趣的新特性,其中一个就是支持空间数据,通过阅读本文,你应该已经了解了geometry和geography类型,并看到了如何在C#应用程序中使用这些类型,如如何读写数据点,以及如何利用经纬度值在地图上显示一个点。
虽然使用浮点字段也可以存储经纬度值,但SQL Server 2008让这一切都变得更加简单了,表中的每个geometry或geography数据类型都可以存储一到多个点,因此可以形成任意复杂的多边形,这种支持让你可以在数据库表中只创建一个字段,就可以存储多个点的数据,如果利用浮点字段是很难办到的。
新的空间数据类型另一个好处是你可以利用这些值进行计算,SQL Server 2008为各种不同的计算提供了良好的支持,相信下一个SQL Server版本会这方面进行增强。