附近的人
,這四個字的需求就大有文章可作了。很二逼的作法是,存每一個人的經度緯度,而後遍歷數據庫全部數據,foreach循環,兩點距離座標公式。量少的時候,這個沒啥問題。量大了,掃描全表 + 經緯度距離運算分分鐘拖垮數據庫。那麼是否有方案能夠解決這個痛點呢,今年就來講下Geohash
php
想要不拖垮數據,要作到能走索引。就是跟你無關的點,不要掃描。減小掃描行數來實現減輕數據庫的壓力。那麼減小掃描行數確定要想到索引。但是經緯度有兩個字段,且查詢條件不管怎麼寫都沒辦法走索引。那麼惟一能想到的就是二維變一維。 geohash基本原理是將地球理解爲一個二維平面,將平面遞歸分解成更小的子塊,每一個子塊在必定經緯度範圍內擁有相同的編碼,這種方式簡單粗暴,能夠知足對小規模的數據進行經緯度的檢索。兩個點的距離越近,他們的編碼前綴部分就相同,前綴部分相同越多,表明距離越近
。而後咱們作數據庫掃描的時候 能夠 WHERE geohash Like 'code%'
這樣就起到了走索引從而優化了執行效率。git
<?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的話能夠近似理解爲那我的就在你身邊了。數據庫