From b2bebef007927c5c2f70e17ae70395d0f97aa8c1 Mon Sep 17 00:00:00 2001 From: Gary Gale Date: Tue, 11 Oct 2016 14:39:26 +0100 Subject: [PATCH] Add what3words as a supported provider (#42) * Fix unit tests for changes on Geocoder.ca data (again) * Add what3words as a provider Calls to error_log begone; that was a stupid oversight on my part * Fix table structure for ip2c provider in README.md * Add check for invalid/missing response from the geocoder endpoint --- README.md | 3 +- phpunit.xml.dist | 1 + src/Geocoder/Provider/What3wordsProvider.php | 84 +++++++++++++ .../Tests/Provider/GeocoderCaProviderTest.php | 2 +- .../Tests/Provider/What3wordsProviderTest.php | 111 ++++++++++++++++++ 5 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/Geocoder/Provider/What3wordsProvider.php create mode 100644 tests/Geocoder/Tests/Provider/What3wordsProviderTest.php diff --git a/README.md b/README.md index ebe2742..50fcf48 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ Please, read the [Geocoder's documentation](http://geocoder-php.org/Geocoder/). | [GeoCoder.us](http://geocoder.us/) | yes | no | no | no | no | USA | Free throttled service. $50 USD for 20000 requests for paid service | | [OIORest](http://geo.oiorest.dk/) | yes | no | no | yes | no | Denmark | | | [IGN OpenLS](http://api.ign.fr/accueil) | yes | no | no | no | no | France | API key required | -| [ip2c](http://about.ip2c.org/) | no | yes | no | no | no | | +| [what3words](https://docs.what3words.com/api/v2/) | yes (3 word address only) | no | no | yes | yes | Global | API key required | +| [ip2c](http://about.ip2c.org/) | no | yes | no | no | no | | | | Contributing diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 48651d1..7c96ffe 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,7 @@ + diff --git a/src/Geocoder/Provider/What3wordsProvider.php b/src/Geocoder/Provider/What3wordsProvider.php new file mode 100644 index 0000000..6600fcd --- /dev/null +++ b/src/Geocoder/Provider/What3wordsProvider.php @@ -0,0 +1,84 @@ +apiKey = $apiKey; + } + + public function getName() + { + return 'what3words'; + } + + public function geocode($value) + { + if (filter_var($value, FILTER_VALIDATE_IP)) { + throw new UnsupportedException('The what3words provider does not support IP addresses.'); + } + + if (null == $this->apiKey) { + throw new InvalidCredentialsException('No what3words API key provided.'); + } + + $query = sprintf(self::FORWARD_URL, urlencode($value), $this->apiKey); + return $this->executeQuery($query); + } + + public function reverse($latitude, $longitude) + { + if (null == $this->apiKey) { + throw new InvalidCredentialsException('No what3word API key provided.'); + } + + $query = sprintf(self::REVERSE_URL, $latitude, $longitude, $this->apiKey); + + return $this->executeQuery($query); + } + + protected function executeQuery($query) + { + $content = $this->getAdapter()->get($query)->getBody(); + + if (null === $data = json_decode($content, true)) { + throw new NoResultException(sprintf('Could not execute query: %s', $query)); + } + + if (!isset($data['status']) && isset($data['code']) && 2 === $data['code']) { + throw new InvalidCredentialsException(sprintf('Invalid credentials: %s', $data['message'])); + } + + $results = []; + $results[] = array_merge($this->getDefaults(), [ + 'latitude' => $data['geometry']['lat'], + 'longitude' => $data['geometry']['lng'], + 'bounds' => [ + 'south' => $data['bounds']['southwest']['lat'], + 'west' => $data['bounds']['southwest']['lng'], + 'north' => $data['bounds']['northeast']['lat'], + 'east' => $data['bounds']['northeast']['lng'] + ], + 'locality' => $data['words'] + ]); + + return $results; + } +} + +?> diff --git a/tests/Geocoder/Tests/Provider/GeocoderCaProviderTest.php b/tests/Geocoder/Tests/Provider/GeocoderCaProviderTest.php index e09d847..f2f1fb7 100644 --- a/tests/Geocoder/Tests/Provider/GeocoderCaProviderTest.php +++ b/tests/Geocoder/Tests/Provider/GeocoderCaProviderTest.php @@ -206,7 +206,7 @@ public function testGetReversedDataWithRealCoordinates() $this->assertInternalType('array', $result); $this->assertEquals(40.707507, $result['latitude'], '', 0.0001); $this->assertEquals(-74.011255, $result['longitude'], '', 0.0001); - $this->assertEquals('2', $result['streetNumber']); + $this->assertEquals('1', $result['streetNumber']); $this->assertEquals('New ST', $result['streetName']); $this->assertEquals(10005, $result['zipcode']); $this->assertEquals('NEW YORK', $result['city']); diff --git a/tests/Geocoder/Tests/Provider/What3wordsProviderTest.php b/tests/Geocoder/Tests/Provider/What3wordsProviderTest.php new file mode 100644 index 0000000..ca71ded --- /dev/null +++ b/tests/Geocoder/Tests/Provider/What3wordsProviderTest.php @@ -0,0 +1,111 @@ +getMockAdapter($this->never()), 'api-key'); + $this->assertEquals('what3words', $provider->getName()); + } + + /** + * @expectedException \RuntimeException + */ + public function testGeocodeWithNullApiKey() + { + $provider = new What3wordsProvider($this->getMockAdapter($this->never()), null); + $provider->geocode('foo'); + } + + /** + * @expectedException \Geocoder\Exception\NoResult + */ + public function testGeocodeWithAddress() + { + $provider = new What3wordsProvider($this->getMockAdapter(), 'api-key'); + $provider->geocode('bar'); + } + + /** + * @expectedException \Geocoder\Exception\InvalidCredentials + */ + public function testGetInvalidCredentials() + { + $json = '{"code":2,"message":"Authentication failed; invalid API key"}'; + + $provider = new What3wordsProvider($this->getMockAdapterReturns($json), 'api-key'); + $results = $provider->geocode('baz'); + } + + /** + * @expectedException \Geocoder\Exception\NoResult + * @expectedExceptionMessage Could not execute query: https://api.what3words.com/v2/forward?addr=foobar&key=api_key + */ + public function testGeocode() + { + $provider = new What3wordsProvider($this->getMockAdapterReturns('{}'), 'api_key'); + $provider->geocode('foobar'); + } + + public function testGeocodeWithRealAddress() + { + if (!isset($_SERVER['WHAT3WORDS_API_KEY'])) { + $this->markTestSkipped('You need to configure the WHAT3WORDS_API_KEY value in phpunit.xml'); + } + + $provider = new What3wordsProvider($this->getAdapter(), $_SERVER['WHAT3WORDS_API_KEY']); + $results = $provider->geocode('index.home.raft'); + + $this->assertInternalType('array', $results); + $this->assertCount(1, $results); + + $result = $results[0]; + $this->assertEquals(51.521250999999999, $result['latitude'], 0.01); + $this->assertEquals(-0.20358599999999999, $result['longitude'], 0.01); + $this->assertEquals(51.521237999999997, $result['bounds']['south'], '', 0.01); + $this->assertEquals(-0.20360700000000001, $result['bounds']['west'], '', 0.01); + $this->assertEquals(51.521265, $result['bounds']['north'], '', 0.01); + $this->assertEquals(-0.20356399999999999, $result['bounds']['east'], '', 0.01); + $this->assertSame('index.home.raft', $result['locality']); + } + + /** + * @expectedException \Geocoder\Exception\NoResult + */ + public function testReverse() + { + if (!isset($_SERVER['WHAT3WORDS_API_KEY'])) { + $this->markTestSkipped('You need to configure the WHAT3WORDS_API_KEY value in phpunit.xml'); + } + + $provider = new What3wordsProvider($this->getMockAdapter(), $_SERVER['WHAT3WORDS_API_KEY']); + $provider->reverse(1, 2); + } + + public function testReverseWithRealCoordinates() + { + if (!isset($_SERVER['WHAT3WORDS_API_KEY'])) { + $this->markTestSkipped('You need to configure the WHAT3WORDS_API_KEY value in phpunit.xml'); + } + + $provider = new What3wordsProvider($this->getAdapter(), $_SERVER['WHAT3WORDS_API_KEY']); + $results = $provider->reverse(51.426787, -0.331321); + + $this->assertInternalType('array', $results); + $this->assertCount(1, $results); + + $result = $results[0]; + $this->assertEquals(51.426786999999997, $result['latitude'], 0.01); + $this->assertEquals(-0.33132099999999998, $result['longitude'], 0.01); + $this->assertEquals(51.426772999999997, $result['bounds']['south'], '', 0.01); + $this->assertEquals(-0.331343, $result['bounds']['west'], '', 0.01); + $this->assertEquals(51.4268, $result['bounds']['north'], '', 0.01); + $this->assertEquals(-0.33129999999999998, $result['bounds']['east'], '', 0.01); + $this->assertSame('spoken.land.complains', $result['locality']); + } +}