本文目录导读:

在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) ? '在多边形区域内' : '不在多边形区域内';
性能优化建议
- 预处理:对于静态区域,提前计算并缓存边界框
- 数据库索引:使用空间索引(R-tree)
- 分批处理:批量判断时使用
in_array或 SQLIN - 缓存:使用 Redis 缓存热点区域的判断结果
- 精度控制:根据业务需求调整计算精度
注意事项
- 坐标系:确保所有坐标使用相同的坐标系(WGS-84/GCJ-02/BD-09)
- 边界情况:点在边界上通常视为在区域内
- 性能:PHP 适合少量实时判断,大量数据建议用数据库处理
- 精度:经纬度计算存在精度误差,必要时加容差
选择哪种方法取决于你的具体需求:简单圆形区域用距离法,复杂不规则区域用射线法,大量数据推荐数据库空间函数。