Skip to content

Commit

Permalink
Add what3words as a supported provider (#42)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vicchi authored and Nyholm committed Oct 11, 2016
1 parent a0c0764 commit b2bebef
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Expand Up @@ -16,6 +16,7 @@
<!-- <server name="GEOCODIO_API_KEY" value="YOUR_API_KEY" /> -->
<!-- <server name="HERE_APP_ID" value="YOUR_APP_ID" /> -->
<!-- <server name="HERE_APP_CODE" value="YOUR_APP_CODE" /> -->
<!-- <server name="WHAT3WORDS_API_KEY" value="YOUR_API_KEY" /> -->
</php>
<testsuites>
<testsuite name="geocoder-extra Test Suite">
Expand Down
84 changes: 84 additions & 0 deletions src/Geocoder/Provider/What3wordsProvider.php
@@ -0,0 +1,84 @@
<?php

namespace Geocoder\Provider;

use Geocoder\Exception\InvalidCredentials as InvalidCredentialsException;
use Geocoder\Exception\NoResult as NoResultException;
use Geocoder\Exception\UnsupportedOperation as UnsupportedException;

use Ivory\HttpAdapter\HttpAdapterInterface;

class What3wordsProvider extends AbstractHttpProvider implements Provider
{
const FORWARD_URL = 'https://api.what3words.com/v2/forward?addr=%s&key=%s';
const REVERSE_URL = 'https://api.what3words.com/v2/reverse?coords=%F,%F&key=%s';

private $apiKey = null;

public function __construct(HttpAdapterInterface $adapter, $apiKey)
{
parent::__construct($adapter);

$this->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;
}
}

?>
2 changes: 1 addition & 1 deletion tests/Geocoder/Tests/Provider/GeocoderCaProviderTest.php
Expand Up @@ -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']);
Expand Down
111 changes: 111 additions & 0 deletions tests/Geocoder/Tests/Provider/What3wordsProviderTest.php
@@ -0,0 +1,111 @@
<?php

namespace Geocoder\Tests\Provider;

use Geocoder\Tests\TestCase;
use Geocoder\Provider\What3wordsProvider;

class What3wordsProviderTest extends TestCase
{
public function testGetName()
{
$provider = new What3wordsProvider($this->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']);
}
}

0 comments on commit b2bebef

Please sign in to comment.