场景解决方案:附近的人(GeoHash的应用)
前言
附近的人
,这四个字的需求就大有文章可做了。很二逼的做法是,存每个人的经度纬度,然后遍历数据库所有数据,foreach循环,两点距离坐标公式。量少的时候,这个没啥问题。量大了,扫描全表 + 经纬度距离运算分分钟拖垮数据库。那么是否有方案可以解决这个痛点呢,今年就来说下Geohash
实现思路
想要不拖垮数据,要做到能走索引。就是跟你无关的点,不要扫描。减少扫描行数来实现减轻数据库的压力。那么减少扫描行数肯定要想到索引。可是经纬度有两个字段,且查询条件无论怎么写都没办法走索引。那么唯一能想到的就是二维变一维。 geohash基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,每个子块在一定经纬度范围内拥有相同的编码,这种方式简单粗暴,可以满足对小规模的数据进行经纬度的检索。两个点的距离越近,他们的编码前缀部分就相同,前缀部分相同越多,代表距离越近
。然后我们做数据库扫描的时候 可以 WHERE geohash Like 'code%'
这样就起到了走索引从而优化了执行效率。
代码思路(PHP)
<?php
class Geohash
{
const LATITUDE = 1;
const LONGITUDE = 2;
const BASE32 = array(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm',
'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z');
public static function encode($latitude = 0, $longitude = 0, $level = 11)
{
$latitude_str = self::separate($latitude, '', 1, self::get_precision_level_num($level, self::LATITUDE), self::get_interval_value(self::LATITUDE));
$longitude_str = self::separate($longitude, '', 1, self::get_precision_level_num($level, self::LONGITUDE), self::get_interval_value(self::LONGITUDE));
return self::geohash_encode(self::combination($latitude_str, $longitude_str));
}
public static function decode($geohash)
{
$data = array();
$combination_str = self::geohash_decode($geohash);
$separate_str = self::de_combination($combination_str);
$data[self::LATITUDE] = self::de_separate($separate_str[self::LATITUDE],1,self::get_interval_value(self::LATITUDE));
$data[self::LONGITUDE] = self::de_separate($separate_str[self::LONGITUDE],1,self::get_interval_value(self::LONGITUDE));
return $data;
}
/**
* 编码
*/
/**
* @param float $num经度或纬度
* @param string $str递归字符串
* @param int $i 递归次数
* @param int $max_separate_num递归总次数
* @param array $data 区间值
* @return string
*/
public static function separate($num, $str = '', $i = 1, $max_separate_num = 20, $data = array('min' => -90, 'max' => 90))
{
$count = ($data['max'] - $data['min']) / 2;
$limit_0 = array(
'min' => $data['min'],
'max' => $data['min'] + $count
);
$limit_1 = array(
'min' => $data['min'] + $count,
'max' => $data['max']
);
$str .= $num > $limit_1['min'] ? 1 : 0;
if ($i >= $max_separate_num) {
return $str;
} else {
return self::separate($num, $str, $i + 1, $max_separate_num, $num > $limit_1['min'] ? $limit_1 : $limit_0);
}
}
/**
* @param $latitude_str 纬度
* @param $longitude_str 经度
*/
public static function combination($latitude_str, $longitude_str)
{
$str = '';
for ($i = 0; $i < strlen($longitude_str); $i++) {//根据精度表,可发现维度>=精度
$str .= $longitude_str{$i};
if(isset($latitude_str{$i})){
$str .= $latitude_str{$i};
}
}
return $str;
}
public static function geohash_encode($str)
{
$str_arr = str_split($str, 5);//按5位分割字符串
$encode_str = '';
foreach ($str_arr as $va) {
$decimal = bindec($va);
$encode_str .= self::BASE32[$decimal];
}
return $encode_str;
}
/**
* 编码
*/
/**
* 解码
*/
public static function geohash_decode($str)
{
//根据一位字符串进行切割
$str_arr = str_split($str, 1);
$decode_str = '';
$base32 = array_flip(self::BASE32);
foreach ($str_arr as $va) {
$decode_str .= str_pad(decbin($base32[$va]),5,'0',STR_PAD_LEFT);
}
return (string)$decode_str;
}
/**
* 解码二进制组合
* @param $str
* @return array
*/
public static function de_combination($str)
{
$latitude_str = '';
$longitude_str = '';
//根据两位字符串切割
$str_arr = str_split($str, 2);
foreach ($str_arr as $va) {
$longitude_str .= $va[0];
if(isset($va[1])){//根据精度表,可发现维度>=精度
$latitude_str .= $va[1];
}
}
return array(
self::LATITUDE=>$latitude_str,
self::LONGITUDE=>$longitude_str,
);
}
/**
* 解码二分区间
* @param $str
* @param string $i//执行次数
* @param array $data、、区间
*/
public static function de_separate($str,$i=1,$data = array('min' => -90, 'max' => 90)){
$count = ($data['max'] - $data['min']) / 2;
$limit_0 = array(
'min' => $data['min'],
'max' => $data['min'] + $count
);
$limit_1 = array(
'min' => $data['min'] + $count,
'max' => $data['max']
);
if($str[$i-1]==0){
$data = $limit_0;
}else{
$data = $limit_1;
}
if ($i >= strlen($str)) {
return $data;
} else {
return self::de_separate($str, $i + 1, $data);
}
}
/**
* 解码
*/
/**
* 根据精度获取二分次数
* @param $level
* @param $type
*/
public static function get_precision_level_num($level, $type = self::LATITUDE)
{
$precision = array(
1 => array(
self::LATITUDE => 2,
self::LONGITUDE => 3,
),
2 => array(
self::LATITUDE => 5,
self::LONGITUDE => 5,
),
3 => array(
self::LATITUDE => 7,
self::LONGITUDE => 8,
),
4 => array(
self::LATITUDE => 10,
self::LONGITUDE => 10,
),
5 => array(
self::LATITUDE => 12,
self::LONGITUDE => 13,
),
6 => array(
self::LATITUDE => 15,
self::LONGITUDE => 15,
),
7 => array(
self::LATITUDE => 17,
self::LONGITUDE => 18,
),
8 => array(
self::LATITUDE => 20,
self::LONGITUDE => 20,
),
9 => array(
self::LATITUDE => 22,
self::LONGITUDE => 23,
),
10 => array(
self::LATITUDE => 25,
self::LONGITUDE => 25,
),
11 => array(
self::LATITUDE => 27,
self::LONGITUDE => 28,
),
12 => array(
self::LATITUDE => 30,
self::LONGITUDE => 30,
),
);
return $precision[$level][$type];
}
/**
* 获取区间
* @param $type
* @return mixed
*/
public static function get_interval_value($type = self::LATITUDE)
{
$interval = array(
self::LATITUDE => array(
'min' => -90,
'max' => 90
),
self::LONGITUDE => array(
'min' => -180,
'max' => 180
),
);
return $interval[$type];
}
}
精度值
如图,当前缀码相同为7相差76米左右,为8相差19米,为9的话可以近似理解为那个人就在你身边了。
Lowky
服务端程序狗一枚
推荐阅读
运维笔记:防ssh恶意登录神器denyhosts
只要一台服务器暴露到公网,而且没有禁止ssh账号密码登录或者使用vpn 跳板机等防控行为,那么必定会受到一大波恶意登录尝试。这里推荐一款神器,denyhosts,专门用来应付这种情况。该
谢远东赞 1阅读 2.3k
怎样用 PHP 来实现枚举?
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...
唯一丶赞 25阅读 6.3k评论 4
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...
王中阳Go赞 5阅读 1.8k评论 2
微信公众号开发:自动回复文本/图片/图文消息/关键词回复/上传素材/自定义菜单
对接流程1、申请微信公众号测试账号URL:[链接]2、登录,配置开发者服务器URL和Token开发者服务器配置代码:config.php {代码...} URL是config.php在你服务器的URLToken是上面代码自己设置的Token搞定之后,就能完...
TANKING赞 2阅读 10k
Hyperf 3.0 发布,PHP 新时代
在过去的一年半时间里,Hyperf 2.2 共发布了 35 个小版本,使 Hyperf 达到了一个前所未有的高度,这里也获得了一些不错的数据反馈。
huangzhhui赞 3阅读 1.1k
多个著名 Go 开源项目被放弃,做大开源不能用爱发电,更不能只靠自己!
大家好,我是煎鱼。相信关注我的许多同学都有接触 Go 语言的开发,甚至在企业中多有实践。那么你在日常开发中,势必会接触到 gorilla 组织下的各个 Go 开源项目。如下图:gorilla/mux:Star:17.9k。a powerful r...
煎鱼赞 1阅读 2.3k
Go for 循环有时候真的很坑。。。
大家好,我是煎鱼。不知道有多少 Go 的面试题和泄露,都和 for 循环有关。今天我在周末认真一看,发现了 redefining for loop variable semantics 。著名的硬核大佬 Russ Cox 表示他一直在研究这个问题,并表示十...
煎鱼阅读 3.4k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。