距离内的Oracle空间搜索

时间:2012-01-30 09:31:20

标签: oracle search geo oracle-spatial

我有以下表格城市:

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,但这需要太长时间......

4 个答案:

答案 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的列,填充空间索引元数据并创建空间索引:

  1. SDO_GEOMETRY专栏:

    CREATE TABLE MYTABLE(
    ...,
    GEOLOC MDSYS.SDO_GEOMETRY,
    ...
    );
    
  2. 空间索引元数据:

    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);
    
  3. 创建空间索引:

    CREATE INDEX MY_SPATIAL_IDX ON MYTABLE (GEOLOC)
    tablespace SomeTablespace; -- optional
    
  4. 现在用GEOLOC代替[我不知道]。

  5. 这是为了回答你的问题。其他人给你一个暗示,使用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