<?php
/**
* 生成指定区域内的随机经纬度(以北京区域为例,方便验证效果)
* 北京大致范围:纬度39.4°~41.6°,经度115.4°~117.8°
* @return array [lat, lng] 随机纬度、经度
*/
function generate_random_lnglat() {
// 随机纬度(39.4 ~ 41.6)
$lat = 39.4 + mt_rand(0, 220000) / 100000;
// 随机经度(115.4 ~ 117.8)
$lng = 115.4 + mt_rand(0, 240000) / 100000;
// 保留6位小数(与数据表字段一致)
return [
round($lat, 6),
round($lng, 6)
];
}
/**
* GeoHash编码核心函数:将经纬度转换为GeoHash字符串
* GeoHash原理:将地球表面划分为网格,每个网格用一串字符表示,字符越长精度越高
* @param float $lat 纬度(-90 ~ 90)
* @param float $lng 经度(-180 ~ 180)
* @param int $precision GeoHash精度(默认8位,6位约对应10km范围,8位约对应19m范围)
* @return string 生成的GeoHash字符串
*/
function geohash_encode($lat, $lng, $precision = 8) {
$base32 = '0123456789bcdefghjkmnpqrstuvwxyz';
$latRange = [-90.0, 90.0];
$lngRange = [-180.0, 180.0];
$binary = '';
while (strlen($binary) < $precision * 5) {
$lngMid = ($lngRange[0] + $lngRange[1]) / 2;
if ($lng > $lngMid) {
$binary .= '1';
$lngRange[0] = $lngMid;
} else {
$binary .= '0';
$lngRange[1] = $lngMid;
}
$latMid = ($latRange[0] + $latRange[1]) / 2;
if ($lat > $latMid) {
$binary .= '1';
$latRange[0] = $latMid;
} else {
$binary .= '0';
$latRange[1] = $latMid;
}
}
$geohash = '';
for ($i = 0; $i < strlen($binary); $i += 5) {
$chunk = substr($binary, $i, 5);
$index = bindec($chunk);
$geohash .= $base32[$index];
}
return substr($geohash, 0, $precision);
}
/**
* 计算6位GeoHash的8个相邻区域(上、右上、右、右下、下、左下、左、左上)
* 6位GeoHash是常用精度,对应约1.2km×0.6km的矩形区域
* @param string $geohash6 6位的GeoHash字符串
* @return array 包含自身和8个邻居的GeoHash数组(共9个)
*/
function geohash_get_6bit_neighbors($geohash6) {
$neighbors = [
'north' => ['p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx'],
'northeast' => ['14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp'],
'east' => ['bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'],
'southeast' => ['238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb'],
'south' => ['14365h7k9dcfesgujnmqp0r2twvyx8zb', '238967debc01fg45kmstqrwxuvhjyznp'],
'southwest' => ['p0r21436x8zb9dcf5h7kjnmqesgutwvy', 'bc01fg45238967deuvhjyznpkmstqrwx'],
'west' => ['bc01fg45238967deuvhjyznpkmstqrwx', 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'],
'northwest' => ['238967debc01fg45kmstqrwxuvhjyznp', '14365h7k9dcfesgujnmqp0r2twvyx8zb']
];
$borders = [
'north' => ['prxz', 'bcfguvyz'],
'northeast' => ['prxz', 'bcfguvyz'],
'east' => ['bcfguvyz', 'prxz'],
'southeast' => ['bcfguvyz', 'prxz'],
'south' => ['028b', '0145hjnp'],
'southwest' => ['028b', '0145hjnp'],
'west' => ['0145hjnp', '028b'],
'northwest' => ['0145hjnp', '028b']
];
$geohash = strtolower($geohash6);
$len = strlen($geohash);
if ($len !== 6) {
return [$geohash6];
}
$base32 = '0123456789bcdefghjkmnpqrstuvwxyz';
$base32Idx = array_flip(str_split($base32));
$neighborsList = [$geohash6];
// 遍历8个方向
$directions = ['north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'];
foreach ($directions as $dir) {
$neighbor = geohash_calculate_neighbor($geohash, $dir, $neighbors, $borders, $base32, $base32Idx);
if ($neighbor && !in_array($neighbor, $neighborsList)) {
$neighborsList[] = $neighbor;
}
}
return $neighborsList;
}
/**
* 递归计算单个方向的GeoHash邻居(核心辅助函数)
* @param string $hash 原始GeoHash字符串
* @param string $dir 方向(north/east/south/west等)
* @param array $neighbors 邻居字符映射表
* @param array $borders 边界字符表
* @param string $base32 32进制编码表
* @param array $base32Idx 32进制字符索引映射
* @return string|null 计算出的邻居GeoHash,越界则返回null
*/
function geohash_calculate_neighbor($hash, $dir, $neighbors, $borders, $base32, $base32Idx) {
$len = strlen($hash);
$last = substr($hash, -1);
$prefix = substr($hash, 0, $len - 1);
$isLat = ($len % 2) == 1; // true: 最后一位是纬度
// 检查是否在边界,需要递归进位
$borderChars = $borders[$dir][$isLat ? 1 : 0];
if (in_array($last, str_split($borderChars)) && $prefix !== '') {
$prefix = geohash_calculate_neighbor($prefix, $dir, $neighbors, $borders, $base32, $base32Idx);
if ($prefix === null) {
return null; // 越界,无邻居
}
}
// 获取原字符在 base32 中的位置
if (!isset($base32Idx[$last])) {
return null; // 非法字符
}
$idx = $base32Idx[$last];
// 从 neighbor 映射表中取对应字符
$neighborMap = $neighbors[$dir][$isLat ? 1 : 0];
if ($idx >= strlen($neighborMap)) {
return null; // 安全检查
}
$newLast = $neighborMap[$idx];
return $prefix . $newLast;
}
geohash编码和周边查询
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
相关阅读更多精彩内容
- Laravel GeoHash Laravel GeoHash LBS地理位置距离计算方法geohash,将一个经...
- 1.场景 随着智能手机和传感器技术的发展,LBS(Location based service)类的应用也逐渐多了...
- 一、联联周边游邀请码填什么填多少 1、联联周边游邀请码填写:555888 (这是高省APP的) ,这样可以获得高佣...
- 21世纪当下互联网时代,网购逐渐的取代我们线下实体店购物。联联周边游是顺势互联网为广大用户研发的一款新型返利App...