本文目录导读:

在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 适合少量实时判断,大量数据建议用数据库处理
- 精度:经纬度计算存在精度误差,必要时加容差
选择哪种方法取决于你的具体需求:简单圆形区域用距离法,复杂不规则区域用射线法,大量数据推荐数据库空间函数。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播