之前没做过,后来自己写好了发现个问题,读出来的就不是附近的人。只是单纯的计算出两点间的距离而已,后来又想了一下,并且网上找了找,对比了一下。用geohash 算法 还是比较不错的。
下面附上方法步骤
原理
首先要了解一下 geohash 算法 网上关于这个讲的蛮多的,在此不详细说明了,我简单的理解就是无限缩小定位一个范围。虽说是无限的,但是还是有限的。
比如一个坐标点(29,121) ,把经纬度分为 (-90,0) (0,90) (-180,0)(0,180) ,如果给的坐标在这个区间内,就为1 不在的话 就为 0 ,以纬度然后继续分 在(0,45)的区间内 则为1 不在则为0,然后继续 (0,22.5)(22.5,45)一直分直到达到你所需要的精度为止。
然后得到类似 1011 1000 1100 0111 1001 这样的字符串 经度的 也是一样 合并两个字符串 11100 11101 00100 01111 00000 01101 01011 00001 奇数位为 纬度 偶数位 为经度。最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码。 然后得到的 类似 wx4g0ec1(举例,不准的) 这样的字符串。
附 PHPGeohash.class.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); $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 ); } } ?> |
查找
把这样的一条数据(wx4g0ec1)存入到你的数据表里面
把你需要查找的坐标 转换成 类似(wx4g0ec1)字符 ,然后用LIKE 查询 你存入的 坐标信息
查找附近,利用 在SQL中 LIKE ‘wx4g%’;且此结果可缓存;在小区域内,不会因为改变经纬度,而重新数据库查询
查找出的有限结果,如需要求距离或者排序,可利用距离公式和二维数据排序;此时也是少量数据,会很快的
查出来的数据按照距离远近排序一下,然后对查出的数据分下页就好了
测算距离的函数。(具体原理不在详细说明)
/** * 说明: 根据两点间的经纬度计算距离 * @param type $lat1 纬度值1 * @param type $lng1 经度值1 * @param type $lat2 纬度值2 * @param type $lng2 经度值2 * @return type */ function getDistance( $lat1 , $lng1 , $lat2 , $lng2 ) { $earthRadius = 6367000; $lat1 = ( $lat1 * pi() ) / 180; $lng1 = ( $lng1 * pi() ) / 180; $lat2 = ( $lat2 * pi() ) / 180; $lng2 = ( $lng2 * pi() ) / 180; $calcLongitude = $lng2 - $lng1 ; $calcLatitude = $lat2 - $lat1 ; $stepOne = pow(sin( $calcLatitude / 2), 2) + cos ( $lat1 ) * cos ( $lat2 ) * pow(sin( $calcLongitude / 2), 2); $stepTwo = 2 * asin(min(1, sqrt( $stepOne ))); $calculatedDistance = $earthRadius * $stepTwo ; return $calculatedDistance ; } |
排序函数 |
php 自带的 函数 array_multisort 具体怎么用可以查下手册; 下面是我写的一个 原先不知道有自带函数。。 /** * 对二维数组进行排序 * @param type $arr * @param type $keys * @param type $type * @return type */ function array_sort( $arr , $keys , $type = 'asc' ) { $keysvalue = $new_array = array (); foreach ( $arr as $k => $v ) { $keysvalue [ $k ] = $v [ $keys ]; } if ( $type == 'asc' ) { asort( $keysvalue ); } else { arsort( $keysvalue ); } foreach ( $keysvalue as $k => $v ) { $new_array [ $k ] = $arr [ $k ]; } $array = array (); foreach ( $new_array as $v ) { $array [] = $v ; } return $array ; } |