本文目录导读:

针对PHP项目中POI(兴趣点)周边地点检索的后端查询,我为你提供几种常见的技术方案和实现思路:
使用地图服务商API(推荐方案)
高德地图API示例
<?php
class POISearchService {
private $apiKey = '你的高德地图API Key';
/**
* 周边POI检索
* @param float $lat 纬度
* @param float $lng 经度
* @param int $radius 半径(米)
* @param string $keywords 关键词
* @param int $page 页码
* @return array
*/
public function searchAround($lat, $lng, $radius = 1000, $keywords = '', $page = 1) {
$url = 'https://restapi.amap.com/v3/place/around';
$params = [
'key' => $this->apiKey,
'location' => $lng . ',' . $lat,
'radius' => $radius,
'keywords' => $keywords,
'offset' => 20, // 每页记录数
'page' => $page,
'extensions' => 'all'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . '?' . http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
/**
* 计算两点间距离(米)
*/
public function calculateDistance($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000; // 地球半径(米)
$latFrom = deg2rad($lat1);
$latTo = deg2rad($lat2);
$lngFrom = deg2rad($lng1);
$lngTo = deg2rad($lng2);
$latDelta = $latTo - $latFrom;
$lngDelta = $lngTo - $lngFrom;
$a = sin($latDelta/2) * sin($latDelta/2) +
cos($latFrom) * cos($latTo) *
sin($lngDelta/2) * sin($lngDelta/2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
return $earthRadius * $c;
}
}
// 使用示例
$poiService = new POISearchService();
$result = $poiService->searchAround(
39.9042, // 纬度
116.4074, // 经度
1000, // 半径1公里
'餐厅' // 关键词
);
print_r($result);
?>
百度地图API示例
<?php
class BaiduPOISearch {
private $ak = '你的百度地图AK';
public function searchAround($lat, $lng, $radius, $query) {
$url = 'https://api.map.baidu.com/place/v2/search';
$params = [
'ak' => $this->ak,
'output' => 'json',
'query' => $query,
'location' => $lat . ',' . $lng,
'radius' => $radius,
'page_size' => 20,
'page_num' => 0,
'scope' => 2
];
// 使用curl发起请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . '?' . http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
?>
自建MySQL空间数据库查询
数据库表结构
CREATE TABLE poi_locations (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
address VARCHAR(500),
latitude DECIMAL(10, 7) NOT NULL,
longitude DECIMAL(10, 7) NOT NULL,
category VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
SPATIAL INDEX idx_location (latitude, longitude)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP查询实现
<?php
class SelfPOISearch {
private $db;
public function __construct($host, $dbname, $user, $pass) {
$this->db = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$user,
$pass
);
}
/**
* 使用Haversine公式计算距离
*/
public function searchByHaversine($lat, $lng, $radius, $limit = 20) {
$sql = "
SELECT
*,
(
6371 * acos(
cos(radians(:lat)) *
cos(radians(latitude)) *
cos(radians(longitude) - radians(:lng)) +
sin(radians(:lat2)) *
sin(radians(latitude))
)
) AS distance
FROM poi_locations
HAVING distance <= :radius
ORDER BY distance
LIMIT :limit
";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':lat', $lat);
$stmt->bindParam(':lng', $lng);
$stmt->bindParam(':lat2', $lat);
$stmt->bindParam(':radius', $radius);
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 使用GeoHash优化查询
*/
public function searchByGeoHash($lat, $lng, $radius) {
// 计算经纬度范围
$latRange = $radius / 111000; // 1度纬度 ≈ 111km
$lngRange = $radius / (111000 * cos(deg2rad($lat)));
$minLat = $lat - $latRange;
$maxLat = $lat + $latRange;
$minLng = $lng - $lngRange;
$maxLng = $lng + $lngRange;
$sql = "
SELECT *,
get_distance(:lat, :lng, latitude, longitude) AS distance
FROM poi_locations
WHERE latitude BETWEEN :min_lat AND :max_lat
AND longitude BETWEEN :min_lng AND :max_lng
HAVING distance <= :radius
ORDER BY distance
";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':lat', $lat);
$stmt->bindParam(':lng', $lng);
$stmt->bindParam(':min_lat', $minLat);
$stmt->bindParam(':max_lat', $maxLat);
$stmt->bindParam(':min_lng', $minLng);
$stmt->bindParam(':max_lng', $maxLng);
$stmt->bindParam(':radius', $radius);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
// 创建距离计算函数(MySQL)
$createFunction = "
CREATE FUNCTION get_distance(
lat1 DECIMAL(10,7),
lng1 DECIMAL(10,7),
lat2 DECIMAL(10,7),
lng2 DECIMAL(10,7)
) RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
DECLARE R DECIMAL(10,5) DEFAULT 6371;
DECLARE dLat DECIMAL(20,10);
DECLARE dLng DECIMAL(20,10);
DECLARE a DECIMAL(20,10);
DECLARE c DECIMAL(20,10);
SET dLat = RADIANS(lat2 - lat1);
SET dLng = RADIANS(lng2 - lng1);
SET a = POW(SIN(dLat/2), 2) +
COS(RADIANS(lat1)) * COS(RADIANS(lat2)) *
POW(SIN(dLng/2), 2);
SET c = 2 * ATAN2(SQRT(a), SQRT(1-a));
RETURN R * c * 1000; -- 返回米
END
";
?>
使用Elasticsearch地理位置查询
Elasticsearch索引设置
<?php
class ElasticPOISearch {
private $client;
public function __construct() {
// 使用Elasticsearch PHP客户端
$hosts = ['http://localhost:9200'];
$this->client = Elasticsearch\ClientBuilder::create()
->setHosts($hosts)
->build();
}
/**
* 创建索引映射
*/
public function createIndex() {
$params = [
'index' => 'poi_locations',
'body' => [
'mappings' => [
'properties' => [
'name' => ['type' => 'text'],
'location' => ['type' => 'geo_point'],
'category' => ['type' => 'keyword'],
'rating' => ['type' => 'float']
]
]
]
];
return $this->client->indices()->create($params);
}
/**
* 周边POI搜索
*/
public function searchAround($lat, $lng, $radius, $query = '') {
$params = [
'index' => 'poi_locations',
'body' => [
'query' => [
'bool' => [
'must' => [
'geo_distance' => [
'distance' => $radius . 'm',
'location' => [
'lat' => $lat,
'lon' => $lng
]
]
],
'should' => [
'match' => [
'name' => $query
]
]
]
],
'sort' => [
'_geo_distance' => [
'location' => [
'lat' => $lat,
'lon' => $lng
],
'order' => 'asc',
'unit' => 'm'
]
]
]
];
$response = $this->client->search($params);
return $response['hits']['hits'];
}
}
?>
性能优化建议
缓存策略
<?php
class POICache {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* 带缓存的POI查询
*/
public function getCachedPOI($lat, $lng, $radius) {
// 生成缓存key
$cacheKey = "poi:{$lat}:{$lng}:{$radius}";
// 检查缓存
if ($this->redis->exists($cacheKey)) {
return json_decode($this->redis->get($cacheKey), true);
}
// 查询数据库
$poiService = new SelfPOISearch('localhost', 'poi_db', 'user', 'pass');
$result = $poiService->searchByHaversine($lat, $lng, $radius);
// 设置缓存(5分钟过期)
$this->redis->setex($cacheKey, 300, json_encode($result));
return $result;
}
}
?>
数据库优化
-- 添加复合索引
ALTER TABLE poi_locations ADD INDEX idx_category_location (category, latitude, longitude);
-- 使用分区表
CREATE TABLE poi_locations_partitioned (
-- ... 字段定义
) ENGINE=InnoDB
PARTITION BY RANGE (id) (
PARTITION p0 VALUES LESS THAN (100000),
PARTITION p1 VALUES LESS THAN (200000),
PARTITION p2 VALUES LESS THAN (300000),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
完整的API接口示例
<?php
// api/poi_search.php
header('Content-Type: application/json; charset=utf-8');
// 验证请求参数
$lat = isset($_GET['lat']) ? floatval($_GET['lat']) : 0;
$lng = isset($_GET['lng']) ? floatval($_GET['lng']) : 0;
$radius = isset($_GET['radius']) ? intval($_GET['radius']) : 1000;
$type = isset($_GET['type']) ? $_GET['type'] : 'food';
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// 参数验证
if ($lat == 0 || $lng == 0) {
echo json_encode(['code' => 400, 'message' => '缺少经纬度参数']);
exit;
}
try {
// 选择搜索方式(配置文件定义)
$searchType = 'baidu'; // 或 'self', 'elastic'
switch($searchType) {
case 'baidu':
$search = new BaiduPOISearch();
$result = $search->searchAround($lat, $lng, $radius, $type);
break;
case 'self':
$search = new SelfPOISearch('localhost', 'poi_db', 'user', 'pass');
$result = $search->searchByHaversine($lat, $lng, $radius);
break;
case 'elastic':
$search = new ElasticPOISearch();
$result = $search->searchAround($lat, $lng, $radius);
break;
}
// 格式化输出
$response = [
'code' => 200,
'data' => [
'total' => count($result),
'pois' => formatPOIResult($result),
'page' => $page
],
'request' => [
'lat' => $lat,
'lng' => $lng,
'radius' => $radius,
'type' => $type
]
];
echo json_encode($response);
} catch (Exception $e) {
echo json_encode([
'code' => 500,
'message' => '查询失败: ' . $e->getMessage()
]);
}
function formatPOIResult($data) {
$formatted = [];
foreach ($data as $poi) {
$formatted[] = [
'id' => $poi['id'],
'name' => $poi['name'],
'address' => $poi['address'] ?? '',
'lat' => $poi['latitude'],
'lng' => $poi['longitude'],
'distance' => round($poi['distance'] ?? 0, 2),
'category' => $poi['category'] ?? ''
];
}
return $formatted;
}
?>
选择建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 地图服务商API | 快速开发,数据量小 | 数据准确,部署简单 | 有调用次数限制 |
| 自建数据库 | 数据量大,定制化需求 | 完全控制,无费用 | 维护成本高 |
| Elasticsearch | 大数据量,高并发 | 查询快,扩展性好 | 学习成本高 |
根据你的项目规模、数据量和服务要求选择合适方案,对于小型项目,推荐使用地图服务商API;对于大型商业项目,推荐Elasticsearch方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播