我有以下表格城市:
ID(int),City(char),latitude(float),longitude(float).
现在根据用户的经度(例如:44.8)和纬度(例如:46.3),我想在100英里/公里范围内搜索他附近的所有城市。
我找到了一些例子但不知道如何使它们适应我的案例
select *
from GEO.Cities a
where SDO_WITHIN_DISTANCE([I don`t know],
MDSYS.SDO_GEOMETRY(2001, 8307, MDSYS.SDO_POINT_TYPE(44.8,46.3, NULL) ,NULL, NULL),
'distance = 1000') = 'TRUE';
任何帮助都将不胜感激。
P.S:如果可以有距离并进行分类
P.P.S:由于性能问题,我想以这种方式做到这一点,我这样做了http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL,但这需要太长时间......
答案 0 :(得分:16)
你对mySQL距离搜索有很好的参考。
忘记Oracle Spatial的东西。代码太多,复杂性太高,没有足够的增值。
这是一个可以解决问题的查询。这使用法定里程的距离。 编辑这修复了mdarwin提到的错误,如果您尝试将其用于北极或南极的位置,则会以划分检查为代价。
SELECT id, city, LATITUDE, LONGITUDE, distance
FROM
(
SELECT id,
city,
LATITUDE, LONGITUDE,
(3959 * ACOS(COS(RADIANS(LATITUDE))
* COS(RADIANS(mylat))
* COS(RADIANS(LONGITUDE) - RADIANS(mylng))
+ SIN(RADIANS(LATITUDE))
* SIN(RADIANS(mylat))
))
AS distance,
b.mydst
FROM Cities
JOIN (
SELECT :LAT AS mylat,
:LONG AS mylng,
:RADIUS_LIMIT AS mydst
FROM DUAL
)b ON (1 = 1)
WHERE LATITUDE >= mylat -(mydst/69)
AND LATITUDE <= mylat +(mydst/69)
AND LONGITUDE >= mylng -(mydst/(69 * COS(RADIANS(mylat))))
AND LONGITUDE <= mylng +(mydst/(69 * COS(RADIANS(mylat))))
)a
WHERE distance <= mydst
ORDER BY distance
如果您以公里为单位工作,请将mydst / 69更改为mydst / 111.045,并将3959更改为6371.4。 (1/69将里程转换为度数; 3959是行星半径的值。)
现在,您可能会想要将这个大型查询用作“魔术黑盒子”。不要这样做!这不是很难理解,如果你理解它,你将能够做得更好。这是正在发生的事情。
此子句是使查询快速运行的核心。它会在您的Cities表中搜索附近城市的指定点。
WHERE LATITUDE >= mylat -(mydst/69)
AND LATITUDE <= mylat +(mydst/69)
AND LONGITUDE >= mylng -(mydst/(69 * COS(RADIANS(mylat))))
AND LONGITUDE <= mylng +(mydst/(69 * COS(RADIANS(mylat))))
要使它工作,你肯定需要一个LATITUDE列的索引。 LONGITUDE列上的索引也会有所帮助。它进行近似搜索,寻找在您的点附近的地球表面上的准矩形块内的行。它选择了太多的城市,但不是太多。
此子句允许您从结果集中删除额外的城市:
WHERE distance <= mydst
此子句是用于计算每个城市与您的观点之间的大圆距离的半正公式。
(3959 * ACOS(COS(RADIANS(LATITUDE))
* COS(RADIANS(mylat))
* COS(RADIANS(LONGITUDE) - RADIANS(mylng))
+ SIN(RADIANS(LATITUDE))
* SIN(RADIANS(mylat))
此子句允许您输入您的点和半径限制,只需输入一次作为查询的绑定变量。它很有用,因为各种公式多次使用这些变量。
SELECT :LAT AS mylat,
:LONG AS mylng,
:RADIUS_LIMIT AS mydst
FROM DUAL
查询的其余部分只是组织事物,因此您可以选择并按距离排序。
以下是更完整的解释:http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
答案 1 :(得分:3)
如果您决定制作自己的公式,我认为此功能对于oracle用户非常有用,并且可以针对其他DB进行轻微修改。这是扁平地球公式,其计算成本远低于更准确的半正式公式。
CREATE OR REPLACE Function CIC3.F_FLATEARTHRAD
( latoriginrad IN number,
longoriginrad IN number,
latdestrad IN number,
longdestrad IN number)
RETURN number IS
a number;
b number;
c number;
u number;
v number;
HalfPi number:=1.5707963;
R number:=3956;
BEGIN
if latoriginrad is null or latdestrad is null or
longdestrad is null or longoriginrad is null then
return null;
end if;
a := HalfPi - latoriginrad;
b := HalfPi - latdestrad;
u := a * a + b * b;
v := - 2 * a * b * cos(longdestrad - longoriginrad);
c := sqrt(abs(u + v));
return R * c;
END;
然后您的查询变为
select * from GEO.Cities a
where F_FLATEARTHRAD(44.8*0.0174,46.3*0.0174,
latitude_radians,longitude_radians)<1000
需要0.0174因子,因为公式使用弧度而非度数。所以你需要存储弧度(可能带有触发器)。或者您需要修改公式以接受度数。出于查询目的,您可能正在查询数千条记录,甚至一次额外的乘法也会对响应时间产生影响。在我们的例子中,一些查询比较了两个表4k记录在1和200k之间的距离,因此我们有大约数十亿个函数调用。
以下是不需要担心时间的人的等值。
CREATE OR REPLACE Function CIC3.F_HAVERSINE
( latorigin IN number,
longorigin IN number,
latdest IN number,
longdest IN number)
RETURN number IS
v_longoriginrad number;
v_latoriginrad number;
v_longdestrad number;
v_latdestrad number;
v_difflat number;
v_difflong number;
a number;
c number;
d number;
z number;
x number;
e number;
f number;
g number;
h number;
i number;
j number;
k number;
l number;
m number;
n number;
o number;
p number;
q number;
y number;
BEGIN
z := .017453293;
x := 3956;
y := 57.295780;
v_longoriginrad:=longorigin*z;
v_latoriginrad:=latorigin*z;
v_longdestrad:=longdest*z;
v_latdestrad:=latdest*z;
v_difflong:=v_longdestrad-v_longoriginrad;
v_difflat:=v_latdestrad-v_latoriginrad;
j:=(v_difflat/2);
k:=sin(j);
l:=power(k,2);
m:=cos(v_latoriginrad);
n:=cos(v_latdestrad);
o:=v_difflong/2;
p:=sin(o);
q:=power(p,2);
a:=l+m*n*q;
c := 2 * asin(sqrt(a));
d := x * c;
return d;
END;
答案 2 :(得分:2)
如果您确实想使用SDO_WITHIN_DISTANCE
,则需要在Cities表中创建类型为SDO_GEOMETRY
的列,填充空间索引元数据并创建空间索引:
SDO_GEOMETRY
专栏:
CREATE TABLE MYTABLE(
...,
GEOLOC MDSYS.SDO_GEOMETRY,
...
);
空间索引元数据:
INSERT INTO USER_SDO_GEOM_METADATA (TABLE_NAME, COLUMN_NAME, DIMINFO, SRID)
VALUES ('MYTABLE' /*your table name*/, 'GEOLOC', /*your spatial column name*/
SDO_DIM_ARRAY(SDO_DIM_ELEMENT('X', -180, 180, 1),
SDO_DIM_ELEMENT('Y', -90, 90, 1)),
8307);
创建空间索引:
CREATE INDEX MY_SPATIAL_IDX ON MYTABLE (GEOLOC)
tablespace SomeTablespace; -- optional
现在用GEOLOC代替[我不知道]。
这是为了回答你的问题。其他人给你一个暗示,使用Oracle Spatial来完成这么简单的任务是有点过分了。在这种情况下,我倾向于同意,因为您可以在WHERE子句中进行简单的装箱,以切出不在矩形框中的城市,其中心为起点和搜索距离的大小;但有时你需要一个R树索引的智能。无论如何,他们的解决方案有两个主要问题:
一个。他们使用Great Circle方法计算点之间的距离。它太粗糙,你需要使用椭球方法来获得更准确的结果。谷歌搜索立即提供答案,如this。
湾如果你要在PL / SQL中编程椭球距离算法,你会发现它很慢。解决方案是将此逻辑移至Java或C ++并使其可从Oracle调用(有标准方法)。
答案 3 :(得分:0)
在接受答案的几年后,可以为查询添加一些增强功能:
版本11.1中的Oracle数据库添加了函数calc_distance(http://psoug.org/reference/functions.html),用于精确计算距离
关于使查询更快的子句,使用从距离到弧度的转换常量随纬度变化(http://www.longitudestore.com/how-big-is-one-gps-degree.html)并添加随搜索半径增加的误差。
在这里,我的变化使用地球半径的平均值,在我的测试中,对于欧洲纬度的大半径搜索似乎更准确:
SELECT id, city, LATITUDE, LONGITUDE, distance FROM
(
SELECT id,
city,
LATITUDE, LONGITUDE,
calc_distance(LATITUDE, LONGITUDE, mylat, mylng) AS distance,
b.mydst
FROM Cities
JOIN (
SELECT :LAT AS mylat,
:LONG AS mylng,
:RADIUS_LIMIT AS mydst,
3.1415926 AS pi, -- or use pi() function if available
6371.4 earthradius
FROM DUAL
)b ON (1 = 1)
WHERE LATITUDE >= mylat - ((mydst / earthradius) * (180 / pi))
AND LATITUDE <= mylat + ((mydst / earthradius) * (180 / pi))
AND LONGITUDE >= mylng - ((mydst / earthradius) * (180 / pi) / cos(mylat * pi/180))
AND LONGITUDE <= mylng + ((mydst / earthradius) * (180 / pi) / cos(mylat * pi/180))
)a
WHERE distance <= mydst
ORDER BY distance