-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGeoHash.php
More file actions
204 lines (167 loc) · 6.51 KB
/
GeoHash.php
File metadata and controls
204 lines (167 loc) · 6.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php
/**
* Created by IntelliJ IDEA.
* User: fang.cai.liang@aliyun.com
* Date: 2017/7/13
* Time: 20:12
*/
namespace lbs;
class GeoHash
{
const LAT_RANGE_MIN = -90; // 纬度的范围 [-90, 90]
const LAT_RANGE_MAX = 90;
const LONG_RANGE_MIN = -180; // 经度的范围 [-180, 180]
const LONG_RANGE_MAX = 180;
const BASE32_CODE = [ // base32 编码表
0 => '0', 1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9',
10 => 'b', 11 => 'c', 12 => 'd', 13 => 'e', 14 => 'f', 1 => 'g', 16 => 'h', 17 => 'j', 18 => 'k', 19 => 'm',
20 => 'n', 21 => 'p', 22 => 'q', 23 => 'r', 24 => 's', 25 => 't', 26 => 'u', 27 => 'v', 28 => 'w', 29 => 'x',
30 => 'y', 31 => 'z'
];
private $geohashLen = 8; // 默认生成的 geohash 字符串的长度
private $latComminuteTimes = 20; // 默认对纬度切分的次数
private $longComminuteTimes = 20; // 默认对经度切分的次数
private $minLatUnit; // 纬度最小划分单位
private $minLongUnit; // 经度最小划分单位
function __construct($geohashLen = null, $latComminuteTimes = null, $longComminuteTimes = null)
{
if(!is_null($geohashLen)){
$this->geohashLen = $geohashLen;
}
if(!is_null($latComminuteTimes)){
$this->latComminuteTimes = $latComminuteTimes;
}
if(!is_null($longComminuteTimes)){
$this->longComminuteTimes = $longComminuteTimes;
}
$this->check();
$this ->countMinUnit();
}
/**
* 检查传入的参数是否合法
* @return bool
* @throws \Exception
*/
private function check(){
if((($this->latComminuteTimes + $this->longComminuteTimes) >= 5) && ($this->geohashLen === ($this->latComminuteTimes + $this->longComminuteTimes) / 5)){
return true;
}
if($this->latComminuteTimes > $this->longComminuteTimes){ // 按照奇数位放纬度,偶数为放经度的规则并且偶数下标是从0开始, 所以纬度切分的次数不能大于经度切分的次数
throw new \Exception('纬度切分的次数不能大于经度切分的次数!');
}
throw new \Exception('$geohashLen, $latComminuteTimes 和 $longComminuteTimes 3者的配置不匹配!');
}
/**
* 计算经纬度的最小切分单位
*/
private function countMinUnit(){
$this->minLatUnit = self::LAT_RANGE_MAX - self::LAT_RANGE_MIN;
for($i = 0; $i < $this->latComminuteTimes; $i ++){
$this->minLatUnit = $this->minLatUnit / 2.0;
}
$this->minLongUnit = self::LONG_RANGE_MAX - self::LONG_RANGE_MIN;
for($j = 0; $j < $this->longComminuteTimes; $j ++){
$this->minLongUnit = $this->minLongUnit / 2.0;
}
}
/**
* 将经度或纬度转换为一个二进制数组
* @param $val
* @param $minVal
* @param $maxVal
* @param $comminuteTimes
* @return array
*/
private function genBinCodeArr($val, $minVal, $maxVal, $comminuteTimes){
$binCodeArr = [];
for($i = 0; $i < $comminuteTimes; $i ++){
$midVal = ($minVal + $maxVal) / 2.0;
if($val > $midVal){
$binCodeArr[$i] = '1';
$minVal = $midVal;
}else{
$binCodeArr[$i] = '0';
$maxVal = $midVal;
}
}
return $binCodeArr;
}
/**
* 按照奇数位放纬度,偶数为放经度的规则组码
* @param $latBinCodeArr
* @param $longBinCodeArr
* @return string
*/
private function mergeBinCode($latBinCodeArr, $longBinCodeArr){
$len = count($latBinCodeArr) + count($longBinCodeArr);
$binCode = '';
for($i = 0; $i < $len; $i ++){
if(($i % 2) === 0){
$binCode = $binCode.array_shift($longBinCodeArr);
}else{
$binCode = $binCode.array_shift($latBinCodeArr);
}
}
return $binCode;
}
/**
* base32编码
* @param $binCode
* @return string
*/
private function base32($binCode){
$binaryArr = str_split($binCode, 5);
$base32Code = '';
foreach ($binaryArr as $binary) {
$base32Code = $base32Code.(self::BASE32_CODE[bindec($binary)]);
}
return $base32Code;
}
/**
* 生成某个坐标的 goehash 值
* @param $lat
* @param $long
* @return string
*/
public function genGeoHash($lat, $long){
$latBinCodeArr = $this->genBinCodeArr($lat, self::LAT_RANGE_MIN, self::LAT_RANGE_MAX, $this->latComminuteTimes);
$longBinCodeArr = $this->genBinCodeArr($long, self::LONG_RANGE_MIN, self::LONG_RANGE_MAX, $this->longComminuteTimes);
return $this->base32($this->mergeBinCode($latBinCodeArr, $longBinCodeArr));
}
/**
* 获取 当前坐标矩形块,以及其周围8个矩形块 的 geohash 值
* @param $lat
* @param $long
* @return array
*/
public function genGeoHash9($lat, $long){
$geoHashArr = [];
$upLat = $lat + $this->minLatUnit;
$geoHashArr['up'] = $this->genGeoHash($upLat, $long);
$geoHashArr['current'] = $this->genGeoHash($lat, $long);
$downLat = $lat - $this->minLatUnit;
$geoHashArr['down'] = $this->genGeoHash($downLat, $long);
$leftUpLat = $lat + $this->minLatUnit;
$leftUpLong = $long - $this->minLongUnit;
$geoHashArr['leftUp'] = $this->genGeoHash($leftUpLat, $leftUpLong);
$leftLong = $long - $this->minLongUnit;
$geoHashArr['left'] = $this->genGeoHash($lat, $leftLong);
$leftDownLat = $lat - $this->minLatUnit;
$leftDownLong = $long - $this->minLongUnit;
$geoHashArr['leftDown'] = $this->genGeoHash($leftDownLat, $leftDownLong);
$rightUpLat = $lat + $this->minLatUnit;
$rightUpLong = $long + $this->minLongUnit;
$geoHashArr['rightUp'] = $this->genGeoHash($rightUpLat, $rightUpLong);
$rightLong = $long + $this->minLongUnit;
$geoHashArr['right'] = $this->genGeoHash($lat, $rightLong);
$rightDownLat = $lat - $this->minLatUnit;
$rightDownLong = $long + $this->minLongUnit;
$geoHashArr['rightDown'] = $this->genGeoHash($rightDownLat, $rightDownLong);
return $geoHashArr;
}
}
$geo = new GeoHash();
$hash = $geo->genGeoHash(40.058918, 116.312621);
echo $hash.PHP_EOL;
$hash9 = $geo->genGeoHash9(40.058918, 116.312621);
echo json_encode($hash9).PHP_EOL;