geohash有如下幾個特色:php
首先,geohash用一個字符串表示經度和緯度兩個座標。某些狀況下沒法在兩列上同時應用索引 (例如MySQL 4以前的版本,Google App Engine的數據層等),利用geohash,只需在一列上應用索引便可。mysql
其次,geohash表示的並非一個點,而是一個矩形區域。好比編碼wx4g0ec19,它表示的是一個矩形區域。 使用者能夠發佈地址編碼,既能代表本身位於北海公園附近,又不至於暴露本身的精確座標,有助於隱私保護。git
第三,編碼的前綴能夠表示更大的區域。例如wx4g0ec1,它的前綴wx4g0e表示包含編碼wx4g0ec1在內的更大範圍。 這個特性能夠用於附近地點搜索。首先根據用戶當前座標計算geohash(例如wx4g0ec1)而後取其前綴進行查詢 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),便可查詢附近的全部地點。算法
Geohash比直接用經緯度的高效不少。sql
Geohash算法實現(PHP版本)數據庫
<?php class Geohash { private $coding="0123456789bcdefghjkmnpqrstuvwxyz"; private $codingMap=array(); public function Geohash() { for($i=0; $i<32; $i++) { $this->codingMap[substr($this->coding,$i,1)]=str_pad(decbin($i), 5, "0", STR_PAD_LEFT); } } public function decode($hash) { $binary=""; $hl=strlen($hash); for($i=0; $i<$hl; $i++) { $binary.=$this->codingMap[substr($hash,$i,1)]; } $bl=strlen($binary); $blat=""; $blong=""; for ($i=0; $i<$bl; $i++) { if ($i%2) $blat=$blat.substr($binary,$i,1); else $blong=$blong.substr($binary,$i,1); } $lat=$this->binDecode($blat,-90,90); $long=$this->binDecode($blong,-180,180); // $lat=$this->binDecode($blat,2,54); // $long=$this->binDecode($blong,72,136); $latErr=$this->calcError(strlen($blat),-90,90); $longErr=$this->calcError(strlen($blong),-180,180); $latPlaces=max(1, -round(log10($latErr))) - 1; $longPlaces=max(1, -round(log10($longErr))) - 1; $lat=round($lat, $latPlaces); $long=round($long, $longPlaces); return array($lat,$long); } public function encode($lat,$long) { $plat=$this->precision($lat); $latbits=1; $err=45; while($err>$plat) { $latbits++; $err/=2; } $plong=$this->precision($long); $longbits=1; $err=90; while($err>$plong) { $longbits++; $err/=2; } $bits=max($latbits,$longbits); $longbits=$bits; $latbits=$bits; $addlong=1; while (($longbits+$latbits)%5 != 0) { $longbits+=$addlong; $latbits+=!$addlong; $addlong=!$addlong; } $blat=$this->binEncode($lat,-90,90, $latbits); $blong=$this->binEncode($long,-180,180,$longbits); $binary=""; $uselong=1; while (strlen($blat)+strlen($blong)) { if ($uselong) { $binary=$binary.substr($blong,0,1); $blong=substr($blong,1); } else { $binary=$binary.substr($blat,0,1); $blat=substr($blat,1); } $uselong=!$uselong; } $hash=""; for ($i=0; $i<strlen($binary); $i+=5) { $n=bindec(substr($binary,$i,5)); $hash=$hash.$this->coding[$n]; } return $hash; } private function calcError($bits,$min,$max) { $err=($max-$min)/2; while ($bits--) $err/=2; return $err; } private function precision($number) { $precision=0; $pt=strpos($number,'.'); if ($pt!==false) { $precision=-(strlen($number)-$pt-1); } return pow(10,$precision)/2; } private function binEncode($number, $min, $max, $bitcount) { if ($bitcount==0) return ""; $mid=($min+$max)/2; if ($number>$mid) return "1".$this->binEncode($number, $mid, $max,$bitcount-1); else return "0".$this->binEncode($number, $min, $mid,$bitcount-1); } private function binDecode($binary, $min, $max) { $mid=($min+$max)/2; if (strlen($binary)==0) return $mid; $bit=substr($binary,0,1); $binary=substr($binary,1); if ($bit==1) return $this->binDecode($binary, $mid, $max); else return $this->binDecode($binary, $min, $mid); } } ?>
測試app
<?php require_once("db_config.php"); require_once('geohash.class.php'); $geohash=new Geohash; //經緯度轉換成Geohash //獲取附近的信息 $n_latitude = 34.236080797698; $n_longitude = 109.0145193757; echo "當前位置爲:經度108.7455,緯度34.3608<br/><br/> 如下網點離我最近:"; //開始 $b_time = microtime(true); //方案A,直接利用數據庫存儲函數,遍歷排序 //方案B geohash求出附近,而後排序 //當前 geohash值 $n_geohash = $geohash->encode($n_latitude,$n_longitude); //附近 $n = 3; $like_geohash = substr($n_geohash, 0, $n); $sql = 'select * from retailersinfotable where geohash like "'.$like_geohash.'%"'; $query = mysql_query($sql); if(mysql_num_rows($query)) { while($row=mysql_fetch_array($query)) { $data[] = array ( "latitude" =>$row["Latitude"], "longitude"=>$row["Longitude"], "name" =>$row["RetailersName"], ); } } //算出實際距離 foreach($data as $key=>$val) { $distance = getDistance($n_latitude,$n_longitude,$val['latitude'],$val['longitude']); $data[$key]['distance'] = $distance; //排序列 $sortdistance[$key] = $distance; } //距離排序 array_multisort($sortdistance,SORT_ASC,$data); //結束 $e_time = microtime(true); echo "(計算耗時:" ; echo $e_time - $b_time; echo "秒)<br/>"; //var_dump($data); foreach($data as $key=>$val) { echo "</br>"; echo $val['distance']. " 米-------".$val['name']; } /** * @desc 根據兩點間的經緯度計算距離 * @param float $latitude 緯度值 * @param float $longitude 經度值 */ function getDistance($latitude1, $longitude1, $latitude2, $longitude2) { $earth_radius = 6371000; //approximate radius of earth in meters $dLat = deg2rad($latitude2 - $latitude1); $dLon = deg2rad($longitude2 - $longitude1); /* Using the Haversine formula http://en.wikipedia.org/wiki/Haversine_formula http://www.codecodex.com/wiki/Calculate_Distance_Between_Two_Points_on_a_Globe 驗證:百度地圖 http://developer.baidu.com/map/jsdemo.htm#a6_1 calculate the distance */ $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * sin($dLon/2) * sin($dLon/2); $c = 2 * asin(sqrt($a)); $d = $earth_radius * $c; return round($d); //四捨五入 } ?>