如何返回邮政编码列表的## 英里半径内的所有实例
Posted
技术标签:
【中文标题】如何返回邮政编码列表的## 英里半径内的所有实例【英文标题】:How To Return All Instances Within ## Mile Radius of a List of Zipcodes 【发布时间】:2010-10-12 23:48:20 【问题描述】:SQL Server 2008 添加了一些很酷的新空间类型,并为 SQL 开发人员提供了更强大的空间数据处理方法,但它仍然让我无法有效地返回,例如,仅返回 ## 英里半径范围内的位置。一长串邮政编码(20 到 15,000 个不同的邮政编码)。
有没有简单的方法来做到这一点?由于创建了笛卡尔积,因此想到的唯一可能的解决方案似乎有些可怕,因此计算的数量也很荒谬......
如果有帮助的话,我擅长创建 CLR SP 和函数(我认为它会...)。
我不太关心如何找到 2 个点(或地理类型)之间的距离,而是“在提供的列表中的任何邮政编码(地理点)的 ## 英里内的给定位置吗?”这里复杂的部分是要搜索的 zip 列表。
谢谢。
【问题讨论】:
【参考方案1】:我必须实施地理定位搜索,经过大量研究后,我决定使用 sql2008 地理。您需要一个包含纬度/经度的邮政编码表。该表应如下所示:
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。然后我运行以下脚本将数据插入到我的邮政编码表中并创建空间索引。 (我必须添加自己的 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);
最后我创建了一个函数,它接受纬度/经度并返回一定范围内的所有邮政编码。因为它使用空间索引,所以速度非常快。
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
我知道这不是您正在寻找的,但它可以转换为您的目的。我发现使用邮政编码比城市更有效、更准确,因为城市可以跨越许多邮政编码,因此向最终用户返回的数据是错误的。
这一切都非常以美国为中心,但很容易转化为国际使用。我计划在未来的某个时候这样做,但还没有需要。
【讨论】:
【参考方案2】:还要考虑这需要有多准确...对于小半径(不需要大圆数学),只需获取边上那么多英里的正方形内的所有位置就足够了。 . 如果您有每个邮政编码的纬度和经度,只需一个过滤器即可完成此操作,无需任何计算。并且返回的行数将仅以 1 - pi/4 的因子关闭(太多了),也就是大约 21%
foreach 给定位置(Tgt 纬度/经度) - 假设半径以海里(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)
【讨论】:
【参考方案3】:好吧,我确实有一个 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
【讨论】:
【参考方案4】:查看 GeoNames Web 服务。我用过这个,效果很好。
http://www.geonames.org/export/client-libraries.html
【讨论】:
【参考方案5】:我已经用 Oracle Spatial 做了一些非常相似的事情,所以我的回答持保留态度,因为我不熟悉 MSSQL 的空间特性:
我将继续假设您有代表包含每个邮政编码的多边形的数据,您所要做的就是获取邮政编码列表,组合它们的多边形,然后询问属于其中的所有记录组合多边形或其边缘 x 英里内。一些空间包具有“多角”类型,可让您在不连续的区域(在您的情况下为不相邻的邮政编码)进行组合和操作。
如果您只有邮政编码的中心点,您可以执行相同的操作:合并这些点并查找 x 英里内的任何内容。这里的陷阱是某些邮政编码可能非常大,您会丢失一些符合您的标准但不在中心点 x 英里范围内的记录。
听起来很麻烦,但用于空间数据的索引系统非常有效。
HTH。
【讨论】:
【参考方案6】:无论您决定采用何种解决方案,您都需要一个邮政编码数据库。这里是one,下载后导入表格。
【讨论】:
以上是关于如何返回邮政编码列表的## 英里半径内的所有实例的主要内容,如果未能解决你的问题,请参考以下文章