PHP项目地理围栏如何判断坐标是否区域内

wen PHP项目 28

本文目录导读:

PHP项目地理围栏如何判断坐标是否区域内

  1. 射线法(Ray Casting Algorithm)
  2. 圆形区域判断(距离法)
  3. 矩形区域判断(经纬度范围)
  4. 使用数据库功能(推荐)
  5. 使用第三方库
  6. 完整的围栏判断类
  7. 性能优化建议
  8. 注意事项

在PHP项目中判断地理坐标是否在某个区域内,通常有以下几种主流方法,按复杂度从低到高排列:

射线法(Ray Casting Algorithm)

最通用的方法,适用于任何多边形区域(凸多边形或凹多边形)。

function pointInPolygon($point, $polygon) {
    $x = $point['lng'];
    $y = $point['lat'];
    $vertices = $polygon;
    $n = count($vertices);
    $inside = false;
    for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
        $xi = $vertices[$i]['lng'];
        $yi = $vertices[$i]['lat'];
        $xj = $vertices[$j]['lng'];
        $yj = $vertices[$j]['lat'];
        // 检查射线与边的交点
        if ((($yi > $y) != ($yj > $y)) && 
            ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi)) {
            $inside = !$inside;
        }
    }
    return $inside;
}
// 使用示例
$point = ['lng' => 113.264385, 'lat' => 23.129163]; // 广州塔
$area = [
    ['lng' => 113.2, 'lat' => 23.1],
    ['lng' => 113.3, 'lat' => 23.1],
    ['lng' => 113.3, 'lat' => 23.2],
    ['lng' => 113.2, 'lat' => 23.2]
];
var_dump(pointInPolygon($point, $area));

圆形区域判断(距离法)

用于以某点为中心,半径为范围的圆形区域。

function isInCircle($point, $center, $radius) {
    // 使用Haversine公式计算两点距离
    $lat1 = deg2rad($point['lat']);
    $lon1 = deg2rad($point['lng']);
    $lat2 = deg2rad($center['lat']);
    $lon2 = deg2rad($center['lng']);
    $dlat = $lat2 - $lat1;
    $dlon = $lon2 - $lon1;
    $a = sin($dlat/2) * sin($dlat/2) + 
         cos($lat1) * cos($lat2) * 
         sin($dlon/2) * sin($dlon/2);
    $c = 2 * atan2(sqrt($a), sqrt(1-$a));
    $distance = 6371 * $c; // 地球半径6371km
    return $distance <= $radius;
}
// 使用示例
$point = ['lat' => 23.129163, 'lng' => 113.264385];
$center = ['lat' => 23.13, 'lng' => 113.27];
$radius = 1; // 1公里
var_dump(isInCircle($point, $center, $radius));

矩形区域判断(经纬度范围)

最简单的范围判断,适合规则的矩形区域。

function isInRectangle($point, $bounds) {
    return $point['lat'] >= $bounds['minLat'] && 
           $point['lat'] <= $bounds['maxLat'] && 
           $point['lng'] >= $bounds['minLng'] && 
           $point['lng'] <= $bounds['maxLng'];
}
// 使用示例
$point = ['lat' => 23.129163, 'lng' => 113.264385];
$bounds = [
    'minLat' => 23.1,
    'maxLat' => 23.2,
    'minLng' => 113.2,
    'maxLng' => 113.3
];
var_dump(isInRectangle($point, $bounds));

使用数据库功能(推荐)

MySQL/MariaDB 使用空间函数:

-- 创建表时使用空间类型
CREATE TABLE geo_fences (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    area POLYGON NOT NULL,
    SPATIAL INDEX(area)
);
-- 插入数据(广州塔区域)
INSERT INTO geo_fences (id, name, area) VALUES 
(1, '广州塔区域', ST_GeomFromText('POLYGON((113.2 23.1, 113.3 23.1, 113.3 23.2, 113.2 23.2, 113.2 23.1))'));
-- 判断点是否在区域内
SELECT * FROM geo_fences 
WHERE ST_Contains(area, POINT(113.264385, 23.129163));

PostgreSQL + PostGIS:

-- 创建表
CREATE TABLE geo_fences (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    geom GEOMETRY(POLYGON, 4326)
);
-- 创建索引
CREATE INDEX geo_fences_geom_idx ON geo_fences USING GIST (geom);
-- 查询
SELECT * FROM geo_fences 
WHERE ST_Contains(geom, ST_SetSRID(ST_MakePoint(113.264385, 23.129163), 4326));

使用第三方库

geoPHP 库:

composer require phayes/geophp
require_once 'vendor/autoload.php';
use geoPHP\geoPHP;
$point = geoPHP::load('POINT(113.264385 23.129163)', 'wkt');
$polygon = geoPHP::load('POLYGON((113.2 23.1, 113.3 23.1, 113.3 23.2, 113.2 23.2, 113.2 23.1))', 'wkt');
// 判断
if ($polygon->contains($point)) {
    echo "点在区域内";
}

完整的围栏判断类

class GeoFence {
    /**
     * 判断点是否在区域内
     * @param array $point ['lat' => float, 'lng' => float]
     * @param array $fence 围栏数据
     * @return bool
     */
    public static function isInside(array $point, array $fence): bool {
        switch ($fence['type']) {
            case 'circle':
                return self::isInCircle($point, $fence['center'], $fence['radius']);
            case 'rectangle':
                return self::isInRectangle($point, $fence['bounds']);
            case 'polygon':
                return self::isInPolygon($point, $fence['vertices']);
            default:
                throw new \InvalidArgumentException("Unknown fence type: {$fence['type']}");
        }
    }
    /**
     * 批量判断(优化大量判断)
     * @param array $points
     * @param array $fence
     * @return array
     */
    public static function batchCheck(array $points, array $fence): array {
        $results = [];
        foreach ($points as $key => $point) {
            $results[$key] = self::isInside($point, $fence);
        }
        return $results;
    }
    private static function isInCircle($point, $center, $radius) {
        // 使用Haversine公式
        $lat1 = deg2rad($point['lat']);
        $lon1 = deg2rad($point['lng']);
        $lat2 = deg2rad($center['lat']);
        $lon2 = deg2rad($center['lng']);
        $dlat = $lat2 - $lat1;
        $dlon = $lon2 - $lon1;
        $a = sin($dlat/2)**2 + cos($lat1) * cos($lat2) * sin($dlon/2)**2;
        $c = 2 * asin(sqrt($a));
        $distance = 6371000 * $c; // 米
        return $distance <= $radius;
    }
    private static function isInRectangle($point, $bounds) {
        return $point['lat'] >= $bounds['minLat'] && 
               $point['lat'] <= $bounds['maxLat'] && 
               $point['lng'] >= $bounds['minLng'] && 
               $point['lng'] <= $bounds['maxLng'];
    }
    private static function isInPolygon($point, $vertices) {
        $x = $point['lng'];
        $y = $point['lat'];
        $n = count($vertices);
        $inside = false;
        for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
            $xi = $vertices[$i]['lng'];
            $yi = $vertices[$i]['lat'];
            $xj = $vertices[$j]['lng'];
            $yj = $vertices[$j]['lat'];
            if ((($yi > $y) != ($yj > $y)) && 
                ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi)) {
                $inside = !$inside;
            }
        }
        return $inside;
    }
}
// 使用示例
$point = ['lat' => 23.129163, 'lng' => 113.264385];
// 圆形围栏
$circleFence = [
    'type' => 'circle',
    'center' => ['lat' => 23.13, 'lng' => 113.27],
    'radius' => 1000 // 米
];
// 多边形围栏
$polygonFence = [
    'type' => 'polygon',
    'vertices' => [
        ['lng' => 113.2, 'lat' => 23.1],
        ['lng' => 113.3, 'lat' => 23.1],
        ['lng' => 113.3, 'lat' => 23.2],
        ['lng' => 113.2, 'lat' => 23.2]
    ]
];
echo GeoFence::isInside($point, $circleFence) ? '在圆形区域内' : '不在圆形区域内';
echo "\n";
echo GeoFence::isInside($point, $polygonFence) ? '在多边形区域内' : '不在多边形区域内';

性能优化建议

  1. 预处理:对于静态区域,提前计算并缓存边界框
  2. 数据库索引:使用空间索引(R-tree)
  3. 分批处理:批量判断时使用 in_array 或 SQL IN
  4. 缓存:使用 Redis 缓存热点区域的判断结果
  5. 精度控制:根据业务需求调整计算精度

注意事项

  • 坐标系:确保所有坐标使用相同的坐标系(WGS-84/GCJ-02/BD-09)
  • 边界情况:点在边界上通常视为在区域内
  • 性能:PHP 适合少量实时判断,大量数据建议用数据库处理
  • 精度:经纬度计算存在精度误差,必要时加容差

选择哪种方法取决于你的具体需求:简单圆形区域用距离法,复杂不规则区域用射线法,大量数据推荐数据库空间函数。

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