thinkphp 8.0 通过 mysql 空间函数实现地理围栏,核心是用 st_distance_sphere 判断坐标是否在圆形/多边形区域内;需建表含 point 类型 center 字段与 radius 字段,并按 lng,lat 顺序传参调用空间查询。

ThinkPHP 8.0 本身不内置地理围栏功能,但可通过组合数据库空间索引(如 MySQL 的 POINT 类型 + ST_Distance_Sphere)、PHP 地理计算函数和业务逻辑快速实现轻量级 Geo-fencing。核心在于“判断某坐标是否在指定圆形/多边形区域内”,而非依赖第三方服务。
1. 数据库准备:存储围栏区域
使用 MySQL 5.7+ 支持的空间数据类型,推荐用 POINT 存圆心 + 半径字段(单位:米),或用 POLYGON 存多边形顶点:
CREATE TABLE `geo_fences` ( `id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT, `name` varchar(100) NOT NULL, `center` point NOT NULL SRID 4326, -- WGS84 坐标系 `radius` int NOT NULL COMMENT '单位:米', `status` tinyint DEFAULT 1, SPATIAL INDEX(`center`) );
插入示例(北京国贸中心,半径 500 米):
INSERT INTO geo_fences (name, center, radius)
VALUES ('国贸围栏', ST_PointFromText('POINT(116.4584 39.9042)', 4326), 500);
2. 判断坐标是否在圆形围栏内
利用 MySQL 的 ST_Distance_Sphere 计算球面距离(精度高、性能好),避免手动写 Haversine 公式:
- 在 ThinkPHP 模型中定义查询方法,例如
FenceModel::isInCircle($lng, $lat) - SQL 关键逻辑:
ST_Distance_Sphere(center, POINT(?, ?)) - 注意:经纬度传入顺序为
经度, 纬度(即lng, lat),与常见直觉相反
示例代码(模型方法):
public function isInCircle(float $lng, float $lat, int $fenceId = null)
{
$query = $this->where('status', 1);
if ($fenceId) {
$query->where('id', $fenceId);
}
return $query->whereRaw('ST_Distance_Sphere(`center`, POINT(?, ?)) find() !== null;
}
3. 支持多边形围栏(可选进阶)
若需不规则区域(如园区边界),改用 POLYGON 字段:
- 建表时字段类型改为
polygon NOT NULL SRID 4326 - 插入示例:
ST_PolygonFromText('POLYGON((116.45 39.90, 116.46 39.90, 116.46 39.91, 116.45 39.91, 116.45 39.90))', 4326) - 判断逻辑改为:
ST_Contains(`area`, POINT(?, ?)) - 注意:多边形必须闭合(首尾点相同),且顶点按顺时针或逆时针有序排列
4. 实际调用与注意事项
在控制器中调用即可完成实时判定:
$fence = new FenceModel();
if ($fence->isInCircle(116.4585, 39.9043)) {
echo '用户在围栏内';
} else {
echo '用户在围栏外';
}
- 确保数据库连接开启
strict模式,并设置正确的字符集(utf8mb4)和时区 - 坐标必须使用 WGS84(GPS 标准),若来源是高德/百度地图,需先做坐标系转换(BD09/ GCJ02 → WGS84)
- 高频查询建议对
center字段加空间索引,大幅提升性能 - TP8 的 Query Builder 对空间函数支持良好,无需拼接原始 SQL,但
whereRaw仍需谨慎使用
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











