社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

查找包含给定坐标的圆的mysql查询

eboye • 6 年前 • 1633 次点击  

我有一个正在工作的php脚本,它获取经度和纬度值,然后将它们输入到mysql查询中。我想把它单独做成MySQL。下面是我当前的php代码:

if ($distance != "Any" && $customer_zip != "") { //get the great circle distance

    //get the origin zip code info
    $zip_sql = "SELECT * FROM zip_code WHERE zip_code = '$customer_zip'";
    $result = mysql_query($zip_sql);
    $row = mysql_fetch_array($result);
    $origin_lat = $row['lat'];
    $origin_lon = $row['lon'];

    //get the range
    $lat_range = $distance/69.172;
    $lon_range = abs($distance/(cos($details[0]) * 69.172));
    $min_lat = number_format($origin_lat - $lat_range, "4", ".", "");
    $max_lat = number_format($origin_lat + $lat_range, "4", ".", "");
    $min_lon = number_format($origin_lon - $lon_range, "4", ".", "");
    $max_lon = number_format($origin_lon + $lon_range, "4", ".", "");
    $sql .= "lat BETWEEN '$min_lat' AND '$max_lat' AND lon BETWEEN '$min_lon' AND '$max_lon' AND ";
    }

有人知道怎么把它完全变成mysql吗?我浏览了一下因特网,但大多数有关它的文献都很混乱。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/44010
 
1633 次点击  
文章 [ 9 ]  |  最新文章 6 年前
IMRA
Reply   •   1 楼
IMRA    6 年前

在mysql中计算距离

 SELECT (6371 * acos(cos(radians(lat2)) * cos(radians(lat1) ) * cos(radians(long1) -radians(long2)) + sin(radians(lat2)) * sin(radians(lat1)))) AS distance

因此,距离值将被计算,任何人都可以根据需要申请。

David Harkness Sam Vloeberghs
Reply   •   2 楼
David Harkness Sam Vloeberghs    11 年前

我认为我的javascript实现是一个很好的参考:

/*
 * Check to see if the second coord is within the precision ( meters )
 * of the first coord and return accordingly
 */
function checkWithinBound(coord_one, coord_two, precision) {
    var distance = 3959000 * Math.acos( 
        Math.cos( degree_to_radian( coord_two.lat ) ) * 
        Math.cos( degree_to_radian( coord_one.lat ) ) * 
        Math.cos( 
            degree_to_radian( coord_one.lng ) - degree_to_radian( coord_two.lng ) 
        ) +
        Math.sin( degree_to_radian( coord_two.lat ) ) * 
        Math.sin( degree_to_radian( coord_one.lat ) ) 
    );
    return distance <= precision;
}

/**
 * Get radian from given degree
 */
function degree_to_radian(degree) {
    return degree * (Math.PI / 180);
}
Harish Lalwani
Reply   •   3 楼
Harish Lalwani    7 年前
 SELECT *, (  
    6371 * acos(cos(radians(search_lat)) * cos(radians(lat) ) *   
cos(radians(lng) - radians(search_lng)) + sin(radians(search_lat)) *         sin(radians(lat)))  
) AS distance  
FROM table  
WHERE lat != search_lat AND lng != search_lng AND distance < 25  
 ORDER BY distance  
FETCH 10 ONLY 

距离25公里

John Crenshaw
Reply   •   4 楼
John Crenshaw    12 年前

我无法评论上面的答案,但要小心@ Pavel Chuchuva的回答。如果两个坐标相同,则该公式不会返回结果。在这种情况下,distance为空,因此该行不会按原样随该公式返回。

我不是mysql专家,但这似乎对我有用:

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance 
FROM markers HAVING distance < 25 OR distance IS NULL ORDER BY distance LIMIT 0 , 20;
Bo Persson
Reply   •   5 楼
Bo Persson    14 年前

我编写了一个可以计算相同的过程, 但必须在相应的表中输入纬度和经度。

drop procedure if exists select_lattitude_longitude;

delimiter //

create procedure select_lattitude_longitude(In CityName1 varchar(20) , In CityName2 varchar(20))

begin

    declare origin_lat float(10,2);
    declare origin_long float(10,2);

    declare dest_lat float(10,2);
    declare dest_long float(10,2);

    if CityName1  Not In (select Name from City_lat_lon) OR CityName2  Not In (select Name from City_lat_lon) then 

        select 'The Name Not Exist or Not Valid Please Check the Names given by you' as Message;

    else

        select lattitude into  origin_lat from City_lat_lon where Name=CityName1;

        select longitude into  origin_long  from City_lat_lon where Name=CityName1;

        select lattitude into  dest_lat from City_lat_lon where Name=CityName2;

        select longitude into  dest_long  from City_lat_lon where Name=CityName2;

        select origin_lat as CityName1_lattitude,
               origin_long as CityName1_longitude,
               dest_lat as CityName2_lattitude,
               dest_long as CityName2_longitude;

        SELECT 3956 * 2 * ASIN(SQRT( POWER(SIN((origin_lat - dest_lat) * pi()/180 / 2), 2) + COS(origin_lat * pi()/180) * COS(dest_lat * pi()/180) * POWER(SIN((origin_long-dest_long) * pi()/180 / 2), 2) )) * 1.609344 as Distance_In_Kms ;

    end if;

end ;

//

delimiter ;
O. Jones
Reply   •   6 楼
O. Jones    12 年前

我必须把这件事做得很详细,所以我要和大家分享我的结果。这使用了 zip 表与 latitude longitude 桌子。它不依赖于谷歌地图,而是可以将其适配到包含LAT/LUN的任何表中。

SELECT zip, primary_city, 
       latitude, longitude, distance_in_mi
  FROM (
SELECT zip, primary_city, latitude, longitude,r,
       (3963.17 * ACOS(COS(RADIANS(latpoint)) 
                 * COS(RADIANS(latitude)) 
                 * COS(RADIANS(longpoint) - RADIANS(longitude)) 
                 + SIN(RADIANS(latpoint)) 
                 * SIN(RADIANS(latitude)))) AS distance_in_mi
 FROM zip
 JOIN (
        SELECT  42.81  AS latpoint,  -70.81 AS longpoint, 50.0 AS r
   ) AS p 
 WHERE latitude  
  BETWEEN latpoint  - (r / 69) 
      AND latpoint  + (r / 69)
   AND longitude 
  BETWEEN longpoint - (r / (69 * COS(RADIANS(latpoint))))
      AND longpoint + (r / (69 * COS(RADIANS(latpoint))))
  ) d
 WHERE distance_in_mi <= r
 ORDER BY distance_in_mi
 LIMIT 30

看看查询中间的这一行:

    SELECT  42.81  AS latpoint,  -70.81 AS longpoint, 50.0 AS r

这将搜索 拉链 距纬度/经度点42.81/-70.81 50.0英里内的表。当你将其构建到一个应用程序中时,你可以在其中放置自己的点和搜索半径。

如果你想以公里而不是英里为单位工作,那就换 69 111.045 变化 3963.17 6378.10 在查询中。

这是一份详细的书面报告。我希望它能帮助别人。 http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

silvio
Reply   •   7 楼
silvio    11 年前

如果将辅助字段添加到坐标表中,则可以提高查询的响应时间。

这样地:

CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)    

如果您使用tokudb,如果添加集群,您将获得更好的性能 任何一个谓词上的索引,例如:

alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);

您将需要基本的LAT和Lon的程度,以及正弦(LAT)在弧度,COS(LAT)* COS(Lon)在弧度和COS(LAT)*SIN(Lon)在弧度为每个点。 然后创建一个mysql函数,smth如下:

CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
                              `cos_cos1` FLOAT, `cos_sin1` FLOAT,
                              `sin_lat2` FLOAT,
                              `cos_cos2` FLOAT, `cos_sin2` FLOAT)
    RETURNS float
    LANGUAGE SQL
    DETERMINISTIC
    CONTAINS SQL
    SQL SECURITY INVOKER
   BEGIN
   RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
   END

这给了你距离。

不要忘记在LAT/Lon上添加索引,因此绑定装箱可以帮助搜索,而不是减慢它(在上面的CREATE表查询中已经添加了索引)。

INDEX `lat_lon_idx` (`lat`, `lon`)

给定一个只有LAT/Lon坐标的旧表,您可以设置一个脚本来更新它:(PHP使用MEEKRODB)

$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');

foreach ($users as $user)
{
  $lat_rad = deg2rad($user['lat']);
  $lon_rad = deg2rad($user['lon']);

  DB::replace('Coordinates', array(
    'object_id' => $user['id'],
    'object_type' => 0,
    'sin_lat' => sin($lat_rad),
    'cos_cos' => cos($lat_rad)*cos($lon_rad),
    'cos_sin' => cos($lat_rad)*sin($lon_rad),
    'lat' => $user['lat'],
    'lon' => $user['lon']
  ));
}

然后优化实际查询,以便仅在真正需要时进行距离计算,例如从内部和外部包围圆(well,oval)。 为此,您需要预先计算查询本身的几个指标:

// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));

考虑到这些准备工作,查询将如下所示(php):

$neighbors = DB::query("SELECT id, type, lat, lon,
       geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
       FROM Coordinates WHERE
       lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
       HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
  // center radian values: sin_lat, cos_cos, cos_sin
       sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
  // min_lat, max_lat, min_lon, max_lon for the outside box
       $lat-$dist_deg_lat,$lat+$dist_deg_lat,
       $lon-$dist_deg_lon,$lon+$dist_deg_lon,
  // min_lat, max_lat, min_lon, max_lon for the inside box
       $lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
       $lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
  // distance in radians
       $distance_rad);

解释上面的查询可能会说它没有使用索引,除非有足够的结果触发这样的查询。当坐标表中有足够的数据时,将使用索引。 您可以添加 力指数(横向) 要选择,使其使用与表大小无关的索引,这样您就可以通过解释它是否正确工作来验证。

通过上面的代码示例,您应该能够以最小的错误实现按距离进行的对象搜索。

Jacco
Reply   •   8 楼
Jacco    8 年前

$greatCircleDistance = acos( cos($latitude0) * cos($latitude1) * cos($longitude0 - $longitude1) + sin($latitude0) * sin($latitude1));

以弧度表示经纬度。

所以

SELECT 
  acos( 
      cos(radians( $latitude0 ))
    * cos(radians( $latitude1 ))
    * cos(radians( $longitude0 ) - radians( $longitude1 ))
    + sin(radians( $latitude0 )) 
    * sin(radians( $latitude1 ))
  ) AS greatCircleDistance 
 FROM yourTable;

是您的SQL查询

要得到以公里或英里为单位的结果,请将结果乘以地球的平均半径( 3959 英里, 6371 公里或 3440 海里)

您在示例中计算的是一个边界框。 如果你把你的坐标数据放在 spatial enabled MySQL column ,你可以使用 MySQL's build in functionality 查询数据。

SELECT 
  id
FROM spatialEnabledTable
WHERE 
  MBRWithin(ogc_point, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
Yahel
Reply   •   9 楼
Yahel    11 年前

Google Code FAQ - Creating a Store Locator with PHP, MySQL & Google Maps :

下面的sql语句将找到距离37,-122坐标25英里半径范围内最近的20个位置。它根据该行的纬度/经度和目标纬度/经度计算距离,然后只要求距离值小于25的行,按距离对整个查询进行排序,并将其限制为20个结果。要按公里而不是英里搜索,请将3959替换为6371。

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) 
* cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin(radians(lat)) ) ) AS distance 
FROM markers 
HAVING distance < 25 
ORDER BY distance 
LIMIT 0 , 20;