php通过geohash算法实现查找附近的商铺
geohash有以下几个特点:
首先,geohash用一个字符串表示经度和纬度两个坐标。利用geohash,只需在一列上应用索引即可。
其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。
第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),即可查询附近的所有地点。
Geohash比直接用经纬度的高效很多。
Geohash基础类:
<?php /**
* Geohash generation class for php
*/
/**
*
* Encode and decode geohashes
*
* Find neighbors
*
*/
class Geohash
{
private $bitss = [16, 8, 4, 2, 1];
private $neighbors = [];
private $borders = []; private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
private $codingMap = []; public function __construct()
{ $this->neighbors['right']['even'] = 'bc01fg45238967deuvhjyznpkmstqrwx';
$this->neighbors['left']['even'] = '238967debc01fg45kmstqrwxuvhjyznp';
$this->neighbors['top']['even'] = 'p0r21436x8zb9dcf5h7kjnmqesgutwvy';
$this->neighbors['bottom']['even'] = '14365h7k9dcfesgujnmqp0r2twvyx8zb'; $this->borders['right']['even'] = 'bcfguvyz';
$this->borders['left']['even'] = '0145hjnp';
$this->borders['top']['even'] = 'prxz';
$this->borders['bottom']['even'] = '028b'; $this->neighbors['bottom']['odd'] = $this->neighbors['left']['even'];
$this->neighbors['top']['odd'] = $this->neighbors['right']['even'];
$this->neighbors['left']['odd'] = $this->neighbors['bottom']['even'];
$this->neighbors['right']['odd'] = $this->neighbors['top']['even']; $this->borders['bottom']['odd'] = $this->borders['left']['even'];
$this->borders['top']['odd'] = $this->borders['right']['even'];
$this->borders['left']['odd'] = $this->borders['bottom']['even'];
$this->borders['right']['odd'] = $this->borders['top']['even']; //build map from encoding char to 0 padded bitfield
for($i=0; $i<32; $i++) {
$this->codingMap[substr($this->coding, $i, 1)] = str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
} } /**
* Decode a geohash and return an array with decimal lat,long in it
*/
public function decode($hash)
{
//decode hash into binary string
$binary = "";
$hl = strlen($hash);
for ($i=0; $i<$hl; $i++) { $binary .= $this->codingMap[substr($hash, $i, 1)];
} //split the binary into lat and log binary strings
$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);
}
} //now concert to decimal
$lat = $this->binDecode($blat, -90, 90);
$long = $this->binDecode($blong, -180, 180); //figure out how precise the bit count makes this calculation
$latErr = $this->calcError(strlen($blat), -90, 90);
$longErr = $this->calcError(strlen($blong), -180, 180); //how many decimal places should we use? There's a little art to
//this to ensure I get the same roundings as geohash.org
$latPlaces = max(1, -round(log10($latErr))) - 1;
$longPlaces = max(1, -round(log10($longErr))) - 1; //round it
$lat = round($lat, $latPlaces);
$long = round($long, $longPlaces);
return array($lat, $long);
} private function calculateAdjacent($srcHash, $dir)
{
$srcHash = strtolower($srcHash);
$lastChr = $srcHash[strlen($srcHash) - 1];
$type = (strlen($srcHash) % 2) ? 'odd' : 'even';
$base = substr($srcHash, 0, strlen($srcHash) - 1); if (strpos($this->borders[$dir][$type], $lastChr) !== false) {
$base = $this->calculateAdjacent($base, $dir);
} return $base . $this->coding[strpos($this->neighbors[$dir][$type], $lastChr)];
} public function neighbors($srcHash)
{
$geohashPrefix = substr($srcHash, 0, strlen($srcHash) - 1); $neighbors['top'] = $this->calculateAdjacent($srcHash, 'top');
$neighbors['bottom'] = $this->calculateAdjacent($srcHash, 'bottom');
$neighbors['right'] = $this->calculateAdjacent($srcHash, 'right');
$neighbors['left'] = $this->calculateAdjacent($srcHash, 'left'); $neighbors['topleft'] = $this->calculateAdjacent($neighbors['left'], 'top');
$neighbors['topright'] = $this->calculateAdjacent($neighbors['right'], 'top');
$neighbors['bottomright'] = $this->calculateAdjacent($neighbors['right'], 'bottom');
$neighbors['bottomleft'] = $this->calculateAdjacent($neighbors['left'], 'bottom'); return $neighbors;
} /**
* Encode a hash from given lat and long
*/
public function encode($lat, $long)
{
//how many bits does latitude need?
$plat = $this->precision($lat);
$latbits = 1;
$err = 45;
while ($err > $plat) {
$latbits++;
$err /= 2;
} //how many bits does longitude need?
$plong = $this->precision($long);
$longbits = 1;
$err = 90;
while ($err > $plong) {
$longbits++;
$err /= 2;
} //bit counts need to be equal
$bits = max($latbits, $longbits); //as the hash create bits in groups of 5, lets not
//waste any bits - lets bulk it up to a multiple of 5
//and favour the longitude for any odd bits
$longbits = $bits;
$latbits = $bits;
$addlong = 1;
while (($longbits + $latbits) % 5 != 0) {
$longbits += $addlong;
$latbits += !$addlong;
$addlong = !$addlong;
} //encode each as binary string
$blat = $this->binEncode($lat, -90, 90, $latbits);
$blong = $this->binEncode($long, -180, 180, $longbits); //merge lat and long together
$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;
} //convert binary string to hash
$hash = "";
for ($i=0; $i<strlen($binary); $i+=5) {
$n = bindec(substr($binary, $i, 5));
$hash = $hash.$this->coding[$n];
} return $hash;
} /**
* What's the maximum error for $bits bits covering a range $min to $max
*/
private function calcError($bits, $min, $max)
{
$err = ($max - $min) / 2; while ($bits--) {
$err /= 2;
} return $err;
} /*
* returns precision of number
* precision of 42 is 0.5
* precision of 42.4 is 0.05
* precision of 42.41 is 0.005 etc
*
*/
private function precision($number)
{
$precision = 0;
$pt = strpos($number,'.');
if ($pt !== false) {
$precision = -(strlen($number) - $pt - 1);
} return pow(10, $precision) / 2;
} /**
* create binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
* removing the tail recursion is left an exercise for the reader
*
* Author: Bruce Chen (weibo: @一个开发者)
*/
private function binEncode($number, $min, $max, $bitcount)
{
if ($bitcount == 0) return ""; #echo "$bitcount: $min $max<br>"; //this is our mid point - we will produce a bit to say
//whether $number is above or below this mid point
$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);
}
} /**
* decodes binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
* removing the tail recursion is left an exercise for the reader
*
*/
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);
}
}
}
测试实例:
$geohash = new GeoHash();
$hash = $geohash->encode($latitude, $longitude);
//var_dump($hash);exit;
//决定查询范围,值越大,获取的范围越小
//当geohash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,编码长度需要根据数据情况进行选择。
$pre_hash = substr($hash, 0, 5);
//取出相邻八个区域
$neighbors = $geohash->neighbors($pre_hash);
array_push($neighbors, $pre_hash); $values = '';
foreach ($neighbors as $key=>$val) {
$values .= '\'' . $val . '\'' .',';
}
$values = substr($values, 0, -1);
// var_dump($values);
$stores=\DB::select("SELECT * FROM `stores` WHERE LEFT(`geohash`,5) IN ($values)"); foreach ($stores as $key => $value) {
$geohash_arr=$geohash->decode($value->geohash);
$stores[$key]->latitude=$geohash_arr[0];//纬度
$stores[$key]->longitude=$geohash_arr[1];//经度
$distance=$this->getDistance($request['latitude'],$request['longitude'],$value->latitude,$value->longitude);
$stores[$key]->distance=$distance;
$sortdistance[$key] = $distance;
}
array_multisort($sortdistance,SORT_ASC,$stores);
// var_dump($stores);
return response()->json(['status_code'=>0,'nearby_stores'=>$stores]); /**
* @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); //四舍五入
}
php通过geohash算法实现查找附近的商铺的更多相关文章
- 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现
本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...
- 查找附近网点geohash算法及实现 (Java版本号)
參考文档: http://blog.csdn.net/wangxiafghj/article/details/9014363geohash 算法原理及实现方式 http://blog.charlee ...
- 空间索引 - GeoHash算法及其实现优化
h1,h2,h3,h4,h5,h6,p,blockquote { margin: 0; padding: 0 } body { font-family: "Helvetica Neue&qu ...
- geohash 算法原理及实现方式
转自:http://www.cnblogs.com/dengxinglin/archive/2012/12/14/2817761.html geohash 算法原理及实现方式 1.geohash 特点 ...
- Java中常用的查找算法——顺序查找和二分查找
Java中常用的查找算法——顺序查找和二分查找 神话丿小王子的博客 一.顺序查找: a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数据最后一位 ...
- 基于GeoHash算法的附近点搜索实现(一)
1. 引入 最近在参加学校的计算机仿真大赛,时间好像有点不够,所以只完成了前面的一部分最基础的功能,中途还是选择了放弃.但是之前的部分的确觉得完成得还不错,在这里分享一下.题目是要完成一个宇宙飞船加油 ...
- PHP数组基本排序算法和查找算法
关于PHP中的基础算法,小结一下,也算是本博客的第一篇文章1.2种排序算法冒泡排序:例子:个人见解 5 6 2 3 7 9 第一趟 5 6 2 3 7 9 5 2 6 3 7 9 5 2 3 6 7 ...
- cb34a_c++_STL_算法_查找算法_(7)_lower_bound
cb34a_c++_STL_算法_查找算法_(7)_lower_bound//针对已序区间的查找算法,如set,multiset关联容器-自动排序lower_bound()--第一个可能的位置uppe ...
- cb33a_c++_STL_算法_查找算法_(6)binary_search_includes
cb33a_c++_STL_算法_查找算法_(6)binary_search_includes//针对已序区间的查找算法,如set,multiset关联容器-自动排序binary_search(b,e ...
随机推荐
- Leetcode:单调数列
题目 如果数组是单调递增或单调递减的,那么它是单调的. 如果对于所有 i <= j,A[i] <= A[j],那么数组 A 是单调递增的. 如果对于所有 i <= j,A[i]> ...
- 如何使用sass
Sass 是对 CSS 的扩展,让 CSS 语言更强大.优雅. 它允许你使用变量.嵌套规则.mixin.导入等众多功能, 并且完全兼容 CSS 语法. Sass 有助于保持大型样式表结构良好, 同时也 ...
- Try .NET离线版
https://github.com/dotnet/try Try .NET离线版 使用Try.NET创建可交互.NET文档 原文地址:Create Interactive .NET Docume ...
- Proto.Actor模型
Proto.Actor模型 http://proto.actor/ https://github.com/axzxs2001/ProtoActorSample https://www.cnblogs. ...
- 次小生成树(SST)
次小生成树(SST) 题目背景 Awson是某国际学校信竞组的一只菜鸡.Awson最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当Awson洋洋得意之时,信竞组其他大 ...
- Typora--Draw Diagrams With Markdown
Typora Typora supports some Markdown extension for diagrams, you could enable this feature from pref ...
- nuxt实践
利用手脚架搭起来的服务端渲染实例目录结构.nuxtassets 未编译的静态资源如 LESS.SASS 或 JavaScriptcomponents 用于组织应用的 Vue.js 组件middlewa ...
- HDP和包围曝光
摄影笔记:http://mp.weixin.qq.com/s/6xgTtAcLAPkWY9FjqrfvtA 我们通过观察直方图曝光,尽量要直方图两边的纯黑和纯白区域不要有信息,就是亮的地方不死白,暗的 ...
- 洛谷P1435 回文字串(dp)
题意 题目链接 回文词是一种对称的字符串.任意给定一个字符串,通过插入若干字符,都可以变成回文词.此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数. 比如 “Ab3bd”插入2个字符后可 ...
- java校验maven下载的jar文件
有时候maven真的很坑! 有时候提示invalid LOC header (bad signat signature), 但又有时候什么都不提示,工程报错,情况有肯多中,不知道大家遇到过几种诡异的. ...