|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Geotools library. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Antoine Corcy <[email protected]> |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace League\Geotools\Geohash; |
|
13
|
|
|
|
|
14
|
|
|
use League\Geotools\Coordinate\Coordinate; |
|
15
|
|
|
use League\Geotools\Coordinate\CoordinateInterface; |
|
16
|
|
|
use League\Geotools\Exception\InvalidArgumentException; |
|
17
|
|
|
use League\Geotools\Exception\RuntimeException; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Geohash class |
|
21
|
|
|
* |
|
22
|
|
|
* @author Antoine Corcy <[email protected]> |
|
23
|
|
|
*/ |
|
24
|
|
|
class Geohash implements GeohashInterface |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* The minimum length of the geo hash. |
|
28
|
|
|
* |
|
29
|
|
|
* @var integer |
|
30
|
|
|
*/ |
|
31
|
|
|
const MIN_LENGTH = 1; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* The maximum length of the geo hash. |
|
35
|
|
|
* |
|
36
|
|
|
* @var integer |
|
37
|
|
|
*/ |
|
38
|
|
|
const MAX_LENGTH = 12; |
|
39
|
|
|
|
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* The geo hash. |
|
43
|
|
|
* |
|
44
|
|
|
* @var string |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $geohash; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* The interval of latitudes in degrees. |
|
50
|
|
|
* |
|
51
|
|
|
* @var array |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $latitudeInterval = array(-90.0, 90.0); |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* The interval of longitudes in degrees. |
|
57
|
|
|
* |
|
58
|
|
|
* @var array |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $longitudeInterval = array(-180.0, 180.0); |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* The interval of bits. |
|
64
|
|
|
* |
|
65
|
|
|
* @var array |
|
66
|
|
|
*/ |
|
67
|
|
|
protected $bits = array(16, 8, 4, 2, 1); |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* The array of chars in base 32. |
|
71
|
|
|
* |
|
72
|
|
|
* @var array |
|
73
|
|
|
*/ |
|
74
|
|
|
protected $base32Chars = array( |
|
75
|
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', |
|
76
|
|
|
'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' |
|
77
|
|
|
); |
|
78
|
|
|
|
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* Returns the geo hash. |
|
82
|
|
|
* |
|
83
|
|
|
* @return string |
|
84
|
|
|
*/ |
|
85
|
5 |
|
public function getGeohash() |
|
86
|
|
|
{ |
|
87
|
5 |
|
return $this->geohash; |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
/** |
|
91
|
|
|
* Returns the decoded coordinate (The center of the bounding box). |
|
92
|
|
|
* |
|
93
|
|
|
* @return CoordinateInterface |
|
94
|
|
|
*/ |
|
95
|
5 |
|
public function getCoordinate() |
|
96
|
|
|
{ |
|
97
|
5 |
|
return new Coordinate(array( |
|
98
|
5 |
|
($this->latitudeInterval[0] + $this->latitudeInterval[1]) / 2, |
|
99
|
5 |
|
($this->longitudeInterval[0] + $this->longitudeInterval[1]) / 2 |
|
100
|
|
|
)); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* Returns the bounding box which is an array of coordinates (SouthWest & NorthEast). |
|
105
|
|
|
* |
|
106
|
|
|
* @return CoordinateInterface[] |
|
107
|
|
|
*/ |
|
108
|
6 |
|
public function getBoundingBox() |
|
109
|
|
|
{ |
|
110
|
|
|
return array( |
|
111
|
6 |
|
new Coordinate(array( |
|
112
|
6 |
|
$this->latitudeInterval[0], |
|
113
|
6 |
|
$this->longitudeInterval[0] |
|
114
|
|
|
)), |
|
115
|
6 |
|
new Coordinate(array( |
|
116
|
6 |
|
$this->latitudeInterval[1], |
|
117
|
6 |
|
$this->longitudeInterval[1] |
|
118
|
|
|
)) |
|
119
|
|
|
); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* {@inheritDoc} |
|
124
|
|
|
* |
|
125
|
|
|
* @see http://en.wikipedia.org/wiki/Geohash |
|
126
|
|
|
* @see http://geohash.org/ |
|
127
|
|
|
*/ |
|
128
|
19 |
|
public function encode(CoordinateInterface $coordinate, $length = self::MAX_LENGTH) |
|
129
|
|
|
{ |
|
130
|
19 |
|
if ((int) $length < self::MIN_LENGTH || (int) $length > self::MAX_LENGTH) { |
|
131
|
10 |
|
throw new InvalidArgumentException('The length should be between 1 and 12.'); |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
9 |
|
$latitudeInterval = $this->latitudeInterval; |
|
135
|
9 |
|
$longitudeInterval = $this->longitudeInterval; |
|
136
|
9 |
|
$isEven = true; |
|
137
|
9 |
|
$bit = 0; |
|
138
|
9 |
|
$charIndex = 0; |
|
139
|
|
|
|
|
140
|
9 |
|
while (strlen($this->geohash) < $length) { |
|
141
|
9 |
|
if ($isEven) { |
|
142
|
9 |
|
$middle = ($longitudeInterval[0] + $longitudeInterval[1]) / 2; |
|
143
|
9 |
|
if ($coordinate->getLongitude() > $middle) { |
|
144
|
8 |
|
$charIndex |= $this->bits[$bit]; |
|
145
|
8 |
|
$longitudeInterval[0] = $middle; |
|
146
|
|
|
} else { |
|
147
|
9 |
|
$longitudeInterval[1] = $middle; |
|
148
|
|
|
} |
|
149
|
|
|
} else { |
|
150
|
9 |
|
$middle = ($latitudeInterval[0] + $latitudeInterval[1]) / 2; |
|
151
|
9 |
|
if ($coordinate->getLatitude() > $middle) { |
|
152
|
8 |
|
$charIndex |= $this->bits[$bit]; |
|
153
|
8 |
|
$latitudeInterval[0] = $middle; |
|
154
|
|
|
} else { |
|
155
|
9 |
|
$latitudeInterval[1] = $middle; |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
9 |
|
if ($bit < 4) { |
|
160
|
9 |
|
$bit++; |
|
161
|
|
|
} else { |
|
162
|
9 |
|
$this->geohash = $this->geohash . $this->base32Chars[$charIndex]; |
|
163
|
9 |
|
$bit = 0; |
|
164
|
9 |
|
$charIndex = 0; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
9 |
|
$isEven = $isEven ? false : true; |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
9 |
|
$this->latitudeInterval = $latitudeInterval; |
|
171
|
9 |
|
$this->longitudeInterval = $longitudeInterval; |
|
172
|
|
|
|
|
173
|
9 |
|
return $this; |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* {@inheritDoc} |
|
178
|
|
|
*/ |
|
179
|
25 |
|
public function decode($geohash) |
|
180
|
|
|
{ |
|
181
|
25 |
|
if (!is_string($geohash)) { |
|
182
|
4 |
|
throw new InvalidArgumentException('The geo hash should be a string.'); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
21 |
|
if (strlen($geohash) < self::MIN_LENGTH || strlen($geohash) > self::MAX_LENGTH) { |
|
186
|
4 |
|
throw new InvalidArgumentException('The length of the geo hash should be between 1 and 12.'); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
17 |
|
$base32DecodeMap = array(); |
|
190
|
17 |
|
$base32CharsTotal = count($this->base32Chars); |
|
191
|
17 |
|
for ($i = 0; $i < $base32CharsTotal; $i++) { |
|
192
|
17 |
|
$base32DecodeMap[$this->base32Chars[$i]] = $i; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
17 |
|
$latitudeInterval = $this->latitudeInterval; |
|
196
|
17 |
|
$longitudeInterval = $this->longitudeInterval; |
|
197
|
17 |
|
$isEven = true; |
|
198
|
|
|
|
|
199
|
17 |
|
$geohashLength = strlen($geohash); |
|
200
|
17 |
|
for ($i = 0; $i < $geohashLength; $i++) { |
|
201
|
|
|
|
|
202
|
17 |
|
if (!isset($base32DecodeMap[$geohash[$i]])) { |
|
203
|
8 |
|
throw new RuntimeException('This geo hash is invalid.'); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
10 |
|
$currentChar = $base32DecodeMap[$geohash[$i]]; |
|
207
|
|
|
|
|
208
|
10 |
|
$bitsTotal = count($this->bits); |
|
209
|
10 |
|
for ($j = 0; $j < $bitsTotal; $j++) { |
|
210
|
10 |
|
$mask = $this->bits[$j]; |
|
211
|
|
|
|
|
212
|
10 |
|
if ($isEven) { |
|
213
|
10 |
|
if (($currentChar & $mask) !== 0) { |
|
214
|
10 |
|
$longitudeInterval[0] = ($longitudeInterval[0] + $longitudeInterval[1]) / 2; |
|
215
|
|
|
} else { |
|
216
|
10 |
|
$longitudeInterval[1] = ($longitudeInterval[0] + $longitudeInterval[1]) / 2; |
|
217
|
|
|
} |
|
218
|
|
|
} else { |
|
219
|
10 |
|
if (($currentChar & $mask) !== 0) { |
|
220
|
10 |
|
$latitudeInterval[0] = ($latitudeInterval[0] + $latitudeInterval[1]) / 2; |
|
221
|
|
|
} else { |
|
222
|
9 |
|
$latitudeInterval[1] = ($latitudeInterval[0] + $latitudeInterval[1]) / 2; |
|
223
|
|
|
} |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
10 |
|
$isEven = $isEven ? false : true; |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
9 |
|
$this->latitudeInterval = $latitudeInterval; |
|
231
|
9 |
|
$this->longitudeInterval = $longitudeInterval; |
|
232
|
|
|
|
|
233
|
9 |
|
return $this; |
|
234
|
|
|
} |
|
235
|
|
|
} |
|
236
|
|
|
|