Skip to content

Commit 5913784

Browse files
authored
Updated ArcGISOnline to migrate , add support, and update response handling. (#1090)
* #1089: Updated ArcGISOnline to migrate , add support, and update response handling. * #1089: Updated for StyleCI issues. * #1089: Updated for StyleCI issue with sentence. * #1089: Updated tests with results that ArcGIS returns. Added an exception for reverse on (0,0). * #1089: StyleCI update for (0,0) exception. * #1089: Changed exception to skipped test. * #1089: Updated and added tests for migrated and added functionality. * #1089: StyleCI fix to do yoda assignment. * #1089: Removed old/invalid cached responses.
1 parent c1824fe commit 5913784

22 files changed

+201
-44
lines changed

src/Provider/ArcGISOnline/ArcGISOnline.php

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use Geocoder\Collection;
1616
use Geocoder\Exception\InvalidArgument;
17+
use Geocoder\Exception\InvalidCredentials;
1718
use Geocoder\Exception\InvalidServerResponse;
1819
use Geocoder\Exception\UnsupportedOperation;
1920
use Geocoder\Model\Address;
@@ -32,7 +33,12 @@ final class ArcGISOnline extends AbstractHttpProvider implements Provider
3233
/**
3334
* @var string
3435
*/
35-
const ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find?text=%s';
36+
const ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?SingleLine=%s';
37+
38+
/**
39+
* @var string
40+
*/
41+
const TOKEN_ENDPOINT_URL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/geocodeAddresses?token=%s&addresses=%s';
3642

3743
/**
3844
* @var string
@@ -45,14 +51,45 @@ final class ArcGISOnline extends AbstractHttpProvider implements Provider
4551
private $sourceCountry;
4652

4753
/**
54+
* @var string
55+
*
56+
* Currently valid ArcGIS World Geocoding Service token.
57+
* https://developers.arcgis.com/rest/geocode/api-reference/geocoding-authenticate-a-request.htm
58+
*/
59+
private $token;
60+
61+
/**
62+
* ArcGIS World Geocoding Service.
63+
* https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm.
64+
*
4865
* @param HttpClient $client An HTTP adapter
66+
* @param string $token Your authentication token
4967
* @param string $sourceCountry Country biasing (optional)
68+
*
69+
* @return ArcGISOnline
5070
*/
51-
public function __construct(HttpClient $client, string $sourceCountry = null)
71+
public static function token(
72+
HttpClient $client,
73+
string $token,
74+
string $sourceCountry = null
75+
) {
76+
$provider = new self($client, $sourceCountry, $token);
77+
78+
return $provider;
79+
}
80+
81+
/**
82+
* @param HttpClient $client An HTTP adapter
83+
* @param string $sourceCountry Country biasing (optional)
84+
* @param string $token ArcGIS World Geocoding Service token
85+
* Required for the geocodeAddresses endpoint
86+
*/
87+
public function __construct(HttpClient $client, string $sourceCountry = null, string $token = null)
5288
{
5389
parent::__construct($client);
5490

5591
$this->sourceCountry = $sourceCountry;
92+
$this->token = $token;
5693
}
5794

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

73-
$url = sprintf(self::ENDPOINT_URL, urlencode($address));
110+
if (is_null($this->token)) {
111+
$url = sprintf(self::ENDPOINT_URL, urlencode($address));
112+
} else {
113+
$url = sprintf(self::TOKEN_ENDPOINT_URL, $this->token, urlencode($this->formatAddresses([$address])));
114+
}
74115
$json = $this->executeQuery($url, $query->getLimit());
75116

117+
$property = is_null($this->token) ? 'candidates' : 'locations';
118+
76119
// no result
77-
if (empty($json->locations)) {
120+
if (!property_exists($json, $property) || empty($json->{$property})) {
78121
return new AddressCollection([]);
79122
}
80123

81124
$results = [];
82-
foreach ($json->locations as $location) {
83-
$data = $location->feature->attributes;
125+
foreach ($json->{$property} as $location) {
126+
$data = $location->attributes;
84127

85-
$coordinates = (array) $location->feature->geometry;
128+
$coordinates = (array) $location->location;
86129
$streetName = !empty($data->StAddr) ? $data->StAddr : null;
87130
$streetNumber = !empty($data->AddNum) ? $data->AddNum : null;
88131
$city = !empty($data->City) ? $data->City : null;
@@ -171,8 +214,11 @@ private function buildQuery(string $query, int $limit): string
171214
if (null !== $this->sourceCountry) {
172215
$query = sprintf('%s&sourceCountry=%s', $query, $this->sourceCountry);
173216
}
217+
if (is_null($this->token)) {
218+
$query = sprintf('%s&maxLocations=%d&outFields=*', $query, $limit);
219+
}
174220

175-
return sprintf('%s&maxLocations=%d&f=%s&outFields=*', $query, $limit, 'json');
221+
return sprintf('%s&f=%s', $query, 'json');
176222
}
177223

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

195246
return $json;
196247
}
248+
249+
/**
250+
* Formatter for 1..n addresses, for the geocodeAddresses endpoint.
251+
*
252+
* @param array $array an array of SingleLine addresses
253+
*
254+
* @return string an Array formatted as a JSON string
255+
*/
256+
private function formatAddresses(array $array): string
257+
{
258+
// Just in case, get rid of any custom, non-numeric indices.
259+
$array = array_values($array);
260+
261+
$addresses = [
262+
'records' => [],
263+
];
264+
foreach ($array as $i => $address) {
265+
$addresses['records'][] = [
266+
'attributes' => [
267+
'OBJECTID' => $i + 1,
268+
'SingleLine' => $address,
269+
],
270+
];
271+
}
272+
273+
return json_encode($addresses);
274+
}
197275
}

src/Provider/ArcGISOnline/Readme.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,36 @@
99
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
1010

1111
This is the ArcGIS provider from the PHP Geocoder. This is a **READ ONLY** repository. See the
12-
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.
12+
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.
13+
14+
## Usage
15+
16+
```php
17+
$httpClient = new \Http\Adapter\Guzzle6\Client();
18+
19+
$provider = new \Geocoder\Provider\ArcGISList\ArcGISList($httpClient);
20+
21+
$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
22+
```
23+
24+
### Storing results
25+
26+
ArcGIS prohibits storing the results of geocoding transactions without providing
27+
a valid ArcGIS Online token, which requires
28+
[ArcGIS Online credentials](https://developers.arcgis.com/rest/geocode/api-reference/geocoding-authenticate-a-request.htm).
29+
30+
You can use the static `token` method on the provider to create a client which
31+
uses your valid ArcGIS Online token:
32+
33+
```php
34+
35+
$httpClient = new \Http\Adapter\Guzzle6\Client();
36+
37+
// Client ID is required. Private key is optional.
38+
$provider = \Geocoder\Provider\ArcGISList\ArcGISList::token($httpClient, 'your-token');
39+
40+
$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
41+
```
1342

1443
### Install
1544

@@ -26,5 +55,5 @@ geocoding).
2655

2756
### Contribute
2857

29-
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
58+
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
3059
report any issues you find on the [issue tracker](https://github.com/geocoder-php/Geocoder/issues).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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}}}";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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":""}}]}";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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}}]}";

0 commit comments

Comments
 (0)