4

我有下表城市:

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';

任何帮助,将不胜感激。

PS:如果有可能有距离和排序

PPS:由于性能问题,我想以这种方式执行此操作,我以这种方式执行此操作http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL但耗时太长...

4

4 回答 4

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

该子句是计算每个城市与您的点之间的大圆距离的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/

于 2012-01-30T13:52:27.763 回答
4

如果您决定制作自己的公式,我认为此功能对 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; 
于 2012-05-18T22:52:43.680 回答
2

如果你真的想使用SDO_WITHIN_DISTANCE,你需要SDO_GEOMETRY在你的 Cities 表中创建一个类型的列,填充空间索引元数据并创建空间索引:

  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 索引的智能。无论如何,他们的解决方案有两个主要问题:

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

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

于 2012-10-08T09:35:06.850 回答
1

在接受答案几年后,可以为查询添加一些增强功能: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-02-24T13:50:40.247 回答