技术开发 频道

SQL Server 2008:新位置感知数据类型

  【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 示例应用程序

  按下最顶端的按钮,它可以将一个几何点保存到数据库中,为了存储这些数据,首先需要创建表: 

CREATE TABLE [dbo].[GeometryTest](

  
[ID] [int] IDENTITY(1,1) NOT NULL,

  
[Points] [geometry] NOT NULL,

  
[Description] [nvarchar](50) NULL

  )

  有了这个表,你就可以使用下面的C#代码将值存储到该字段中了:

Microsoft.SqlServer.Types;

  ...

  
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方法完成,而它又是这样实现的:

 internal int 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值进行检索,在程序代码,可能需要实现下面的代码:

 internal SqlGeometry ReadPointFromDatabase(int 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创建图像:

 http://www.bing.com/maps/default.aspx?v=2&FORM=LMLTCC&cp=37.839179~-119.541904&style=r&lvl=8

  在这个例子中,查询字符串cp后指定了经纬度值-119.5和37.8,有了这个信息,你可以使用下面的代码构造URL: 

int id = int.Parse(pointIdTextBox.Text);

  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对象构建几何对象,下面是使用这个类的例子: 

SqlGeometryBuilder builder = new 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 square = builder.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版本会这方面进行增强。

0
相关文章