diff --git a/lib/adapters/GeoHash.class.php b/lib/adapters/GeoHash.class.php index d8a2958f..57f1a3dd 100644 --- a/lib/adapters/GeoHash.class.php +++ b/lib/adapters/GeoHash.class.php @@ -7,8 +7,64 @@ * */ class GeoHash extends GeoAdapter{ + + /** + * base32 encoding character map. + */ private $table = "0123456789bcdefghjkmnpqrstuvwxyz"; + /** + * array of neighbouring hash character maps. + */ + private $neighbours = array ( + // north + 'top' => array ( + 'even' => 'p0r21436x8zb9dcf5h7kjnmqesgutwvy', + 'odd' => 'bc01fg45238967deuvhjyznpkmstqrwx' + ), + // east + 'right' => array ( + 'even' => 'bc01fg45238967deuvhjyznpkmstqrwx', + 'odd' => 'p0r21436x8zb9dcf5h7kjnmqesgutwvy' + ), + // west + 'left' => array ( + 'even' => '238967debc01fg45kmstqrwxuvhjyznp', + 'odd' => '14365h7k9dcfesgujnmqp0r2twvyx8zb' + ), + // south + 'bottom' => array ( + 'even' => '14365h7k9dcfesgujnmqp0r2twvyx8zb', + 'odd' => '238967debc01fg45kmstqrwxuvhjyznp' + ) + ); + + /** + * array of bordering hash character maps. + */ + private $borders = array ( + // north + 'top' => array ( + 'even' => 'prxz', + 'odd' => 'bcfguvyz' + ), + // east + 'right' => array ( + 'even' => 'bcfguvyz', + 'odd' => 'prxz' + ), + // west + 'left' => array ( + 'even' => '0145hjnp', + 'odd' => '028b' + ), + // south + 'bottom' => array ( + 'even' => '028b', + 'odd' => '0145hjnp' + ) + ); + /** * Convert the geohash to a Point. The point is 2-dimensional. * @return Point the converted geohash @@ -163,4 +219,31 @@ private function decode($hash){ $ll['medlon'] = round(($minlon+$maxlon)/2, max(1, -round(log10($lonE)))-1); return $ll; } + + /** + * Calculates the adjacent geohash of the geohash in the specified direction. + * This algorithm is available in various ports that seem to point back to + * geohash-js by David Troy under MIT notice. + * + * + * @see https://github.com/davetroy/geohash-js + * @see https://github.com/lyokato/objc-geohash + * @see https://github.com/lyokato/libgeohash + * @see https://github.com/masuidrive/pr_geohash + * @see https://github.com/sunng87/node-geohash + * @see https://github.com/davidmoten/geo + * + * @param string $hash the geohash (lowercase) + * @param string $direction the direction of the neighbor (top, bottom, left or right) + * @return string the geohash of the adjacent cell + */ + public function adjacent($hash, $direction){ + $last = substr($hash, -1); + $type = (strlen($hash) % 2)? 'odd': 'even'; + $base = substr($hash, 0, strlen($hash) - 1); + if(strpos(($this->borders[$direction][$type]), $last) !== false){ + $base = $this->adjacent($base, $direction); + } + return $base.$this->table[strpos($this->neighbours[$direction][$type], $last)]; + } } diff --git a/tests/tests/geohashTest.php b/tests/tests/geohashTest.php new file mode 100644 index 00000000..19949082 --- /dev/null +++ b/tests/tests/geohashTest.php @@ -0,0 +1,20 @@ +assertEquals ( 'xne', $geohash->adjacent ( 'xn7', 'top' ), 'Did not find correct top adjacent geohash for xn7' ); + $this->assertEquals ( 'xnk', $geohash->adjacent ( 'xn7', 'right' ), 'Did not find correct right adjacent geohash for xn7' ); + $this->assertEquals ( 'xn5', $geohash->adjacent ( 'xn7', 'bottom' ), 'Did not find correct bottom adjacent geohash for xn7' ); + $this->assertEquals ( 'xn6', $geohash->adjacent ( 'xn7', 'left' ), 'Did not find correct left adjacent geohash for xn7' ); + $this->assertEquals ( 'xnd', $geohash->adjacent ( $geohash->adjacent ( 'xn7', 'left' ), 'top' ), 'Did not find correct top-left adjacent geohash for xn7' ); + } +}