如何返回Zipcode列表的## Mile Radius内的所有实例

时间:2010-10-12 23:48:20

标签: c# sql sql-server database tsql

SQL Server 2008添加了一些很酷的新Spacial类型,并为SQL开发人员提供了更强大的操作空间数据的方法,但它仍然无法让我如何有效地返回,例如,只有位于##英里半径范围内的位置一长串拉链(20到15,000个不同的拉链码)。

有一种简单的方法吗?想到的唯一可能的解决方案似乎有些可怕,因为产生了cartiasian产品,因此计算得非常多......

我擅长创建CLR SP和函数,如果这有帮助(我认为它会......)。

我不太关心如何找到2点(或地理类型)之间的距离,而不是“是提供列表中任何一个zipcodes(地理点)的##里程内的给定位置?”这里的复杂部分是要搜索的拉链列表。

感谢。

6 个答案:

答案 0 :(得分:4)

我必须实施地理位置搜索,经过大量研究后我决定使用sql2008地理位置。您需要一个填充了lat / long的邮政编码表。该表应如下所示:

CREATE TABLE [dbo].[PostalCodes](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [StateID] [bigint] NOT NULL,
    [PostalCode] [varchar](10) NOT NULL,
    [Latitude] [decimal](16, 12) NULL,
    [Longitude] [decimal](16, 12) NULL,
    [GeographyLocation] [geography] NULL,
    [CreatedOn] [datetime] NOT NULL,
    [LastUpdated] [datetime] NOT NULL,
    [GeographyLocation_temp] [varchar](100) NULL,
 CONSTRAINT [PK_PostalCode] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

我从GeoNames.org下载了一个国际邮政编码列表,并将其导入为tmp_GeoNames。然后我运行以下脚本将数据插入到我的zipcode表中并创建空间索引。 (我必须添加自己的StateID列并填充它,但您可以跳过该部分并将其从脚本中删除。)

INSERT INTO PostalCodes
(StateID, PostalCode, Latitude, Longitude)
SELECT DISTINCT StateID, PostalCode, Latitude, Longitude FROM temp_GeoNames where stateID is not null

UPDATE PostalCodes
SET GeographyLocation_temp= 'POINT(' + CONVERT(VARCHAR(100),longitude)
+' ' +  CONVERT(VARCHAR(100),latitude) +')'

UPDATE PostalCodes
SET GeographyLocation  =  geography::STGeomFromText(GeographyLocation_temp,4326)

CREATE SPATIAL INDEX  SIndx_SpatialTable_geography_col1
   ON PostalCodes(GeographyLocation);

最后,我创建了一个接受lat / long的函数,并返回特定范围内的所有zipcodes。因为它使用空间索引,所以它非常快。

CREATE FUNCTION [dbo].[PostalCode_SelectNearest]
(
    @Latitude [decimal](16, 12)
    ,@Longitude [decimal](16, 12)
    ,@RangeInMiles int
)
RETURNS @PostalCodes Table (PostalCode varchar(10) PRIMARY KEY NOT NULL, DistanceInMiles FLOAT NULL)
AS
BEGIN
    --Create geography point based on Lat/Long passed ... careful, the values passed are reversed from normal thinking
    DECLARE @g geography;
    SET @g = geography::STGeomFromText('POINT(' +
                                         CONVERT(varchar,@Longitude) + ' ' +
                                         CONVERT(varchar,@Latitude) + ')', 4326);
    --Select the nearest Postal Codes
    INSERT INTO @PostalCodes (PostalCode, DistanceInMiles)
    SELECT PostalCode, GeographyLocation.STDistance(@g)/1609.344 as DistanceInMiles
    FROM PostalCodes
    WHERE GeographyLocation.STDistance(@g)<=(@RangeInMiles * 1609.344)

    RETURN;
END

我意识到这不是你想要的,但它可以转换成你的目的。我发现使用zipcodes比城市更有效和准确,因为城市可以跨越许多邮政编码,因此返回对最终用户来说错误的数据。

这一切都以美国为中心,但很容易转换为国际使用。我打算在将来的某个时候这样做,但还没有必要。

答案 1 :(得分:1)

还要考虑这需要多么精确......对于小半径,(不需要大圆数学),只需将一个方形内的所有位置放在一边多英里即可。如果你有每个邮政编码的纬度和经度,只需一个过滤器即可完成,无需任何计算。并且返回的行数将被关闭(它将太多)仅为因子1 - pi / 4,仅为约21%

foreach给定位置(Tgt lat / long)
  - 假设半径为海里(6080英尺),
纬度和经度以总分钟为单位测量 (即30度,10分钟= 1810分钟)

然后:

Select * From theTable
   Where Latitude  Between TgtLat - radius 
                    And TgtLat + radius
     And Longitude Between TgtLong - radius/Cos(TgtLat)
                    And TgtLong + radius/Cos(TgtLat)

答案 2 :(得分:0)

我确实有一个sql函数可以为你执行那些实际上并不那么慢的可怕计算。但是这里有一个链接,以及如何使用sql 2008中的新功能执行查询:http://msdn.microsoft.com/en-us/magazine/dd434647.aspx

编辑:更多链接:

http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/sql-server-2008-proximity-search-with-th

答案 3 :(得分:0)

查看GeoNames Webservice。我用过这个并且效果很好。

http://www.geonames.org/export/client-libraries.html

答案 4 :(得分:0)

我已经完成了与Oracle Spatial非常相似的事情,所以我不熟悉MSSQL的空间特性,因此我的回答很简单:

我将假设您拥有表示包含每个邮政编码的多边形的数据,您所要做的就是获取邮政编码列表,合并他们的多边形,然后询问所有记录组合多边形或其边缘的 x 英里。某些空间包具有“多种”类型,允许您在非连续区域(在您的情况下为非相邻邮政编码)中进行组合和操作。

如果你拥有的只是邮政编码的中心点,你可以做同样的事情:组合点并在 x 里面寻找任何东西。这里的缺陷是一些邮政编码可能非常大,你会丢失一些符合你标准但不在中心点的 x 英里范围内的记录。

听起来很毛茸茸,但用于空间数据的索引系统非常有效。

HTH。

答案 5 :(得分:0)

无论您决定使用哪种解决方案,您都需要一个邮政编码数据库。这是one,下载并将其导入表格。