php LBS(附近地理位置)功能实现的一些思路
在开发中经常会遇到把数据库已有经纬度的地方进行距离排序然后返回给用户
例如一些外卖app打开会返回附近的商店,这个是怎么做到的呢?
思路一:
根据用户当前的位置,用计算经纬度距离的算法逐一计算比对距离,然后进行排序。这里可以参考下面这个算法:
<?php
/**
* 查找两个经纬度之间的距离
*
* @param $latitude1 float 起始纬度
* @param $longitude1 float 起始经度
* @param $latitude2 float 目标纬度
* @param $longitude2 float 目标经度
* @return array(miles=>英里,feet=>英尺,yards=>码,kilometers=>公里,meters=>米)
* @example
*
* $point1 = array('lat' => 40.770623, 'long' => -73.964367);
* $point2 = array('lat' => 40.758224, 'long' => -73.917404);
* $distance = getDistanceBetweenPointsNew($point1['lat'], $point1['long'], $point2['lat'], $point2['long']);
* foreach ($distance as $unit => $value) {
* echo $unit.': '.number_format($value,4);
* }
*
* The example returns the following:
*
* miles: 2.6025 //英里
* feet: 13,741.4350 //英尺
* yards: 4,580.4783 //码
* kilometers: 4.1884 //公里
* meters: 4,188.3894 //米
*
*/
function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2) {
$theta = $longitude1 - $longitude2;
$miles = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta)));
$miles = acos($miles);
$miles = rad2deg($miles);
$miles = $miles * 60 * 1.1515;
$feet = $miles * 5280;
$yards = $feet / 3;
$kilometers = $miles * 1.609344;
$meters = $kilometers * 1000;
return compact('miles', 'feet', 'yards', 'kilometers', 'meters');
}
?>
这个思路是要每次都获取全部数据,然后进行不断的循环计算,对于大数据量来说简直是噩梦。
思路二:
利用二维的经纬度转换成一维的数据,然后直接sql查询,无须一一比对。
例如经纬度 110.993736,21.495705 => w7yfm9pjt9b4
这就是geohash算法,这里简单说一下,geohash是一种地理位置编码,通过数学的方法进行一定的转换,使其与经纬度对应,变成一串可比对的字符串。
这里不做深入的了解,大概知道一下就好,转换出来的编码有一定的规律,例如同一个省份的前几位字符是一样的,字符数相似越多,证明距离越近。类似于公民身份证一样。
有兴趣的可以自行搜索了解一下。
下面直接给出转换的php代码
<?php
/**
* Encode and decode geohashes
*
*/
class Geohash {
private $coding = "0123456789bcdefghjkmnpqrstuvwxyz";
private $codingMap = array();
public function Geohash() {
//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);
}
/**
* 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
*/
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编码并储存起来,比对的时候直接sql
$sql = 'select * from xxx where geohash like "'.$like_geohash.'%"';
这里like_geohash位数越多说明越精确。
下面是geohash经度距离换算关系,比如geohash如果有7位数,说明范围在76米左右,八位数则是19米,可以根据这个进行查询。
geohash长度 | Lat位数 | Lng位数 | Lat误差 | Lng误差 | km误差 |
---|---|---|---|---|---|
1 | 2 | 3 | ±23 | ±23 | ±2500 |
2 | 5 | 5 | ± 2.8 | ±5.6 | ±630 |
3 | 7 | 8 | ± 0.70 | ± 0.7 | ±78 |
4 | 10 | 10 | ± 0.087 | ± 0.18 | ±20 |
5 | 12 | 13 | ± 0.022 | ± 0.022 | ±2.4 |
6 | 15 | 15 | ± 0.0027 | ± 0.0055 | ±0.61 |
7 | 17 | 18 | ±0.00068 | ±0.00068 | ±0.076 |
8 | 20 | 20 | ±0.000086 | ±0.000172 | ±0.01911 |
9 | 22 | 23 | ±0.000021 | ±0.000021 | ±0.00478 |
10 | 25 | 25 | ±0.00000268 | ±0.00000536 | ±0.0005971 |
11 | 27 | 28 | ±0.00000067 | ±0.00000067 | ±0.0001492 |
12 | 30 | 30 | ±0.00000008 | ±0.00000017 | ±0.0000186 |
这个思路明显优于第一个思路,且查询起来速度非常快,也不用管有多大的数据,直接在数据库里面进行like查询就好,不过要做好索引才行,缺点也是比较明显
无法控制想要的精确访问,对于返回的数据无法进行距离的先后排序,不过已经能满足一定的需求,后期再结合思路一也可以做到距离的先后。
思路三:
前面两种方法都是通过很生硬的数学方法进行比对,所计算的也都是直线距离,但是现实并不是数学那样理想。
现实中两个很靠近的经纬度中间也有可能隔着一条跨不过去的河导致要绕很远的路,这时就要考虑实际情况。
很庆幸有些地图厂商已经帮我们考虑到了,所以还可以借助第三方api。
这里简单说一下高德地图的[ 云图服务API ]()
1、注册高德地图账户,并申请云图key。
2、创建云地图,也就是把你现在的数据放到高德地图上 [ 云图存储API ](),这里可以手动创建也能调用相关的api创建。
可以把数据导出excel然后批量上传,当然后期如果要新增尽量还是用它提供的接口进行增量添加。创建完成大概会生成这样一张表,有tableid,这个后面查询接口需要使用,字段可以自定义,方便业务逻辑。
3、使用高德api进行查询你的云地图 [ 数据检索 ]() ,这里使用周边检索,可以根据你当前的位置进行检索。
过程其实也不复杂,就是把数据放到高德,高德帮你完成了距离的排序,当然它提供的是比较实际的距离。具体实现需要研究一下高德提供的接口。
这个思路可以解决精准度问题,但开发成本大,还要跑一遍第三方去获取数据,可能会牺牲一定效率,具体取舍,仁者见仁吧。
php LBS(附近地理位置)功能实现的一些思路的更多相关文章
- C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能
前面介绍了很多篇关于使用C#开发微信门户及应用的文章,基本上把当时微信能做的接口都封装差不多了,微信框架也积累了不少模块和用户,最近发现微信公众平台增加了不少内容,特别是在自定义菜单里面增加了扫一扫. ...
- 基于LBS的地理位置附近的搜索以及由近及远的排序
Nosql学习之Redis资料(一) http://redis.io/download 目前基于LBS地理位置的搜索已经应用非常广了,的确是个很方便的东西. 我们做程序的就是要考虑如何通过这些功能,来 ...
- 如何实现LBS轨迹回放功能?含多平台实现代码
本篇文章告诉您,如何实现轨迹回放.并且提供了web端,iOS端,Android端3个平台的轨迹回放代码.拷贝后可以直接使用.另外,文末有小彩蛋,算是开发者的福利. Web端/JavaScript 实现 ...
- 百度搜索附近加盟店等基于LBS云搜索功能的实现
一.注册百度账号,进入开发者平台 创建应用并获取ak 地址如下 http://lbsyun.baidu.com/apiconsole/key/update?app-id=7546025 ok获取到了. ...
- Redis6.2发布 地理位置功能增强了什么?
原文地址:https://developer.aliyun.com/article/780257 Redis社区最近刚刚发布Redis6.2 RC1版本,在本次发布中,阿里云Tair团队(阿里云云内存 ...
- 怎么样才是设计功能函数的好思路(javascript)?
在js里面,对于函数的调用,实际上也是也是面向对象的思路,于是写好js函数,也是考核面向对象设计的能力,同时也必须考虑到如何实现高内聚和低耦合,拿一个例子来说,现在的需求是这样的,实现个投资进度框,就 ...
- iOS @功能的部分实现思路
需求描述 1. 发布信息时,通过键盘键入@符号,或者点选相关功能键,唤醒@列表,进行选择 2.选择结束后,输入栏改色显示相关内容 3.删除时,整体删除@区块,且不能让光标落在@区块之间 实现步骤 1. ...
- Vue小白练级之路---001表单验证功能的一般实现思路
思路: 先各自验证 非空校验 具体规则校验 后兜底校验( 防止用户没输入信息直接登录 ) 实现:( 以 element-ui 为例 ) 在 标签上用 model 动态绑定收集数据的对象(form) 在 ...
- 项目源码--Android基于LBS地理位置信息应用的客户端
下载源码 技术要点: 1. LBS应用框架客户端实现 2. 登录与注册系统 3. TAB类型UI实现 4. HTTP通信模块 5. 源码带详细的中文注释 ...... 详细介绍: 1. LBS应用框架 ...
随机推荐
- SQL Server 主库DML操作慢故障处理过程
从某个时间开始,Cat监控到的数据发现,正式环境的Insert 表很慢,数据库用了AlwasON高可用(1个备库做了实时同步),特别是每天早上9:00--11:00,做活动的时候,下单的insert需 ...
- 【Idea】idea waiting until last debugger command completes
https://blog.csdn.net/KingBoyWorld/article/details/73440717 以上方法可以解决 另: 有说: 并未尝试 https://stackoverfl ...
- cnblog项目--20190309
第一个真正意义的Django项目 ! 预计时间5天 20190309--20190314 目标:学会Django的使用,理解模块关系! querset 相当于一个存放列表的字典 day ...
- 《linux就该这么学》第十四节课:第13章,部署DNS域名解析服务(bind服务)
(借鉴请改动) 第十二章收尾 12.2.nfs网络文件系统 RHEL7默认安装了nfs,配置文件在 /etc/export 写入格式:共享目录 允许的客户端(参数) ro ...
- 关于mysql分组查询
在mysql查询中,用到GROUP BY 根据某一字段分组之后,每组显示的结果都只有第一条,这样的结果通常不是我们想要的. GROUP_CONCAT('字段') 可以将每一组下面的这个字段所有的数 ...
- Pyenv部署
一.Git克隆方式 1.安装git yum -y install git 2.克隆pyenv到本地 git clone https://github.com/pyenv/pyenv.git ~/.py ...
- Linux资源分析工具杂谈(长文慎入)
Linux资源分析工具杂谈 开篇之前请大家先思考一个问题: 磁盘的平均I/O响应时间是1 ms,这个指标是好,还是差? 众所周知,计算机科学是客观的,也就是说对于一个给定的问题,我们总是 ...
- 解决CentOS(6和7版本),/etc/sysconfig/下没有iptables的问题
一.Centos 6版本解决办法: 1.任意运行一条iptables防火墙规则配置命令: iptables -P OUTPUT ACCEPT 2.对iptables服务进行保存: service ip ...
- sitecore8.2 基于相对路径查询item
当前项目: bar (path: /sitecore/content/home/foo/bar) 查询: query:./child/grandchild 结果: grandchild (path: ...
- Wireshark使用介绍(二):应用Wireshark观察基本网络协议
TCP: TCP/IP通过三次握手建立一个连接.这一过程中的三种报文是:SYN,SYN/ACK,ACK. 第一步是找到PC发送到网络服务器的第一个SYN报文,这标识了TCP三次握手的开始. 如果你找不 ...