From 9cd7e0596d0c4b822f2ec26291285c4bfd149764 Mon Sep 17 00:00:00 2001 From: Chris Raastad Date: Sat, 24 Aug 2019 08:28:08 -0400 Subject: [PATCH] Add getNeighbors function to compute 8 neighbor hashes of a hash. Upgrade PHP version. Update README. (#4) --- README.md | 16 ++++++++ composer.json | 9 +++- src/Geohash.php | 95 +++++++++++++++++++++++++++++++++++++++++++ tests/GeohashTest.php | 23 ++++++++++- 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c145027..c8c9760 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,22 @@ The result is latitude : 17.38, longitude : 78.42 ~~~ +Get geohashes of 8 neighbors of a geohash +~~~ +use Sk\Geohash\Geohash; + +$g = new Geohash(); +$hash = $g->encode(25.813646, -80.133761, 7); +$neighbors = $g->getNeighbors($hash); +echo "Hash: $hash\n"; +echo "Neighbors: " . json_encode($neighors) . "\n"; +~~~ +The result is +~~~ +Hash: +Neighbors: {"North":"dhx4be2","East":"dhx4be1","South":"dhx4bdb","West":"dhx4b7p","NorthEast":"dhx4be3","SouthEast":"dhx4bdc","SouthWest":"dhx4b6z","NorthWest":"dhx4b7r"} +~~~ + Running the unit tests ------- Go to this directory from your project folder diff --git a/composer.json b/composer.json index 16eb233..72b2b43 100644 --- a/composer.json +++ b/composer.json @@ -8,11 +8,16 @@ { "name": "Saikiran Ch", "email": "saikiranchavan@gmail.com" + },{ + "name": "Chris Raastad", + "email": "craastad@gmail.com" } ], - "require": {}, + "require": { + "php": ">=7.0.0" + }, "require-dev": { - "phpunit/phpunit": "4.0.*" + "phpunit/phpunit": "~6.0" }, "autoload": { "psr-4": { diff --git a/src/Geohash.php b/src/Geohash.php index b142b0d..19dc08f 100644 --- a/src/Geohash.php +++ b/src/Geohash.php @@ -9,11 +9,53 @@ */ class Geohash { + const NORTH = 0; + const EAST = 1; + const SOUTH = 2; + const WEST = 3; + + const EVEN = 0; + const ODD = 1; + /** * Used for decoding the hash from base32 */ protected $base32Mapping = "0123456789bcdefghjkmnpqrstuvwxyz"; + private $borderChars = [ + self::EVEN => [ + self::NORTH => 'bcfguvyz', + self::EAST => 'prxz', + self::SOUTH => '0145hjnp', + self::WEST => '028b', + ] + ]; + + private $neighborChars = [ + self::EVEN => [ + self::NORTH => '238967debc01fg45kmstqrwxuvhjyznp', + self::EAST => '14365h7k9dcfesgujnmqp0r2twvyx8zb', + self::SOUTH => 'bc01fg45238967deuvhjyznpkmstqrwx', + self::WEST => 'p0r21436x8zb9dcf5h7kjnmqesgutwvy', + ], + ]; + + public function __construct() { + $this->neighborChars[self::ODD] = array( + self::NORTH => $this->neighborChars[self::EVEN][self::EAST], + self::EAST => $this->neighborChars[self::EVEN][self::NORTH], + self::SOUTH => $this->neighborChars[self::EVEN][self::WEST], + self::WEST => $this->neighborChars[self::EVEN][self::SOUTH], + ); + + $this->borderChars[self::ODD] = array( + self::NORTH => $this->borderChars[self::EVEN][self::EAST], + self::EAST => $this->borderChars[self::EVEN][self::NORTH], + self::SOUTH => $this->borderChars[self::EVEN][self::WEST], + self::WEST => $this->borderChars[self::EVEN][self::SOUTH], + ); + } + /** * Encode the latitude and longitude into a hashed string * @param float Latitude @@ -145,4 +187,57 @@ public function getBits($coordinate, $min, $max, $bitsLength) } return $binaryString; } + + /** + * Computes neighboring geohash values for given geohash. + * + * @param string $hash + * @return array + */ + public function getNeighbors($hash) { + $hashNorth = $this->calculateNeighbor($hash, self::NORTH); + $hashEast = $this->calculateNeighbor($hash, self::EAST); + $hashSouth = $this->calculateNeighbor($hash, self::SOUTH); + $hashWest = $this->calculateNeighbor($hash, self::WEST); + + $hashNorthEast = $this->calculateNeighbor($hashNorth, self::EAST); + $hashSouthEast = $this->calculateNeighbor($hashSouth, self::EAST); + $hashSouthWest = $this->calculateNeighbor($hashSouth, self::WEST); + $hashNorthWest = $this->calculateNeighbor($hashNorth, self::WEST); + return [ + 'North' => $hashNorth, + 'East' => $hashEast, + 'South' => $hashSouth, + 'West' => $hashWest, + 'NorthEast' => $hashNorthEast, + 'SouthEast' => $hashSouthEast, + 'SouthWest' => $hashSouthWest, + 'NorthWest' => $hashNorthWest, + ]; + } + + /** + * Calculates neighbor geohash for given geohash and direction + * + * @param string $hash + * @param string $direction + * @return string $neighborHash + */ + private function calculateNeighbor($hash, $direction) { + $length = strlen($hash); + if ($length == 0) { + return ''; + } + $lastChar = $hash{$length - 1}; + $evenOrOdd = ($length - 1) % 2; + $baseHash = substr($hash, 0, -1); + if (strpos($this->borderChars[$evenOrOdd][$direction], $lastChar) !== false) { + $baseHash = $this->calculateNeighbor($baseHash, $direction); + } + if (isset($baseHash{0})) { + return $baseHash . $this->neighborChars[$evenOrOdd][$direction]{strpos($this->base32Mapping, $lastChar)}; + } else { + return ''; + } + } } \ No newline at end of file diff --git a/tests/GeohashTest.php b/tests/GeohashTest.php index 5313f34..15c00c1 100644 --- a/tests/GeohashTest.php +++ b/tests/GeohashTest.php @@ -1,8 +1,9 @@ encode($lat1, $lon1, 7); + $neighbors = $geohash->getNeighbors($hash); + $this->assertEquals('dhx4be0', $hash); + $this->assertEquals([ + 'North' => 'dhx4be2', + 'East'=> 'dhx4be1', + 'South' => 'dhx4bdb', + 'West' => 'dhx4b7p', + 'NorthEast' => 'dhx4be3', + 'SouthEast' => 'dhx4bdc', + 'SouthWest' => 'dhx4b6z', + 'NorthWest' => 'dhx4b7r', + ], $neighbors); + } } \ No newline at end of file