如果您在坐标表中添加辅助字段,可以提高查询的响应时间。
示例:
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);
对于每个点,您需要基本的经度和纬度以及弧度中的sin(lat),cos(lat)* cos(lon)和cos(lat)* sin(lon)。
然后创建一个mysql函数,类似于以下内容:
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 TABLE查询中添加)。
INDEX `lat_lon_idx` (`lat`, `lon`)
如果有一个只有纬度/经度坐标的旧表格,您可以设置一个脚本来更新它,如下所示:(使用MeekroDB的PHP)
$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']
));
}
然后,您将优化实际查询,仅在确实需要时执行距离计算,例如通过从内部和外部限定圆形(或椭圆形)进行边界处理。 为此,您需要预先计算查询本身的几个度量。
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371;
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06;
$dist_deg_lat = rad2deg($distance_rad_plus);
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2));
$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",
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
$distance_rad);
以上查询中的EXPLAIN可能会显示,除非有足够的结果触发索引,否则不会使用索引。只有在坐标表中有足够的数据时才会使用索引。
您可以在SELECT语句中添加FORCE INDEX(lat_lon_idx),以使其在不考虑表大小的情况下使用索引,因此您可以使用EXPLAIN验证其是否正确使用。
通过上述代码示例,您应该能够实现一个可扩展的、具有最小误差的距离对象搜索。