PHP项目POI检索如何周边地点后端检索查询

wen PHP项目 23

本文目录导读:

PHP项目POI检索如何周边地点后端检索查询

  1. 使用地图服务商API(推荐方案)
  2. 自建MySQL空间数据库查询
  3. 使用Elasticsearch地理位置查询
  4. 性能优化建议
  5. 完整的API接口示例
  6. 选择建议

针对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方案。

抱歉,评论功能暂时关闭!