【问题标题】:Oracle spatial search within distance距离内的 Oracle 空间搜索
【发布时间】:2012-02-22 02:57:15
【问题描述】:

我有下表城市:

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但是时间太长了......

【问题讨论】:

    标签: oracle search geo oracle-spatial


    【解决方案1】:

    你有一个关于 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
    

    此子句是计算每个城市与您的点之间的大圆距离的haversine 公式。

               (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/

    【讨论】:

    • 我继续使用缓存做这样的事情......我希望通过 Spatial 获得更好的性能......至少做一个比较,看看我应该走哪条路:).. .谢谢你的时间
    • 空间没有任何魔力。无论如何,对浮点列(纬度)的索引搜索将具有相同的复杂性。
    • 它有一些魔力——R-tree 索引,与普通(B-tree)索引有很大不同,旨在明确解决二维上的接近问题。
    • 在我的编辑中添加了适当的错误修复。感谢您的错误报告。
    • 在原始答案几年后,可以对查询添加一些增强功能:Oracle 在 11.1 版本中添加了函数 calc_distance link,用于准确和更有效地计算距离。使查询快速的子句具有从距离到随纬度 link 变化的弧度的转换常数。我的更改在下一条评论中,使用地球半径的平均值(对于欧洲纬度更准确)
    【解决方案2】:

    如果您决定制作自己的公式,我认为此功能对 oracle 用户可能非常有用,并且可以对其他数据库稍作修改。这是平面地球公式,其计算成本比更精确的半正弦公式要少得多。

    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 记录和 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; 
    

    【讨论】:

      【解决方案3】:

      如果你真的要使用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 替换为您所说的 [我不知道]。

      这是为了回答你的问题。其他人给了你一个提示,使用 Oracle 空间来完成这样一个简单的任务是多余的。在这种情况下,我倾向于同意,因为您可以在 WHERE 子句中进行简单的装箱,以切出不在矩形框中的城市,其起点为中心和搜索距离的大小;但是有时您需要 R-tree 索引的智能。无论如何,他们的解决方案有两个主要问题:

      一个。他们使用大圆方法来计算点之间的距离。太粗糙了,需要用椭球的方法才能得到更准确的结果。谷歌搜索立即提供答案,如this

      b.如果你用 PL/SQL 编写椭球距离算法,你会发现它非常慢。解决方案是将此逻辑移至 Java 或 C++ 并使其可从 Oracle 调用(有这样做的标准方法)。

      【讨论】:

        【解决方案4】:

        在接受答案几年后,可以为查询添加一些增强功能: Oracle 数据库在 11.1 版本中添加了函数 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
        

        【讨论】:

          猜你喜欢
          • 2017-01-31
          • 1970-01-01
          • 2012-10-31
          • 2012-06-20
          • 2016-05-17
          • 2010-12-28
          • 1970-01-01
          • 2011-01-18
          • 2012-09-08
          相关资源
          最近更新 更多