基于经纬度查询附近商家的MySQL实现
首先创建一个用于存储地理位置信息的表:
CREATE TABLE location (
id INT NOT NULL AUTO_INCREMENT,
longitude FLOAT NOT NULL COMMENT '经度',
latitude FLOAT NOT NULL COMMENT '纬度',
place_name VARCHAR(100) DEFAULT NULL COMMENT '地点名称',
PRIMARY KEY (id)
);
插入一些测试数据:
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.302416, 33.958887, "实验小学");
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.303997, 33.95188, "宿迁市人民医院");
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.302991, 33.935828, "宿迁学院");
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.28215, 33.959307, "金陵名府");
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.290081, 33.925404, "润园");
INSERT INTO `location`(`longitude`, `latitude`, `place_name`) VALUES (118.354751, 33.959007, "国际馆");
以下是基于公里计算距离的SQL查询语句:
SELECT
id,
place_name,
(
6371 * ACOS(
COS(RADIANS(33.958887))
* COS(RADIANS(latitude))
* COS(RADIANS(longitude) - RADIANS(118.302416))
+ SIN(RADIANS(33.958887))
* SIN(RADIANS(latitude))
)
) AS distance
FROM location
HAVING distance < 20
ORDER BY distance ASC
LIMIT 0, 20;
其中,33.958887 是纬度,118.302416 是经度。
下面是完整的PHP实现代码,用于分页获取附近的商家信息:
public function getNearbyBusiness() {
// 验证经纬度是否合法
if (!$this->validator->check('longitude', ['required', 'regex' => '-?((0|1?[0-7]?[0-9]?)(([.][0-9]{1,4})?)|180(([.][0]{1,4})?))'])) {
$this->response->setError(10001, '缺少或经纬度格式不正确');
$this->response->send();
}
if (!$this->validator->check('latitude', ['required', 'regex' => '-?((0|[1-8]?[0-9]?)(([.][0-9]{1,4})?)|90(([.][0]{1,4})?))'])) {
$this->response->setError(10001, '缺少或经纬度格式不正确');
$this->response->send();
}
// 设置默认分页参数
$page = isset($_POST['page']) && $_POST['page'] >= 1 ? $_POST['page'] : 1;
// 查询符合条件的商家总数
$queryCount = "
SELECT COUNT(*) AS total FROM business_data
WHERE status = 1 AND (
6371 * ACOS(
COS(RADIANS(" . $_POST['latitude'] . "))
* COS(RADIANS(latitude))
* COS(RADIANS(longitude) - RADIANS(" . $_POST['longitude'] . "))
+ SIN(RADIANS(" . $_POST['latitude'] . "))
* SIN(RADIANS(latitude))
)
) < 10
";
$totalRecords = M()->query($queryCount)[0]['total'];
$totalPages = ceil($totalRecords / C('PAGE_LIMIT'));
$totalPages = $totalPages ? $totalPages : 1;
if ($page > $totalPages) {
$this->response->setError(10002, '页数超出范围');
$this->response->send();
}
// 计算分页偏移量
$offset = ($page - 1) * C('PAGE_LIMIT');
// 查询具体商家列表
$queryList = "
SELECT id, name, info, (
6371 * ACOS(
COS(RADIANS(" . $_POST['latitude'] . "))
* COS(RADIANS(latitude))
* COS(RADIANS(longitude) - RADIANS(" . $_POST['longitude'] . "))
+ SIN(RADIANS(" . $_POST['latitude'] . "))
* SIN(RADIANS(latitude))
)
) AS distance FROM business_data
WHERE status = 1 AND distance < 10
ORDER BY distance ASC
LIMIT $offset, " . C('PAGE_LIMIT') . "
";
$businessList = M()->query($queryList);
if ($businessList) {
// 处理商家主图
$businessImageModel = M('business_image');
foreach ($businessList as &$item) {
$mainImage = $businessImageModel->where([
'business_id' => $item['id'],
'type' => 1,
'is_main' => 1
])->find();
$item['image'] = $mainImage['image'];
if (strpos($item['image'], 'cdn') === false) {
$item['image'] = C('CDN_URL') . $item['image'];
}
}
$result = [
'list' => $businessList,
'current_page' => $page,
'total_pages' => $totalPages
];
$this->response->setError(0, '查询成功');
$this->response->setData($result);
$this->response->send();
} else {
$this->response->setError(10003, '暂无数据');
$this->response->send();
}
}
返回的JSON结果示例:
{
"errno": 0,
"errdesc": "查询成功",
"timestamp": 1524749336,
"data": {
"list": [
{
"id": "3",
"name": "京东",
"info": "京东",
"distance": "4.665364145881758",
"image": "https://cdn.caomall.net/1524732806744365022.png"
}
],
"current_page": "1",
"total_pages": 1
}
}
