Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 86 additions & 8 deletions src/Provider/ArcGISOnline/ArcGISOnline.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Geocoder\Collection;
use Geocoder\Exception\InvalidArgument;
use Geocoder\Exception\InvalidCredentials;
use Geocoder\Exception\InvalidServerResponse;
use Geocoder\Exception\UnsupportedOperation;
use Geocoder\Model\Address;
Expand All @@ -32,7 +33,12 @@ final class ArcGISOnline extends AbstractHttpProvider implements Provider
/**
* @var string
*/
const ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find?text=%s';
const ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?SingleLine=%s';

/**
* @var string
*/
const TOKEN_ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/geocodeAddresses?token=%s&addresses=%s';

/**
* @var string
Expand All @@ -45,14 +51,45 @@ final class ArcGISOnline extends AbstractHttpProvider implements Provider
private $sourceCountry;

/**
* @var string
*
* Currently valid ArcGIS World Geocoding Service token.
* https://developers.arcgis.com/rest/geocode/api-reference/geocoding-authenticate-a-request.htm
*/
private $token;

/**
* ArcGIS World Geocoding Service.
* https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm.
*
* @param HttpClient $client An HTTP adapter
* @param string $token Your authentication token
* @param string $sourceCountry Country biasing (optional)
*
* @return ArcGISOnline
*/
public function __construct(HttpClient $client, string $sourceCountry = null)
public static function token(
HttpClient $client,
string $token,
string $sourceCountry = null
) {
$provider = new self($client, $sourceCountry, $token);

return $provider;
}

/**
* @param HttpClient $client An HTTP adapter
* @param string $sourceCountry Country biasing (optional)
* @param string $token ArcGIS World Geocoding Service token
* Required for the geocodeAddresses endpoint
*/
public function __construct(HttpClient $client, string $sourceCountry = null, string $token = null)
{
parent::__construct($client);

$this->sourceCountry = $sourceCountry;
$this->token = $token;
}

/**
Expand All @@ -70,19 +107,25 @@ public function geocodeQuery(GeocodeQuery $query): Collection
throw new InvalidArgument('Address cannot be empty.');
}

$url = sprintf(self::ENDPOINT_URL, urlencode($address));
if (is_null($this->token)) {
$url = sprintf(self::ENDPOINT_URL, urlencode($address));
} else {
$url = sprintf(self::TOKEN_ENDPOINT_URL, $this->token, urlencode($this->formatAddresses([$address])));
}
$json = $this->executeQuery($url, $query->getLimit());

$property = is_null($this->token) ? 'candidates' : 'locations';

// no result
if (empty($json->locations)) {
if (!property_exists($json, $property) || empty($json->{$property})) {
return new AddressCollection([]);
}

$results = [];
foreach ($json->locations as $location) {
$data = $location->feature->attributes;
foreach ($json->{$property} as $location) {
$data = $location->attributes;

$coordinates = (array) $location->feature->geometry;
$coordinates = (array) $location->location;
$streetName = !empty($data->StAddr) ? $data->StAddr : null;
$streetNumber = !empty($data->AddNum) ? $data->AddNum : null;
$city = !empty($data->City) ? $data->City : null;
Expand Down Expand Up @@ -171,8 +214,11 @@ private function buildQuery(string $query, int $limit): string
if (null !== $this->sourceCountry) {
$query = sprintf('%s&sourceCountry=%s', $query, $this->sourceCountry);
}
if (is_null($this->token)) {
$query = sprintf('%s&maxLocations=%d&outFields=*', $query, $limit);
}

return sprintf('%s&maxLocations=%d&f=%s&outFields=*', $query, $limit, 'json');
return sprintf('%s&f=%s', $query, 'json');
}

/**
Expand All @@ -191,7 +237,39 @@ private function executeQuery(string $url, int $limit): \stdClass
if (!isset($json)) {
throw InvalidServerResponse::create($url);
}
if (property_exists($json, 'error') && property_exists($json->error, 'message')) {
if ('Invalid Token' == $json->error->message) {
throw new InvalidCredentials(sprintf('Invalid token %s', $this->token));
}
}

return $json;
}

/**
* Formatter for 1..n addresses, for the geocodeAddresses endpoint.
*
* @param array $array an array of SingleLine addresses
*
* @return string an Array formatted as a JSON string
*/
private function formatAddresses(array $array): string
{
// Just in case, get rid of any custom, non-numeric indices.
$array = array_values($array);

$addresses = [
'records' => [],
];
foreach ($array as $i => $address) {
$addresses['records'][] = [
'attributes' => [
'OBJECTID' => $i + 1,
'SingleLine' => $address,
],
];
}

return json_encode($addresses);
}
}
33 changes: 31 additions & 2 deletions src/Provider/ArcGISOnline/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,36 @@
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)

This is the ArcGIS provider from the PHP Geocoder. This is a **READ ONLY** repository. See the
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.

## Usage

```php
$httpClient = new \Http\Adapter\Guzzle6\Client();

$provider = new \Geocoder\Provider\ArcGISList\ArcGISList($httpClient);

$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
```

### Storing results

ArcGIS prohibits storing the results of geocoding transactions without providing
a valid ArcGIS Online token, which requires
[ArcGIS Online credentials](https://developers.arcgis.com/rest/geocode/api-reference/geocoding-authenticate-a-request.htm).

You can use the static `token` method on the provider to create a client which
uses your valid ArcGIS Online token:

```php

$httpClient = new \Http\Adapter\Guzzle6\Client();

// Client ID is required. Private key is optional.
$provider = \Geocoder\Provider\ArcGISList\ArcGISList::token($httpClient, 'your-token');

$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
```

### Install

Expand All @@ -26,5 +55,5 @@ geocoding).

### Contribute

Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
report any issues you find on the [issue tracker](https://github.com/geocoder-php/Geocoder/issues).
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:653:"{"address":{"Match_addr":"3-7 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"3-7 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"3-7 Avenue Gambetta","Addr_type":"StreetAddress","Type":"","PlaceName":"","AddNum":"5","Address":"5 Avenue Gambetta","Block":"","Sector":"","Neighborhood":"Amandiers","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","Territory":"","Postal":"75020","PostalExt":"","CountryCode":"FRA"},"location":{"x":2.3890068200591377,"y":48.863333541352212,"spatialReference":{"wkid":4326,"latestWkid":4326}}}";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:1320:"{"spatialReference":{"wkid":4326,"latestWkid":4326},"locations":[{"address":"5754 WI-23, Spring Green, Wisconsin, 53588","location":{"x":-90.13179576999994,"y":43.093662879000078},"score":100,"attributes":{"ResultID":1,"Loc_name":"World","Status":"M","Score":100,"Match_addr":"5754 WI-23, Spring Green, Wisconsin, 53588","LongLabel":"5754 WI-23, Spring Green, WI, 53588, USA","ShortLabel":"5754 WI-23","Addr_type":"StreetAddress","Type":"","PlaceName":"","Place_addr":"5754 WI-23, Spring Green, Wisconsin, 53588","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"5754","AddNumFrom":"5652","AddNumTo":"5754","AddRange":"5652-5754","Side":"L","StPreDir":"","StPreType":"","StName":"WI-23","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"5754 WI-23","Block":"","Sector":"","Nbrhd":"","District":"","City":"Spring Green","MetroArea":"","Subregion":"Iowa County","Region":"Wisconsin","RegionAbbr":"WI","Territory":"","Zone":"","Postal":"53588","PostalExt":"8912","Country":"USA","LangCode":"ENG","Distance":0,"X":-90.131795769692417,"Y":43.093662878656403,"DisplayX":-90.131795769692417,"DisplayY":43.093662878656403,"Xmin":-90.132795769692422,"Xmax":-90.130795769692412,"Ymin":43.092662878656405,"Ymax":43.094662878656401,"ExInfo":""}}]}";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:7589:"{"spatialReference":{"wkid":4326,"latestWkid":4326},"candidates":[{"address":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","location":{"x":2.3890699736723207,"y":48.863180009118821},"score":100,"attributes":{"Loc_name":"World","Status":"T","Score":100,"Match_addr":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"10 Avenue Gambetta","Addr_type":"PointAddress","Type":"","PlaceName":"","Place_addr":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"10","AddNumFrom":"","AddNumTo":"","AddRange":"","Side":"R","StPreDir":"","StPreType":"Avenue","StName":"Gambetta","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"10 Avenue Gambetta","Block":"","Sector":"","Nbrhd":"Père Lachaise Réunion","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","RegionAbbr":"IDF","Territory":"","Zone":"","Postal":"75020","PostalExt":"","Country":"FRA","LangCode":"FRE","Distance":0,"X":2.3890166647681372,"Y":48.863242873392622,"DisplayX":2.3890699736723207,"DisplayY":48.863180009118821,"Xmin":2.3880699736723208,"Xmax":2.3900699736723205,"Ymin":48.862180009118823,"Ymax":48.864180009118819,"ExInfo":""},"extent":{"xmin":2.3880699736723208,"ymin":48.862180009118823,"xmax":2.3900699736723205,"ymax":48.864180009118819}},{"address":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","location":{"x":2.3984400194131013,"y":48.865300001979477},"score":100,"attributes":{"Loc_name":"World","Status":"T","Score":100,"Match_addr":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"10 Avenue Gambetta","Addr_type":"PointAddress","Type":"","PlaceName":"","Place_addr":"10 Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"10","AddNumFrom":"","AddNumTo":"","AddRange":"","Side":"L","StPreDir":"","StPreType":"Avenue","StName":"Gambetta","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"10 Avenue Gambetta","Block":"","Sector":"","Nbrhd":"Gambetta","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","RegionAbbr":"IDF","Territory":"","Zone":"","Postal":"75020","PostalExt":"","Country":"FRA","LangCode":"FRE","Distance":0,"X":2.3986787360154822,"Y":48.865183661163428,"DisplayX":2.3984400194131013,"DisplayY":48.865300001979477,"Xmin":2.3974400194131014,"Xmax":2.3994400194131011,"Ymin":48.86430000197948,"Ymax":48.866300001979475,"ExInfo":""},"extent":{"xmin":2.3974400194131014,"ymin":48.86430000197948,"xmax":2.3994400194131011,"ymax":48.866300001979475}},{"address":"10 Place Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","location":{"x":2.3983800049863646,"y":48.865080018930627},"score":97.829999999999998,"attributes":{"Loc_name":"World","Status":"T","Score":97.829999999999998,"Match_addr":"10 Place Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"10 Place Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"10 Place Gambetta","Addr_type":"PointAddress","Type":"","PlaceName":"","Place_addr":"10 Place Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"10","AddNumFrom":"","AddNumTo":"","AddRange":"","Side":"L","StPreDir":"","StPreType":"Place","StName":"Gambetta","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"10 Place Gambetta","Block":"","Sector":"","Nbrhd":"Gambetta","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","RegionAbbr":"IDF","Territory":"","Zone":"","Postal":"75020","PostalExt":"","Country":"FRA","LangCode":"FRE","Distance":0,"X":2.398404563962663,"Y":48.865037271224445,"DisplayX":2.3983800049863646,"DisplayY":48.865080018930627,"Xmin":2.3973800049863647,"Xmax":2.3993800049863645,"Ymin":48.864080018930629,"Ymax":48.866080018930624,"ExInfo":""},"extent":{"xmin":2.3973800049863647,"ymin":48.864080018930629,"xmax":2.3993800049863645,"ymax":48.866080018930624}},{"address":"10 Passage Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","location":{"x":2.4020399630079226,"y":48.872190010208726},"score":97.829999999999998,"attributes":{"Loc_name":"World","Status":"T","Score":97.829999999999998,"Match_addr":"10 Passage Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"10 Passage Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"10 Passage Gambetta","Addr_type":"PointAddress","Type":"","PlaceName":"","Place_addr":"10 Passage Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"10","AddNumFrom":"","AddNumTo":"","AddRange":"","Side":"R","StPreDir":"","StPreType":"Passage","StName":"Gambetta","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"10 Passage Gambetta","Block":"","Sector":"","Nbrhd":"Télégraphe-Pelleport Saint-Fargeau","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","RegionAbbr":"IDF","Territory":"","Zone":"","Postal":"75020","PostalExt":"","Country":"FRA","LangCode":"FRE","Distance":0,"X":2.4020104587087521,"Y":48.872198727388025,"DisplayX":2.4020399630079226,"DisplayY":48.872190010208726,"Xmin":2.4010399630079227,"Xmax":2.4030399630079224,"Ymin":48.871190010208728,"Ymax":48.873190010208724,"ExInfo":""},"extent":{"xmin":2.4010399630079227,"ymin":48.871190010208728,"xmax":2.4030399630079224,"ymax":48.873190010208724}},{"address":"Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","location":{"x":2.391313868877178,"y":48.863858948830256},"score":97.409999999999997,"attributes":{"Loc_name":"World","Status":"T","Score":97.409999999999997,"Match_addr":"Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","LongLabel":"Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France, FRA","ShortLabel":"Avenue Gambetta","Addr_type":"StreetName","Type":"","PlaceName":"","Place_addr":"Avenue Gambetta, 75020, 20e Arrondissement, Paris, Île-de-France","Phone":"","URL":"","Rank":20,"AddBldg":"","AddNum":"","AddNumFrom":"","AddNumTo":"","AddRange":"","Side":"","StPreDir":"","StPreType":"Avenue","StName":"Gambetta","StType":"","StDir":"","BldgType":"","BldgName":"","LevelType":"","LevelName":"","UnitType":"","UnitName":"","SubAddr":"","StAddr":"Avenue Gambetta","Block":"","Sector":"","Nbrhd":"Amandiers","District":"20e Arrondissement","City":"Paris","MetroArea":"","Subregion":"Paris","Region":"Île-de-France","RegionAbbr":"IDF","Territory":"","Zone":"","Postal":"75020","PostalExt":"","Country":"FRA","LangCode":"FRE","Distance":0,"X":2.391313868877178,"Y":48.863858948830256,"DisplayX":2.391313868877178,"DisplayY":48.863858948830256,"Xmin":2.3903138688771781,"Xmax":2.3923138688771779,"Ymin":48.862858948830258,"Ymax":48.864858948830253,"ExInfo":"10"},"extent":{"xmin":2.3903138688771781,"ymin":48.862858948830258,"xmax":2.3923138688771779,"ymax":48.864858948830253}}]}";
Loading