*/
final class Nominatim extends AbstractHttpProvider implements Provider
{
@@ -35,163 +39,238 @@ final class Nominatim extends AbstractHttpProvider implements Provider
private $rootUrl;
/**
- * @param HttpClient $client
- * @param string|null $locale
- *
- * @return Nominatim
+ * @var string
+ */
+ private $userAgent;
+
+ /**
+ * @var string
*/
- public static function withOpenStreetMapServer(HttpClient $client)
+ private $referer;
+
+ /**
+ * @param ClientInterface $client an HTTP client
+ * @param string $userAgent Value of the User-Agent header
+ * @param string $referer Value of the Referer header
+ */
+ public static function withOpenStreetMapServer(ClientInterface $client, string $userAgent, string $referer = ''): self
{
- return new self($client, 'https://nominatim.openstreetmap.org');
+ return new self($client, 'https://nominatim.openstreetmap.org', $userAgent, $referer);
}
/**
- * @param HttpClient $client an HTTP adapter
- * @param string $rootUrl Root URL of the nominatim server
+ * @param ClientInterface $client an HTTP client
+ * @param string $rootUrl Root URL of the nominatim server
+ * @param string $userAgent Value of the User-Agent header
+ * @param string $referer Value of the Referer header
*/
- public function __construct(HttpClient $client, $rootUrl)
+ public function __construct(ClientInterface $client, $rootUrl, string $userAgent, string $referer = '')
{
parent::__construct($client);
$this->rootUrl = rtrim($rootUrl, '/');
+ $this->userAgent = $userAgent;
+ $this->referer = $referer;
+
+ if (empty($this->userAgent)) {
+ throw new InvalidArgument('The User-Agent must be set to use the Nominatim provider.');
+ }
}
- /**
- * {@inheritdoc}
- */
public function geocodeQuery(GeocodeQuery $query): Collection
{
$address = $query->getText();
- // This API does not support IPv6
- if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
- throw new UnsupportedOperation('The Nominatim provider does not support IPv6 addresses.');
+
+ // This API doesn't handle IPs
+ if (filter_var($address, FILTER_VALIDATE_IP)) {
+ throw new UnsupportedOperation('The Nominatim provider does not support IP addresses.');
}
- if ('127.0.0.1' === $address) {
- return new AddressCollection([$this->getLocationForLocalhost()]);
+ $url = $this->rootUrl
+ .'/search?'
+ .http_build_query([
+ 'format' => 'jsonv2',
+ 'q' => $address,
+ 'addressdetails' => 1,
+ 'extratags' => 1,
+ 'limit' => $query->getLimit(),
+ ], '', '&', PHP_QUERY_RFC3986);
+
+ $countrycodes = $query->getData('countrycodes');
+ if (!is_null($countrycodes)) {
+ if (is_array($countrycodes)) {
+ $countrycodes = array_map('strtolower', $countrycodes);
+
+ $url .= '&'.http_build_query([
+ 'countrycodes' => implode(',', $countrycodes),
+ ], '', '&', PHP_QUERY_RFC3986);
+ } else {
+ $url .= '&'.http_build_query([
+ 'countrycodes' => strtolower($countrycodes),
+ ], '', '&', PHP_QUERY_RFC3986);
+ }
+ }
+
+ $viewbox = $query->getData('viewbox');
+ if (!is_null($viewbox) && is_array($viewbox) && 4 === count($viewbox)) {
+ $url .= '&'.http_build_query([
+ 'viewbox' => implode(',', $viewbox),
+ ], '', '&', PHP_QUERY_RFC3986);
+
+ $bounded = $query->getData('bounded');
+ if (!is_null($bounded) && true === $bounded) {
+ $url .= '&'.http_build_query([
+ 'bounded' => 1,
+ ], '', '&', PHP_QUERY_RFC3986);
+ }
}
- $url = sprintf($this->getGeocodeEndpointUrl(), urlencode($address), $query->getLimit());
$content = $this->executeQuery($url, $query->getLocale());
- $doc = new \DOMDocument();
- if (!@$doc->loadXML($content) || null === $doc->getElementsByTagName('searchresults')->item(0)) {
+ $json = json_decode($content);
+ if (is_null($json) || !is_array($json)) {
throw InvalidServerResponse::create($url);
}
- $searchResult = $doc->getElementsByTagName('searchresults')->item(0);
- $places = $searchResult->getElementsByTagName('place');
-
- if (null === $places || 0 === $places->length) {
+ if (empty($json)) {
return new AddressCollection([]);
}
$results = [];
- foreach ($places as $place) {
- $results[] = $this->xmlResultToArray($place, $place);
+ foreach ($json as $place) {
+ $results[] = $this->jsonResultToLocation($place, false);
}
return new AddressCollection($results);
}
- /**
- * {@inheritdoc}
- */
public function reverseQuery(ReverseQuery $query): Collection
{
$coordinates = $query->getCoordinates();
$longitude = $coordinates->getLongitude();
$latitude = $coordinates->getLatitude();
- $url = sprintf($this->getReverseEndpointUrl(), $latitude, $longitude, $query->getData('zoom', 18));
+
+ $url = $this->rootUrl
+ .'/reverse?'
+ .http_build_query([
+ 'format' => 'jsonv2',
+ 'lat' => $latitude,
+ 'lon' => $longitude,
+ 'addressdetails' => 1,
+ 'zoom' => $query->getData('zoom', 18),
+ ], '', '&', PHP_QUERY_RFC3986);
+
$content = $this->executeQuery($url, $query->getLocale());
- $doc = new \DOMDocument();
- if (!@$doc->loadXML($content) || $doc->getElementsByTagName('error')->length > 0) {
+ $json = json_decode($content);
+ if (is_null($json) || isset($json->error)) {
return new AddressCollection([]);
}
- $searchResult = $doc->getElementsByTagName('reversegeocode')->item(0);
- $addressParts = $searchResult->getElementsByTagName('addressparts')->item(0);
- $result = $searchResult->getElementsByTagName('result')->item(0);
+ if (empty($json)) {
+ return new AddressCollection([]);
+ }
- return new AddressCollection([$this->xmlResultToArray($result, $addressParts)]);
+ return new AddressCollection([$this->jsonResultToLocation($json, true)]);
}
- /**
- * @param \DOMElement $resultNode
- * @param \DOMElement $addressNode
- *
- * @return Location
- */
- private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressNode): Location
+ private function jsonResultToLocation(\stdClass $place, bool $reverse): Location
{
$builder = new AddressBuilder($this->getName());
foreach (['state', 'county'] as $i => $tagName) {
- if (null !== ($adminLevel = $this->getNodeValue($addressNode->getElementsByTagName($tagName)))) {
- $builder->addAdminLevel($i + 1, $adminLevel, '');
+ if (isset($place->address->{$tagName})) {
+ $builder->addAdminLevel($i + 1, $place->address->{$tagName}, '');
}
}
// get the first postal-code when there are many
- $postalCode = $this->getNodeValue($addressNode->getElementsByTagName('postcode'));
- if (!empty($postalCode)) {
- $postalCode = current(explode(';', $postalCode));
- }
- $builder->setPostalCode($postalCode);
- $builder->setStreetName($this->getNodeValue($addressNode->getElementsByTagName('road')) ?: $this->getNodeValue($addressNode->getElementsByTagName('pedestrian')));
- $builder->setStreetNumber($this->getNodeValue($addressNode->getElementsByTagName('house_number')));
- $builder->setLocality($this->getNodeValue($addressNode->getElementsByTagName('city')));
- $builder->setSubLocality($this->getNodeValue($addressNode->getElementsByTagName('suburb')));
- $builder->setCountry($this->getNodeValue($addressNode->getElementsByTagName('country')));
- $builder->setCountryCode(strtoupper($this->getNodeValue($addressNode->getElementsByTagName('country_code'))));
- $builder->setCoordinates($resultNode->getAttribute('lat'), $resultNode->getAttribute('lon'));
-
- $boundsAttr = $resultNode->getAttribute('boundingbox');
- if ($boundsAttr) {
- $bounds = [];
- list($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']) = explode(',', $boundsAttr);
- $builder->setBounds($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']);
- }
-
- return $builder->build();
+ if (isset($place->address->postcode)) {
+ $postalCode = $place->address->postcode;
+ if (!empty($postalCode)) {
+ $postalCode = current(explode(';', $postalCode));
+ }
+ $builder->setPostalCode($postalCode);
+ }
+
+ $localityFields = ['city', 'town', 'village', 'hamlet'];
+ foreach ($localityFields as $localityField) {
+ if (isset($place->address->{$localityField})) {
+ $localityFieldContent = $place->address->{$localityField};
+
+ if (!empty($localityFieldContent)) {
+ $builder->setLocality($localityFieldContent);
+
+ break;
+ }
+ }
+ }
+
+ $builder->setStreetName($place->address->road ?? $place->address->pedestrian ?? null);
+ $builder->setStreetNumber($place->address->house_number ?? null);
+ $builder->setSubLocality($place->address->suburb ?? null);
+ $builder->setCountry($place->address->country ?? null);
+ $builder->setCountryCode(isset($place->address->country_code) ? strtoupper($place->address->country_code) : null);
+
+ $builder->setCoordinates(floatval($place->lat), floatval($place->lon));
+
+ $builder->setBounds($place->boundingbox[0], $place->boundingbox[2], $place->boundingbox[1], $place->boundingbox[3]);
+
+ /** @var NominatimAddress $location */
+ $location = $builder->build(NominatimAddress::class);
+ $location = $location->withAttribution($place->licence);
+ $location = $location->withDisplayName($place->display_name);
+
+ $includedAddressKeys = ['city', 'town', 'village', 'state', 'county', 'hamlet', 'postcode', 'road', 'pedestrian', 'house_number', 'suburb', 'country', 'country_code', 'quarter'];
+
+ $location = $location->withDetails(array_diff_key((array) $place->address, array_flip($includedAddressKeys)));
+
+ if (isset($place->extratags)) {
+ $location = $location->withTags((array) $place->extratags);
+ }
+ if (isset($place->address->quarter)) {
+ $location = $location->withQuarter($place->address->quarter);
+ }
+ if (isset($place->address->neighbourhood)) {
+ $location = $location->withNeighbourhood($place->address->neighbourhood);
+ }
+ if (isset($place->osm_id)) {
+ $location = $location->withOSMId(intval($place->osm_id));
+ }
+ if (isset($place->osm_type)) {
+ $location = $location->withOSMType($place->osm_type);
+ }
+
+ if (false === $reverse) {
+ $location = $location->withCategory($place->category);
+ $location = $location->withType($place->type);
+ }
+
+ return $location;
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'nominatim';
}
- /**
- * @param string $url
- * @param string|null $locale
- *
- * @return string
- */
- private function executeQuery(string $url, string $locale = null): string
+ private function executeQuery(string $url, ?string $locale = null): string
{
if (null !== $locale) {
- $url = sprintf('%s&accept-language=%s', $url, $locale);
+ $url .= '&'.http_build_query([
+ 'accept-language' => $locale,
+ ], '', '&', PHP_QUERY_RFC3986);
}
- return $this->getUrlContents($url);
- }
-
- private function getGeocodeEndpointUrl(): string
- {
- return $this->rootUrl.'/search?q=%s&format=xml&addressdetails=1&limit=%d';
- }
+ $request = $this->getRequest($url);
+ /** @var RequestInterface $request */
+ $request = $request->withHeader('User-Agent', $this->userAgent);
- private function getReverseEndpointUrl(): string
- {
- return $this->rootUrl.'/reverse?format=xml&lat=%F&lon=%F&addressdetails=1&zoom=%d';
- }
+ if (!empty($this->referer)) {
+ /** @var RequestInterface $request */
+ $request = $request->withHeader('Referer', $this->referer);
+ }
- private function getNodeValue(\DOMNodeList $element)
- {
- return $element->length ? $element->item(0)->nodeValue : null;
+ return $this->getParsedResponse($request);
}
}
diff --git a/src/Provider/Nominatim/Readme.md b/src/Provider/Nominatim/README.md
similarity index 71%
rename from src/Provider/Nominatim/Readme.md
rename to src/Provider/Nominatim/README.md
index da09ce4be..bea68c73b 100644
--- a/src/Provider/Nominatim/Readme.md
+++ b/src/Provider/Nominatim/README.md
@@ -8,15 +8,31 @@
[](LICENSE)
This is the Nominatim 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.
-### Install
+All usage of the Nominatim provider using `nominatim.openstreetmap.org` must follow the [Nominatim Usage Policy](https://operations.osmfoundation.org/policies/nominatim/) !
+
+## Install
```bash
composer require geocoder-php/nominatim-provider
```
-### Contribute
+## Usage
+
+If you want to use the "default" Nominatim instance (https://nominatim.openstreetmap.org/) :
+
+```php
+$provider = \Geocoder\Provider\Nominatim\Nominatim::withOpenStreetMapServer($httpClient, $userAgent);
+```
+
+If you want to specify yourself the server that will be used :
+
+```php
+$provider = new \Geocoder\Provider\Nominatim($httpClient, 'https://nominatim.openstreetmap.org', $userAgent);
+```
+
+## Contribute
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).
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_070ffa8534ee86aa47fed7a61965a9b9f71cd27c b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_070ffa8534ee86aa47fed7a61965a9b9f71cd27c
new file mode 100644
index 000000000..152ff0d2a
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_070ffa8534ee86aa47fed7a61965a9b9f71cd27c
@@ -0,0 +1 @@
+s:6925:"[{"place_id":257906993,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":3299902,"boundingbox":["50.8411653","50.8428129","4.3601343","4.3646336"],"lat":"50.8419916","lon":"4.3619888303182375","display_name":"Palais Royal - Koninklijk Paleis, 1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"attraction","importance":1.0389733784428217,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_point_of_interest.p.20.png","address":{"tourism":"Palais Royal - Koninklijk Paleis","house_number":"1","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"image":"https://images.mapillary.com/2Ny81XTLPOeb52kp3yoAHQ/thumb-2048.jpg","wikidata":"Q635307","mapillary":"https://www.mapillary.com/map/im/2Ny81XTLPOeb52kp3yoAHQ","wikipedia":"en:Royal Palace of Brussels","start_date":"1826","castle_type":"palace","description":"https://photos.app.goo.gl/5C1gu5fHnmqaESFPA"}},{"place_id":47175961,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":3860604720,"boundingbox":["50.8426684","50.8427684","4.3604791","4.3605791"],"lat":"50.8427184","lon":"4.3605291","display_name":"Coudenberg, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"museum","importance":0.9744633789338841,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//tourist_museum.p.20.png","address":{"tourism":"Coudenberg","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"wikidata":"Q1131821","wikipedia":"fr:Palais du Coudenberg","wheelchair":"limited","contact:email":"info@coudenberg.brussels","contact:phone":"+32 2 500 45 54","description:fr":"Ancien Palais de Bruxelles","description:nl":"Voormalig Paleis van Brussel","contact:website":"https://www.coudenberg.com"}},{"place_id":24442199,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":2514258901,"boundingbox":["50.8426856","50.8427856","4.3604126","4.3605126"],"lat":"50.8427356","lon":"4.3604626","display_name":"Musée BELvue - BELvue Museum, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"museum","importance":0.8785144183561604,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//tourist_museum.p.20.png","address":{"tourism":"Musée BELvue - BELvue Museum","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"wikidata":"Q728437","wikipedia":"en:BELvue Museum","wheelchair":"limited","opening_hours":"Mo off; Tu-Fr 09:00-17:00; Sa-Su 10:00-18:00; Jul-Aug Tu-Fr 10:00-18:00; Jan 1 off; Dec 24 09:00-16:00; Dec 25 off; Dec 31 09:00-16:00","contact:website":"https://belvue.be/"}},{"place_id":24692458,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":2514258857,"boundingbox":["50.8418523","50.8419523","4.3621672","4.3622672"],"lat":"50.8419023","lon":"4.3622172","display_name":"1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"place","type":"house","importance":0.621,"address":{"house_number":"1","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"ref:UrbIS":"8187967"}},{"place_id":322634700,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":8863930920,"boundingbox":["50.8424321","50.8425321","4.3604558","4.3605558"],"lat":"50.8424821","lon":"4.3605058","display_name":"Les Filles, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"amenity","type":"restaurant","importance":0.611,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//food_restaurant.p.20.png","address":{"amenity":"Les Filles","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"contact:website":"https://www.lesfilles.be/","outdoor_seating":"garden"}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1c28ca51962224c6963619eaf033eb70e34892e8 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1c28ca51962224c6963619eaf033eb70e34892e8
new file mode 100644
index 000000000..62e1cfb53
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1c28ca51962224c6963619eaf033eb70e34892e8
@@ -0,0 +1 @@
+s:1631:"[{"place_id":259070493,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":1879842,"boundingbox":["51.5032573","51.5036483","-0.1278356","-0.1273038"],"lat":"51.50344025","lon":"-0.12770820958562096","display_name":"Prime Minister’s Office, 10, Downing Street, Westminster, London, Greater London, England, SW1A 2AA, United Kingdom","place_rank":30,"category":"tourism","type":"attraction","importance":0.9187064122051384,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_point_of_interest.p.20.png","address":{"tourism":"Prime Minister’s Office","house_number":"10","road":"Downing Street","quarter":"Westminster","city":"Westminster","state_district":"Greater London","state":"England","postcode":"SW1A 2AA","country":"United Kingdom","country_code":"gb"},"extratags":{"website":"http://www.number10.gov.uk/","wikidata":"Q169101","wikipedia":"en:10 Downing Street"}},{"place_id":18621002,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":1931404517,"boundingbox":["51.5032302","51.5033302","-0.1276858","-0.1275858"],"lat":"51.5032802","lon":"-0.1276358","display_name":"10, Downing Street, Westminster, London, Greater London, England, SW1, United Kingdom","place_rank":30,"category":"place","type":"house","importance":0.42099999999999993,"address":{"house_number":"10","road":"Downing Street","quarter":"Westminster","city":"Westminster","state_district":"Greater London","state":"England","postcode":"SW1","country":"United Kingdom","country_code":"gb"},"extratags":{"entrance":"yes"}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1cebd4c14dbf55f22e57957b884db74b2484dbb8 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1cebd4c14dbf55f22e57957b884db74b2484dbb8
new file mode 100644
index 000000000..5b02ace69
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_1cebd4c14dbf55f22e57957b884db74b2484dbb8
@@ -0,0 +1 @@
+s:693:"[{"place_id":325306659,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":8945017937,"boundingbox":["52.188674","52.188774","21.0111569","21.0112569"],"lat":"52.188724","lon":"21.0112069","display_name":"17, Jana Pawła Woronicza, Ksawerów, Mokotów, Warszawa, województwo mazowieckie, 02-625, Polska","place_rank":30,"category":"place","type":"house","importance":0.411,"address":{"house_number":"17","road":"Jana Pawła Woronicza","quarter":"Ksawerów","suburb":"Mokotów","city_district":"Warszawa","city":"Warszawa","state":"województwo mazowieckie","postcode":"02-625","country":"Polska","country_code":"pl"},"extratags":{}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_210cc3da1b367ae860e6077e1c4af6aae61bdbfb b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_210cc3da1b367ae860e6077e1c4af6aae61bdbfb
new file mode 100644
index 000000000..c305f3346
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_210cc3da1b367ae860e6077e1c4af6aae61bdbfb
@@ -0,0 +1 @@
+s:1480:"[{"place_id":95065281,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"way","osm_id":24981342,"boundingbox":["53.540877","53.5416212","9.9832434","9.9849674"],"lat":"53.54129165","lon":"9.984205766993528","display_name":"Elbphilharmonie, 1, Platz der Deutschen Einheit, Quartier Am Sandtorkai/Dalmannkai, HafenCity, Hamburg-Mitte, Hamburg, 20457, Deutschland","place_rank":30,"category":"tourism","type":"attraction","importance":1.135921561767739,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_point_of_interest.p.20.png","address":{"tourism":"Elbphilharmonie","house_number":"1","road":"Platz der Deutschen Einheit","neighbourhood":"Quartier Am Sandtorkai/Dalmannkai","suburb":"HafenCity","borough":"Hamburg-Mitte","city":"Hamburg","postcode":"20457","country":"Deutschland","country_code":"de"},"extratags":{"height":"110 m","operator":"Stadt Hamburg","wikidata":"Q673223","architect":"Werner Kallmorgen;Herzog & de Meuron","wikipedia":"de:Elbphilharmonie","roof:shape":"skillion","start_date":"2016-10-31","wheelchair":"limited","roof:colour":"white","roof:height":"20","theatre:type":"concert_hall","contact:email":"info@elbphilharmonie.de","contact:phone":"+49 40 3576660","theatre:genre":"philharmonic","roof:direction":"100","building:levels":"26","contact:website":"https://www.elbphilharmonie.de/de/","construction_year":"2003-2016","wikimedia_commons":"Category:Elbphilharmonie","toilets:wheelchair":"yes"}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_237e0bcaa26f36e484a002f8238e2645591e3622 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_237e0bcaa26f36e484a002f8238e2645591e3622
new file mode 100644
index 000000000..619c2d71a
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_237e0bcaa26f36e484a002f8238e2645591e3622
@@ -0,0 +1 @@
+s:562:"{"place_id":112067733,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"way","osm_id":28063955,"lat":"35.686010380404326","lon":"139.8116922926535","place_rank":26,"category":"highway","type":"tertiary","importance":0.09999999999999998,"addresstype":"road","name":null,"display_name":"Sarue 1-chome, Koto, Tokyo, 135-0002, Japan","address":{"neighbourhood":"Sarue 1-chome","city":"Koto","postcode":"135-0002","country":"Japan","country_code":"jp"},"boundingbox":["35.6859434","35.68617","139.8098558","139.8160149"]}";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_47454e44068c82013d117cb00cca6231d73f923f b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_47454e44068c82013d117cb00cca6231d73f923f
new file mode 100644
index 000000000..1e0bbe2aa
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_47454e44068c82013d117cb00cca6231d73f923f
@@ -0,0 +1 @@
+s:2898:"[{"place_id":257906993,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":3299902,"boundingbox":["50.8411653","50.8428129","4.3601343","4.3646336"],"lat":"50.8419916","lon":"4.3619888303182375","display_name":"Palais Royal - Koninklijk Paleis, 1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"attraction","importance":0.6189733784428217,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_point_of_interest.p.20.png","address":{"tourism":"Palais Royal - Koninklijk Paleis","house_number":"1","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"image":"https://images.mapillary.com/2Ny81XTLPOeb52kp3yoAHQ/thumb-2048.jpg","wikidata":"Q635307","mapillary":"https://www.mapillary.com/map/im/2Ny81XTLPOeb52kp3yoAHQ","wikipedia":"en:Royal Palace of Brussels","start_date":"1826","castle_type":"palace","description":"https://photos.app.goo.gl/5C1gu5fHnmqaESFPA"}},{"place_id":1276415,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":316950720,"boundingbox":["50.8435842","50.8436842","4.3605629","4.3606629"],"lat":"50.8436342","lon":"4.3606129","display_name":"Palais Royal - Koninklijk Paleis, Rue Royale - Koningsstraat, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"amenity","type":"bicycle_rental","importance":0.201,"address":{"amenity":"Palais Royal - Koninklijk Paleis","road":"Rue Royale - Koningsstraat","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"network":"Villo!","capacity":"20","operator":"JCDecaux","brand:wikidata":"Q2510744","brand:wikipedia":"en:Villo!","network:wikidata":"Q2510744","network:wikipedia":"en:Villo!"}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_4b0bd12dfc3c0157ea016fa5c05223058dd1b76a b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_4b0bd12dfc3c0157ea016fa5c05223058dd1b76a
deleted file mode 100644
index 865eca01b..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_4b0bd12dfc3c0157ea016fa5c05223058dd1b76a
+++ /dev/null
@@ -1,4 +0,0 @@
-s:1313:"
-
-
-10 Downing Street10Downing StreetSt. James'sCovent GardenLondonGreater LondonEnglandSW1A 2AAUnited Kingdomgb";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_592b89bc385fdb5a1035c78dbbe53f2140ba2489 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_592b89bc385fdb5a1035c78dbbe53f2140ba2489
new file mode 100644
index 000000000..1c020e25e
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_592b89bc385fdb5a1035c78dbbe53f2140ba2489
@@ -0,0 +1 @@
+s:777:"{"place_id":7323219,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":855292284,"lat":"48.8604416","lon":"2.3503543","place_rank":30,"category":"amenity","type":"cafe","importance":0,"addresstype":"amenity","name":"Starbucks","display_name":"Starbucks, Rue Quincampoix, Beaubourg, Quartier Saint-Merri, Paris 4e Arrondissement, Paris, Île-de-France, France métropolitaine, 75004, France","address":{"amenity":"Starbucks","road":"Rue Quincampoix","neighbourhood":"Beaubourg","suburb":"Paris 4e Arrondissement","city":"Paris","municipality":"Paris","county":"Paris","state":"Île-de-France","country":"France","postcode":"75004","country_code":"fr"},"boundingbox":["48.8603916","48.8604916","2.3503043","2.3504043"]}";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_60b19895d3f895334fa01eb3340701e054d05dca b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_60b19895d3f895334fa01eb3340701e054d05dca
new file mode 100644
index 000000000..dd9bfca6e
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_60b19895d3f895334fa01eb3340701e054d05dca
@@ -0,0 +1 @@
+s:525:"[{"place_id":261099446,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","boundingbox":["33.935082673098","34.255082673098","-118.55932247265","-118.23932247265"],"lat":"34.09508267309781","lon":"-118.39932247264571","display_name":"Beverly Hills, California, 90210, United States","place_rank":21,"category":"place","type":"postcode","importance":0.535,"address":{"town":"Beverly Hills","state":"California","postcode":"90210","country":"United States","country_code":"us"},"extratags":{}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_614a4d899e5e7e47380278075f00be15637686c6 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_614a4d899e5e7e47380278075f00be15637686c6
new file mode 100644
index 000000000..67d7c2b10
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_614a4d899e5e7e47380278075f00be15637686c6
@@ -0,0 +1 @@
+s:411:"[{"place_id":105224518,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"way","osm_id":62194430,"boundingbox":["-62.1749853","-62.1734171","-58.5166348","-58.5136685"],"lat":"-62.17462","lon":"-58.5155525","display_name":"Italia","place_rank":22,"category":"waterway","type":"stream","importance":0.30999999999999994,"address":{"waterway":"Italia"},"extratags":{}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_7df0443a2fd6a14b81f22209fd212d98c62ed524 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_7df0443a2fd6a14b81f22209fd212d98c62ed524
deleted file mode 100644
index 485cd5896..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_7df0443a2fd6a14b81f22209fd212d98c62ed524
+++ /dev/null
@@ -1,3 +0,0 @@
-s:982:"
-
-Hay-Adams Hotel, 800, 16th Street Northwest, Golden Triangle, Washington, District of Columbia, 20006, United States of AmericaHay-Adams Hotel80016th Street NorthwestGolden TriangleWashingtonDistrict of Columbia20006United States of Americaus";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c0e8467e060bb49217e0966a3893931cbc2fbb8c b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c0e8467e060bb49217e0966a3893931cbc2fbb8c
new file mode 100644
index 000000000..152ff0d2a
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c0e8467e060bb49217e0966a3893931cbc2fbb8c
@@ -0,0 +1 @@
+s:6925:"[{"place_id":257906993,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":3299902,"boundingbox":["50.8411653","50.8428129","4.3601343","4.3646336"],"lat":"50.8419916","lon":"4.3619888303182375","display_name":"Palais Royal - Koninklijk Paleis, 1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"attraction","importance":1.0389733784428217,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_point_of_interest.p.20.png","address":{"tourism":"Palais Royal - Koninklijk Paleis","house_number":"1","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"image":"https://images.mapillary.com/2Ny81XTLPOeb52kp3yoAHQ/thumb-2048.jpg","wikidata":"Q635307","mapillary":"https://www.mapillary.com/map/im/2Ny81XTLPOeb52kp3yoAHQ","wikipedia":"en:Royal Palace of Brussels","start_date":"1826","castle_type":"palace","description":"https://photos.app.goo.gl/5C1gu5fHnmqaESFPA"}},{"place_id":47175961,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":3860604720,"boundingbox":["50.8426684","50.8427684","4.3604791","4.3605791"],"lat":"50.8427184","lon":"4.3605291","display_name":"Coudenberg, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"museum","importance":0.9744633789338841,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//tourist_museum.p.20.png","address":{"tourism":"Coudenberg","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"wikidata":"Q1131821","wikipedia":"fr:Palais du Coudenberg","wheelchair":"limited","contact:email":"info@coudenberg.brussels","contact:phone":"+32 2 500 45 54","description:fr":"Ancien Palais de Bruxelles","description:nl":"Voormalig Paleis van Brussel","contact:website":"https://www.coudenberg.com"}},{"place_id":24442199,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":2514258901,"boundingbox":["50.8426856","50.8427856","4.3604126","4.3605126"],"lat":"50.8427356","lon":"4.3604626","display_name":"Musée BELvue - BELvue Museum, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"tourism","type":"museum","importance":0.8785144183561604,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//tourist_museum.p.20.png","address":{"tourism":"Musée BELvue - BELvue Museum","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"wikidata":"Q728437","wikipedia":"en:BELvue Museum","wheelchair":"limited","opening_hours":"Mo off; Tu-Fr 09:00-17:00; Sa-Su 10:00-18:00; Jul-Aug Tu-Fr 10:00-18:00; Jan 1 off; Dec 24 09:00-16:00; Dec 25 off; Dec 31 09:00-16:00","contact:website":"https://belvue.be/"}},{"place_id":24692458,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":2514258857,"boundingbox":["50.8418523","50.8419523","4.3621672","4.3622672"],"lat":"50.8419023","lon":"4.3622172","display_name":"1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"place","type":"house","importance":0.621,"address":{"house_number":"1","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"ref:UrbIS":"8187967"}},{"place_id":322634700,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":8863930920,"boundingbox":["50.8424321","50.8425321","4.3604558","4.3605558"],"lat":"50.8424821","lon":"4.3605058","display_name":"Les Filles, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien","place_rank":30,"category":"amenity","type":"restaurant","importance":0.611,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//food_restaurant.p.20.png","address":{"amenity":"Les Filles","road":"Place des Palais - Paleizenplein","neighbourhood":"Quartier Royal - Koninklijke Wijk","suburb":"Pentagone - Vijfhoek","city_district":"Bruxelles - Brussel","city":"Ville de Bruxelles - Stad Brussel","county":"Brussel-Hoofdstad - Bruxelles-Capitale","region":"Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest","postcode":"1000","country":"België / Belgique / Belgien","country_code":"be"},"extratags":{"contact:website":"https://www.lesfilles.be/","outdoor_seating":"garden"}}]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c8d6ddf2f89fcd37eb45427c4b121d768bd6c499 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c8d6ddf2f89fcd37eb45427c4b121d768bd6c499
deleted file mode 100644
index 2a798d216..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_c8d6ddf2f89fcd37eb45427c4b121d768bd6c499
+++ /dev/null
@@ -1,4 +0,0 @@
-s:978:"
-
-
-8CaloocanCaloocanMetro ManilaPhilippinesph";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_cf31cb719694431c8dfb6ea9caec0b45cba5aeac b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_cf31cb719694431c8dfb6ea9caec0b45cba5aeac
deleted file mode 100644
index 37fb5f01f..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_cf31cb719694431c8dfb6ea9caec0b45cba5aeac
+++ /dev/null
@@ -1,13 +0,0 @@
-s:639:"
-
-Bandwidth limit exceeded
-
-
-Bandwidth limit exceeded
-
-You have been temporarily blocked because you have been overusing OSM's geocoding service or because you have not provided sufficient identification of your application. This block will be automatically lifted after a while. Please take the time and adapt your scripts to reduce the number of requests and make sure that you send a valid UserAgent or Referer.
-
-For more information, consult the usage policy for the OSM Nominatim server.
-
-
-";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dbb2cee16ead7ec2f2199dc8d4107306806ebe19 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dbb2cee16ead7ec2f2199dc8d4107306806ebe19
deleted file mode 100644
index d86d9f65e..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dbb2cee16ead7ec2f2199dc8d4107306806ebe19
+++ /dev/null
@@ -1,3 +0,0 @@
-s:407:"
-
-";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dfdc355642ef34fb9970df028c4ec338ce0ea3f4 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dfdc355642ef34fb9970df028c4ec338ce0ea3f4
new file mode 100644
index 000000000..123f97868
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_dfdc355642ef34fb9970df028c4ec338ce0ea3f4
@@ -0,0 +1 @@
+s:2:"[]";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_e52647a4ee81d8138eb99f6c112982f37b855f91 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_e52647a4ee81d8138eb99f6c112982f37b855f91
new file mode 100644
index 000000000..964cdcab8
--- /dev/null
+++ b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_e52647a4ee81d8138eb99f6c112982f37b855f91
@@ -0,0 +1 @@
+s:744:"{"place_id":3029686,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":367142942,"lat":"38.9003895","lon":"-77.0374769","place_rank":30,"category":"building","type":"public","importance":0,"addresstype":"building","name":"United States Chamber of Commerce Building","display_name":"United States Chamber of Commerce Building, H Street Northwest, Washington, District of Columbia, 20006, United States","address":{"building":"United States Chamber of Commerce Building","road":"H Street Northwest","city":"Washington","state":"District of Columbia","postcode":"20006","country":"United States","country_code":"us"},"boundingbox":["38.9003395","38.9004395","-77.0375269","-77.0374269"]}";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_f650be9c07be19f4c0e01889d990883dfa9ae1e0 b/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_f650be9c07be19f4c0e01889d990883dfa9ae1e0
deleted file mode 100644
index 1dc2fc0db..000000000
--- a/src/Provider/Nominatim/Tests/.cached_responses/nominatim.openstreetmap.org_f650be9c07be19f4c0e01889d990883dfa9ae1e0
+++ /dev/null
@@ -1,3 +0,0 @@
-s:972:"
-
-Bistrot Beaubourg, 25, Rue Quincampoix, Beaubourg, St-Merri, 4e, Paris, Île-de-France, 75004, FranceBistrot Beaubourg25Rue QuincampoixBeaubourgSt-Merri4eParisParisÎle-de-France75004Francefr";
\ No newline at end of file
diff --git a/src/Provider/Nominatim/Tests/IntegrationTest.php b/src/Provider/Nominatim/Tests/IntegrationTest.php
index b3c72c8f1..0fb074917 100644
--- a/src/Provider/Nominatim/Tests/IntegrationTest.php
+++ b/src/Provider/Nominatim/Tests/IntegrationTest.php
@@ -12,30 +12,37 @@
use Geocoder\IntegrationTest\ProviderIntegrationTest;
use Geocoder\Provider\Nominatim\Nominatim;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Tobias Nyholm
*/
class IntegrationTest extends ProviderIntegrationTest
{
- protected $testAddress = true;
- protected $testReverse = true;
- protected $testIpv4 = true;
- protected $testIpv6 = false;
+ protected array $skippedTests = [
+ 'testReverseQueryWithNoResults' => 'There is "Soul Buoy"',
+ ];
- protected function createProvider(HttpClient $httpClient)
+ protected bool $testAddress = true;
+
+ protected bool $testReverse = true;
+
+ protected bool $testIpv4 = false;
+
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
{
- return Nominatim::withOpenStreetMapServer($httpClient);
+ return Nominatim::withOpenStreetMapServer($httpClient, 'Geocoder PHP/Nominatim Provider/Integration Test');
}
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- protected function getApiKey()
+ protected function getApiKey(): string
{
- return null;
+ return '';
}
}
diff --git a/src/Provider/Nominatim/Tests/NominatimTest.php b/src/Provider/Nominatim/Tests/NominatimTest.php
index 1c629aed2..8a4db71fa 100644
--- a/src/Provider/Nominatim/Tests/NominatimTest.php
+++ b/src/Provider/Nominatim/Tests/NominatimTest.php
@@ -15,100 +15,245 @@
use Geocoder\Collection;
use Geocoder\IntegrationTest\BaseTestCase;
use Geocoder\Location;
+use Geocoder\Provider\Nominatim\Nominatim;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Provider\Nominatim\Nominatim;
class NominatimTest extends BaseTestCase
{
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- public function testGeocodeWithLocalhostIPv4()
+ public function testGeocodeWithLocalhostIPv4(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Nominatim provider does not support IP addresses.');
+
+ $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
+ }
+
+ public function testGeocodeWithLocalhostIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Nominatim provider does not support IP addresses.');
+
+ $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $provider->geocodeQuery(GeocodeQuery::create('::1'));
+ }
+
+ public function testGeocodeWithRealIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Nominatim provider does not support IP addresses.');
+
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $provider->geocodeQuery(GeocodeQuery::create('::ffff:88.188.221.14'));
+ }
+
+ public function testReverseWithCoordinatesGetsError(): void
{
- $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient());
- $results = $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
+ $errorJSON = '{"error":"Unable to geocode"}';
+
+ $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient($errorJSON), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $result = $provider->reverseQuery(ReverseQuery::fromCoordinates(-80.000000, -170.000000));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(Collection::class, $result);
+ $this->assertEquals(0, $result->count());
+ }
+
+ public function testGetNodeStreetName(): void
+ {
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.86, 2.35));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals('localhost', $result->getLocality());
- $this->assertEmpty($result->getAdminLevels());
- $this->assertEquals('localhost', $result->getCountry()->getName());
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEquals('Rue Quincampoix', $result->getStreetName());
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The Nominatim provider does not support IPv6 addresses.
- */
- public function testGeocodeWithLocalhostIPv6()
+ public function testGeocodeWithRealAddress(): void
{
- $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient());
- $provider->geocodeQuery(GeocodeQuery::create('::1'));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $results = $provider->geocodeQuery(GeocodeQuery::create('1 Place des Palais 1000 bruxelles'));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(5, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(50.8419916, $result->getCoordinates()->getLatitude(), 0.00001);
+ $this->assertEqualsWithDelta(4.361988, $result->getCoordinates()->getLongitude(), 0.00001);
+ $this->assertEquals('1', $result->getStreetNumber());
+ $this->assertEquals('Place des Palais - Paleizenplein', $result->getStreetName());
+ $this->assertEquals('1000', $result->getPostalCode());
+ $this->assertEquals('Ville de Bruxelles - Stad Brussel', $result->getLocality());
+ $this->assertEquals('Pentagone - Vijfhoek', $result->getSubLocality());
+ $this->assertEquals('BE', $result->getCountry()->getCode());
+
+ $details = $result->getDetails();
+ $this->assertCount(4, $details);
+ $this->assertArrayHasKey('city_district', $details);
+ $this->assertEquals('Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest', $details['region']);
+ $this->assertEquals('Bruxelles - Brussel', $details['city_district']);
+ $this->assertEquals('Quartier Royal - Koninklijke Wijk', $details['neighbourhood']);
+ $this->assertEquals('Palais Royal - Koninklijk Paleis', $details['tourism']);
+
+ $this->assertEquals('Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', $result->getAttribution());
+ $this->assertEquals('tourism', $result->getCategory());
+ $this->assertEquals('Palais Royal - Koninklijk Paleis, 1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien', $result->getDisplayName());
+ $this->assertEquals(3299902, $result->getOSMId());
+ $this->assertEquals('relation', $result->getOSMType());
+ $this->assertEquals('attraction', $result->getType());
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The Nominatim provider does not support IPv6 addresses.
- */
- public function testGeocodeWithRealIPv6()
+ public function testGeocodeWithRealAddressThatReturnsOptionalQuarter(): void
{
- $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient());
- $provider->geocodeQuery(GeocodeQuery::create('::ffff:88.188.221.14'));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $results = $provider->geocodeQuery(GeocodeQuery::create('woronicza 17, warszawa, polska'));
+
+ $this->assertCount(1, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+
+ $this->assertEquals('Ksawerów', $result->getQuarter());
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testGeocodeWithAddressGetsEmptyContent()
+ public function testGeocodeWithRealAddressAndExtraTags(): void
{
- $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient(''));
- $provider->geocodeQuery(GeocodeQuery::create('Läntinen Pitkäkatu 35, Turku'));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Elbphilharmonie, Platz der deutschen Einheit 1, Hamburg'));
+ $this->assertCount(1, $results);
+
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Elbphilharmonie, Platz der deutschen Einheit 1, Hamburg'));
+
+ $this->assertCount(1, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+ $this->assertIsArray($result->getTags());
+ $this->assertArrayHasKey('height', $result->getTags());
+ $this->assertEquals('110 m', $result->getTags()['height']);
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testGeocodeWithAddressGetsEmptyXML()
+ public function testGeocodeWithCountrycodes(): void
{
- $emptyXML = <<<'XML'
-
-XML;
- $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient($emptyXML));
- $provider->geocodeQuery(GeocodeQuery::create('Läntinen Pitkäkatu 35, Turku'));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+
+ $query = GeocodeQuery::create('palais royal')
+ ->withData('countrycodes', 'be');
+
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertGreaterThanOrEqual(1, $results->count());
+
+ /** @var \Geocoder\Model\Address $result */
+ foreach ($results as $result) {
+ $this->assertEquals('BE', $result->getCountry()->getCode());
+ }
}
- public function testReverseWithCoordinatesGetsError()
+ public function testGeocodeWithViewbox(): void
{
- $errorXml = <<<'XML'
-
-
- Unable to geocode
-
-XML;
- $provider = Nominatim::withOpenStreetMapServer($this->getMockedHttpClient($errorXml));
- $result = $provider->reverseQuery(ReverseQuery::fromCoordinates(-80.000000, -170.000000));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
- $this->assertInstanceOf(Collection::class, $result);
- $this->assertEquals(0, $result->count());
+ $query = GeocodeQuery::create('1 Place des Palais 1000 bruxelles')
+ ->withData('viewbox', [4.3574204633, 50.8390856095, 4.3680849263, 50.8443022723])
+ ->withData('bounded', true);
+
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(5, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(50.8419916, $result->getCoordinates()->getLatitude(), 0.00001);
+ $this->assertEqualsWithDelta(4.361988, $result->getCoordinates()->getLongitude(), 0.00001);
+ $this->assertEquals('1', $result->getStreetNumber());
+ $this->assertEquals('Place des Palais - Paleizenplein', $result->getStreetName());
+ $this->assertEquals('1000', $result->getPostalCode());
+ $this->assertEquals('Ville de Bruxelles - Stad Brussel', $result->getLocality());
+ $this->assertEquals('Pentagone - Vijfhoek', $result->getSubLocality());
+ $this->assertEquals('BE', $result->getCountry()->getCode());
+
+ $this->assertEquals('Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', $result->getAttribution());
+ $this->assertEquals('tourism', $result->getCategory());
+ $this->assertEquals('Palais Royal - Koninklijk Paleis, 1, Place des Palais - Paleizenplein, Quartier Royal - Koninklijke Wijk, Pentagone - Vijfhoek, Bruxelles - Brussel, Ville de Bruxelles - Stad Brussel, Brussel-Hoofdstad - Bruxelles-Capitale, Région de Bruxelles-Capitale - Brussels Hoofdstedelijk Gewest, 1000, België / Belgique / Belgien', $result->getDisplayName());
+ $this->assertEquals(3299902, $result->getOSMId());
+ $this->assertEquals('relation', $result->getOSMType());
+ $this->assertEquals('attraction', $result->getType());
}
- public function testGetNodeStreetName()
+ public function testGeocodeNoOSMId(): void
{
- $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient());
- $results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.86, 2.35));
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $results = $provider->geocodeQuery(GeocodeQuery::create('90210,United States'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
- /** @var Location $result */
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals('Rue Quincampoix', $result->getStreetName());
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEquals('90210', $result->getPostalCode());
+ $this->assertEquals('US', $result->getCountry()->getCode());
+
+ $this->assertEquals('Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', $result->getAttribution());
+ $this->assertEquals('place', $result->getCategory());
+ $this->assertEquals('postcode', $result->getType());
+ $this->assertEquals(null, $result->getOSMId());
+ $this->assertEquals(null, $result->getOSMType());
+ }
+
+ public function testGeocodeNoCountry(): void
+ {
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $query = GeocodeQuery::create('Italia')
+ ->withData('viewbox', [-58.541836, -62.181561, -58.41618, -62.141319])
+ ->withData('bounded', true);
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEquals('Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', $result->getAttribution());
+
+ $this->assertEquals('Italia', $result->getDisplayName());
+ $this->assertEquals('waterway', $result->getCategory());
+ $this->assertEquals('62194430', $result->getOSMId());
+ $this->assertEquals('way', $result->getOSMType());
+ $this->assertEquals(null, $result->getCountry());
+ }
+
+ public function testGeocodeNeighbourhood(): void
+ {
+ $provider = Nominatim::withOpenStreetMapServer($this->getHttpClient(), 'Geocoder PHP/Nominatim Provider/Nominatim Test');
+ $results = $provider->reverseQuery(ReverseQuery::fromCoordinates(35.685939, 139.811695)->withLocale('en'));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var \Geocoder\Provider\Nominatim\Model\NominatimAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEquals('Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', $result->getAttribution());
+
+ $this->assertEquals('Sarue 1-chome', $result->getNeighbourhood());
+ $this->assertEquals('Japan', $result->getCountry());
}
}
diff --git a/src/Provider/Nominatim/composer.json b/src/Provider/Nominatim/composer.json
index 635932179..bfa150967 100644
--- a/src/Provider/Nominatim/composer.json
+++ b/src/Provider/Nominatim/composer.json
@@ -12,34 +12,35 @@
}
],
"require": {
- "php": "^7.0",
- "geocoder-php/common-http": "^4.0",
- "willdurand/geocoder": "^4.0"
+ "php": "^8.0",
+ "geocoder-php/common-http": "^4.1",
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
},
"require-dev": {
- "phpunit/phpunit": "^6.1",
- "geocoder-php/provider-integration-tests": "^1.0",
+ "geocoder-php/provider-integration-tests": "^1.6.3",
"php-http/message": "^1.0",
- "php-http/curl-client": "^1.7",
- "nyholm/psr7": "^0.2.2"
+ "phpunit/phpunit": "^9.6.11"
},
- "provide": {
- "geocoder-php/provider-implementation": "1.0"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
},
"autoload": {
- "psr-4": { "Geocoder\\Provider\\Nominatim\\": "" },
+ "psr-4": {
+ "Geocoder\\Provider\\Nominatim\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
}
}
diff --git a/src/Provider/Nominatim/phpunit.xml.dist b/src/Provider/Nominatim/phpunit.xml.dist
index 21fa59518..26265855a 100644
--- a/src/Provider/Nominatim/phpunit.xml.dist
+++ b/src/Provider/Nominatim/phpunit.xml.dist
@@ -1,29 +1,20 @@
-
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
- ./Tests
- ./vendor
-
-
-
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+ ./Tests/
+
+
diff --git a/src/Provider/OpenCage/.github/workflows/provider.yml b/src/Provider/OpenCage/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/OpenCage/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/OpenCage/.travis.yml b/src/Provider/OpenCage/.travis.yml
deleted file mode 100644
index 648662e93..000000000
--- a/src/Provider/OpenCage/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: php
-sudo: false
-
-php: 7.0
-
-
-install:
- - composer update --prefer-stable --prefer-dist
-
-script:
- - composer test-ci
-
-after_success:
- - wget https://scrutinizer-ci.com/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
-
diff --git a/src/Provider/OpenCage/CHANGELOG.md b/src/Provider/OpenCage/CHANGELOG.md
index 77016a46d..21614da7f 100644
--- a/src/Provider/OpenCage/CHANGELOG.md
+++ b/src/Provider/OpenCage/CHANGELOG.md
@@ -2,6 +2,81 @@
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+## 4.8.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 4.7.0
+
+### Added
+
+- Add support for PHP 8.2, 8.3, and 8.4
+- Add support for confidence
+
+### Removed
+
+- Drop support for PHP 7.4
+
+## 4.6.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 4.5.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 4.4.0
+
+### Removed
+
+- Drop support for PHP < 7.2
+
+## 4.3.0
+
+### Added
+
+- Support for parameters to reduce ambiguity with [*ambiguous results*](https://opencagedata.com/api#ambiguous-results)
+
+## 4.2.0
+
+### Changed
+
+- Improve StreetName, Locality, and SubLocality mapping
+
+## 4.1.0
+
+### Added
+
+- Added `OpenCageAddress` model.
+ - Added `MGRS`
+ - Added `Maidenhead`
+ - Added `geohash`
+ - Added `what3words`
+ - Added formatted address
+
## 4.0.0
-First release of this library.
+First release of this library.
diff --git a/src/Provider/OpenCage/Model/OpenCageAddress.php b/src/Provider/OpenCage/Model/OpenCageAddress.php
new file mode 100644
index 000000000..8ccad5145
--- /dev/null
+++ b/src/Provider/OpenCage/Model/OpenCageAddress.php
@@ -0,0 +1,152 @@
+mgrs = $mgrs;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getMGRS()
+ {
+ return $this->mgrs;
+ }
+
+ public function withMaidenhead(?string $maidenhead = null): self
+ {
+ $new = clone $this;
+ $new->maidenhead = $maidenhead;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getMaidenhead()
+ {
+ return $this->maidenhead;
+ }
+
+ public function withGeohash(?string $geohash = null): self
+ {
+ $new = clone $this;
+ $new->geohash = $geohash;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getGeohash()
+ {
+ return $this->geohash;
+ }
+
+ public function withWhat3words(?string $what3words = null): self
+ {
+ $new = clone $this;
+ $new->what3words = $what3words;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getWhat3words()
+ {
+ return $this->what3words;
+ }
+
+ public function withFormattedAddress(?string $formattedAddress = null): self
+ {
+ $new = clone $this;
+ $new->formattedAddress = $formattedAddress;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFormattedAddress()
+ {
+ return $this->formattedAddress;
+ }
+
+ public function withConfidence(?int $confidence = null): self
+ {
+ $new = clone $this;
+ $new->confidence = $confidence;
+
+ return $new;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getConfidence()
+ {
+ return $this->confidence;
+ }
+}
diff --git a/src/Provider/OpenCage/OpenCage.php b/src/Provider/OpenCage/OpenCage.php
index c64687b0a..ddb665b0a 100644
--- a/src/Provider/OpenCage/OpenCage.php
+++ b/src/Provider/OpenCage/OpenCage.php
@@ -12,18 +12,19 @@
namespace Geocoder\Provider\OpenCage;
+use Geocoder\Collection;
use Geocoder\Exception\InvalidArgument;
use Geocoder\Exception\InvalidCredentials;
use Geocoder\Exception\QuotaExceeded;
use Geocoder\Exception\UnsupportedOperation;
-use Geocoder\Collection;
-use Geocoder\Model\Address;
+use Geocoder\Http\Provider\AbstractHttpProvider;
+use Geocoder\Model\AddressBuilder;
use Geocoder\Model\AddressCollection;
+use Geocoder\Provider\OpenCage\Model\OpenCageAddress;
+use Geocoder\Provider\Provider;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Http\Provider\AbstractHttpProvider;
-use Geocoder\Provider\Provider;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author mtm
@@ -33,7 +34,7 @@ final class OpenCage extends AbstractHttpProvider implements Provider
/**
* @var string
*/
- const GEOCODE_ENDPOINT_URL = 'https://api.opencagedata.com/geocode/v1/json?key=%s&query=%s&limit=%d&pretty=1';
+ public const GEOCODE_ENDPOINT_URL = 'https://api.opencagedata.com/geocode/v1/json?key=%s&query=%s&limit=%d&pretty=1';
/**
* @var string
@@ -41,10 +42,10 @@ final class OpenCage extends AbstractHttpProvider implements Provider
private $apiKey;
/**
- * @param HttpClient $client an HTTP adapter
- * @param string $apiKey an API key
+ * @param ClientInterface $client an HTTP adapter
+ * @param string $apiKey an API key
*/
- public function __construct(HttpClient $client, string $apiKey)
+ public function __construct(ClientInterface $client, string $apiKey)
{
if (empty($apiKey)) {
throw new InvalidCredentials('No API key provided.');
@@ -54,9 +55,6 @@ public function __construct(HttpClient $client, string $apiKey)
parent::__construct($client);
}
- /**
- * {@inheritdoc}
- */
public function geocodeQuery(GeocodeQuery $query): Collection
{
$address = $query->getText();
@@ -67,13 +65,19 @@ public function geocodeQuery(GeocodeQuery $query): Collection
}
$url = sprintf(self::GEOCODE_ENDPOINT_URL, $this->apiKey, urlencode($address), $query->getLimit());
+ if (null !== $countryCode = $query->getData('countrycode')) {
+ $url = sprintf('%s&countrycode=%s', $url, $countryCode);
+ }
+ if (null !== $bounds = $query->getBounds()) {
+ $url = sprintf('%s&bounds=%s,%s,%s,%s', $url, $bounds->getWest(), $bounds->getSouth(), $bounds->getEast(), $bounds->getNorth());
+ }
+ if (null !== $proximity = $query->getData('proximity')) {
+ $url = sprintf('%s&proximity=%s', $url, $proximity);
+ }
return $this->executeQuery($url, $query->getLocale());
}
- /**
- * {@inheritdoc}
- */
public function reverseQuery(ReverseQuery $query): Collection
{
$coordinates = $query->getCoordinates();
@@ -87,23 +91,15 @@ public function reverseQuery(ReverseQuery $query): Collection
return $this->geocodeQuery($geocodeQuery);
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'opencage';
}
/**
- * @param string $url
- * @param string|null $locale
- *
- * @return AddressCollection
- *
* @throws \Geocoder\Exception\Exception
*/
- private function executeQuery(string $url, string $locale = null): AddressCollection
+ private function executeQuery(string $url, ?string $locale = null): AddressCollection
{
if (null !== $locale) {
$url = sprintf('%s&language=%s', $url, $locale);
@@ -124,7 +120,7 @@ private function executeQuery(string $url, string $locale = null): AddressCollec
}
}
- if (!isset($json['total_results']) || $json['total_results'] == 0) {
+ if (!isset($json['total_results']) || 0 == $json['total_results']) {
return new AddressCollection([]);
}
@@ -136,91 +132,137 @@ private function executeQuery(string $url, string $locale = null): AddressCollec
$results = [];
foreach ($locations as $location) {
+ $builder = new AddressBuilder($this->getName());
+ $this->parseCoordinates($builder, $location);
+
+ $components = $location['components'];
+ $annotations = $location['annotations'];
+
+ $this->parseAdminsLevels($builder, $components);
+ $this->parseCountry($builder, $components);
+ $builder->setLocality($this->guessLocality($components));
+ $builder->setSubLocality($this->guessSubLocality($components));
+ $builder->setStreetNumber(isset($components['house_number']) ? $components['house_number'] : null);
+ $builder->setStreetName($this->guessStreetName($components));
+ $builder->setPostalCode(isset($components['postcode']) ? $components['postcode'] : null);
+ $builder->setTimezone(isset($annotations['timezone']['name']) ? $annotations['timezone']['name'] : null);
+
+ /** @var OpenCageAddress $address */
+ $address = $builder->build(OpenCageAddress::class);
+ $address = $address->withMGRS(isset($annotations['MGRS']) ? $annotations['MGRS'] : null);
+ $address = $address->withMaidenhead(isset($annotations['Maidenhead']) ? $annotations['Maidenhead'] : null);
+ $address = $address->withGeohash(isset($annotations['geohash']) ? $annotations['geohash'] : null);
+ $address = $address->withWhat3words(isset($annotations['what3words'], $annotations['what3words']['words']) ? $annotations['what3words']['words'] : null);
+ $address = $address->withFormattedAddress($location['formatted']);
+ $address = $address->withConfidence($location['confidence']);
+
+ $results[] = $address;
+ }
+
+ return new AddressCollection($results);
+ }
+
+ /**
+ * @param array $location
+ */
+ private function parseCoordinates(AddressBuilder $builder, array $location): void
+ {
+ $builder->setCoordinates($location['geometry']['lat'], $location['geometry']['lng']);
+
+ $bounds = [
+ 'south' => null,
+ 'west' => null,
+ 'north' => null,
+ 'east' => null,
+ ];
+
+ if (isset($location['bounds'])) {
$bounds = [
- 'south' => null,
- 'west' => null,
- 'north' => null,
- 'east' => null,
+ 'south' => $location['bounds']['southwest']['lat'],
+ 'west' => $location['bounds']['southwest']['lng'],
+ 'north' => $location['bounds']['northeast']['lat'],
+ 'east' => $location['bounds']['northeast']['lng'],
];
- if (isset($location['bounds'])) {
- $bounds = [
- 'south' => $location['bounds']['southwest']['lat'],
- 'west' => $location['bounds']['southwest']['lng'],
- 'north' => $location['bounds']['northeast']['lat'],
- 'east' => $location['bounds']['northeast']['lng'],
- ];
- }
+ }
- $comp = $location['components'];
+ $builder->setBounds(
+ $bounds['south'],
+ $bounds['west'],
+ $bounds['north'],
+ $bounds['east']
+ );
+ }
- $adminLevels = [];
- foreach (['state', 'county'] as $i => $component) {
- if (isset($comp[$component])) {
- $adminLevels[] = ['name' => $comp[$component], 'level' => $i + 1];
- }
- }
+ /**
+ * @param array $components
+ */
+ private function parseAdminsLevels(AddressBuilder $builder, array $components): void
+ {
+ if (isset($components['state'])) {
+ $stateCode = isset($components['state_code']) ? $components['state_code'] : null;
+ $builder->addAdminLevel(1, $components['state'], $stateCode);
+ }
- $results[] = Address::createFromArray([
- 'providedBy' => $this->getName(),
- 'latitude' => $location['geometry']['lat'],
- 'longitude' => $location['geometry']['lng'],
- 'bounds' => $bounds ?: [],
- 'streetNumber' => isset($comp['house_number']) ? $comp['house_number'] : null,
- 'streetName' => $this->guessStreetName($comp),
- 'subLocality' => $this->guessSubLocality($comp),
- 'locality' => $this->guessLocality($comp),
- 'postalCode' => isset($comp['postcode']) ? $comp['postcode'] : null,
- 'adminLevels' => $adminLevels,
- 'country' => isset($comp['country']) ? $comp['country'] : null,
- 'countryCode' => isset($comp['country_code']) ? strtoupper($comp['country_code']) : null,
- 'timezone' => isset($location['annotations']['timezone']['name']) ? $location['annotations']['timezone']['name'] : null,
- ]);
+ if (isset($components['county'])) {
+ $builder->addAdminLevel(2, $components['county']);
}
+ }
- return new AddressCollection($results);
+ /**
+ * @param array $components
+ */
+ private function parseCountry(AddressBuilder $builder, array $components): void
+ {
+ if (isset($components['country'])) {
+ $builder->setCountry($components['country']);
+ }
+
+ if (isset($components['country_code'])) {
+ $builder->setCountryCode(\strtoupper($components['country_code']));
+ }
}
/**
- * @param array $components
+ * @param array $components
*
- * @return null|string
+ * @return string|null
*/
protected function guessLocality(array $components)
{
- $localityKeys = ['city', 'town', 'village', 'hamlet'];
+ $localityKeys = ['city', 'town', 'municipality', 'village', 'hamlet', 'locality', 'croft'];
return $this->guessBestComponent($components, $localityKeys);
}
/**
- * @param array $components
+ * @param array $components
*
- * @return null|string
+ * @return string|null
*/
protected function guessStreetName(array $components)
{
- $streetNameKeys = ['road', 'street', 'street_name', 'residential'];
+ $streetNameKeys = ['road', 'footway', 'street', 'street_name', 'residential', 'path', 'pedestrian', 'road_reference', 'road_reference_intl'];
return $this->guessBestComponent($components, $streetNameKeys);
}
/**
- * @param array $components
+ * @param array $components
*
- * @return null|string
+ * @return string|null
*/
protected function guessSubLocality(array $components)
{
- $subLocalityKeys = ['suburb', 'neighbourhood', 'city_district'];
+ $subLocalityKeys = ['neighbourhood', 'suburb', 'city_district', 'district', 'quarter', 'houses', 'subdivision'];
return $this->guessBestComponent($components, $subLocalityKeys);
}
/**
- * @param array $components
- * @param array $keys
+ * @param array $components
+ * @param string[] $keys
*
- * @return null|string
+ * @return string|null
*/
protected function guessBestComponent(array $components, array $keys)
{
diff --git a/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_658d6162b974c7f12e3b339c31e4b3fd939158f4 b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_658d6162b974c7f12e3b339c31e4b3fd939158f4
new file mode 100644
index 000000000..0e956df1e
--- /dev/null
+++ b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_658d6162b974c7f12e3b339c31e4b3fd939158f4
@@ -0,0 +1,116 @@
+s:3492:"{
+ "documentation" : "https://opencagedata.com/api",
+ "licenses" : [
+ {
+ "name" : "CC-BY-SA",
+ "url" : "https://creativecommons.org/licenses/by-sa/3.0/"
+ },
+ {
+ "name" : "ODbL",
+ "url" : "https://opendatacommons.org/licenses/odbl/summary/"
+ }
+ ],
+ "results" : [
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "50\u00b0 52' 49.04400'' N",
+ "lng" : "12\u00b0 4' 54.73200'' E"
+ },
+ "MGRS" : "33UTS9472740569",
+ "Maidenhead" : "JO60av91tg",
+ "Mercator" : {
+ "x" : 1344947.616,
+ "y" : 6566974.954
+ },
+ "OSM" : {
+ "url" : "https://www.openstreetmap.org/?mlat=50.88029&mlon=12.08187#map=17/50.88029/12.08187"
+ },
+ "callingcode" : 49,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ",",
+ "html_entity" : "€",
+ "iso_code" : "EUR",
+ "iso_numeric" : 978,
+ "name" : "Euro",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20ac",
+ "symbol_first" : 1,
+ "thousands_separator" : "."
+ },
+ "flag" : "\ud83c\udde9\ud83c\uddea",
+ "geohash" : "u3096ymxpvx0pm7kp4dj",
+ "qibla" : 133.33,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551074640,
+ "astronomical" : 1551068100,
+ "civil" : 1551072660,
+ "nautical" : 1551070380
+ },
+ "set" : {
+ "apparent" : 1551113160,
+ "astronomical" : 1551119760,
+ "civil" : 1551115140,
+ "nautical" : 1551117480
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Berlin",
+ "now_in_dst" : 0,
+ "offset_sec" : 3600,
+ "offset_string" : 100,
+ "short_name" : "CET"
+ },
+ "what3words" : {
+ "words" : "slogans.tone.dodging"
+ }
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 50.9673837,
+ "lng" : 12.1546343
+ },
+ "southwest" : {
+ "lat" : 50.8090048,
+ "lng" : 12.0107396
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "DE",
+ "ISO_3166-1_alpha-3" : "DEU",
+ "_type" : "city",
+ "continent" : "Europe",
+ "country" : "Germany",
+ "country_code" : "de",
+ "political_union" : "European Union",
+ "state" : "Free Thuringia",
+ "town" : "Gera"
+ },
+ "confidence" : 6,
+ "formatted" : "Gera, Free Thuringia, Germany",
+ "geometry" : {
+ "lat" : 50.88029,
+ "lng" : 12.08187
+ }
+ }
+ ],
+ "status" : {
+ "code" : 200,
+ "message" : "OK"
+ },
+ "stay_informed" : {
+ "blog" : "https://blog.opencagedata.com",
+ "twitter" : "https://twitter.com/opencagedata"
+ },
+ "thanks" : "For using an OpenCage Data API",
+ "timestamp" : {
+ "created_http" : "Mon, 25 Feb 2019 15:30:47 GMT",
+ "created_unix" : 1551108647
+ },
+ "total_results" : 1
+}
+";
\ No newline at end of file
diff --git a/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_8127723d4501e22d1146796e697e16227d2030b8 b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_8127723d4501e22d1146796e697e16227d2030b8
new file mode 100644
index 000000000..d6f81c3c7
--- /dev/null
+++ b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_8127723d4501e22d1146796e697e16227d2030b8
@@ -0,0 +1,192 @@
+s:6073:"{
+ "documentation" : "https://opencagedata.com/api",
+ "licenses" : [
+ {
+ "name" : "CC-BY-SA",
+ "url" : "https://creativecommons.org/licenses/by-sa/3.0/"
+ },
+ {
+ "name" : "ODbL",
+ "url" : "https://opendatacommons.org/licenses/odbl/summary/"
+ }
+ ],
+ "results" : [
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "8\u00b0 39' 23.09220'' S",
+ "lng" : "122\u00b0 4' 13.75824'' E"
+ },
+ "MGRS" : "51LUL9773443008",
+ "Maidenhead" : "PI11ai82ll",
+ "Mercator" : {
+ "x" : 13588824.61,
+ "y" : -960887.982
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?node=1271856027#map=17/-8.65641/122.07049",
+ "url" : "https://www.openstreetmap.org/?mlat=-8.65641&mlon=122.07049#map=17/-8.65641/122.07049"
+ },
+ "callingcode" : 62,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ",",
+ "html_entity" : "",
+ "iso_code" : "IDR",
+ "iso_numeric" : 360,
+ "name" : "Indonesian Rupiah",
+ "smallest_denomination" : 5000,
+ "subunit" : "Sen",
+ "subunit_to_unit" : 100,
+ "symbol" : "Rp",
+ "symbol_first" : 1,
+ "thousands_separator" : "."
+ },
+ "flag" : "\ud83c\uddee\ud83c\udde9",
+ "geohash" : "qwqycb1h8x3khhxbysdj",
+ "qibla" : 292.39,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551131820,
+ "astronomical" : 1551127560,
+ "civil" : 1551130560,
+ "nautical" : 1551129060
+ },
+ "set" : {
+ "apparent" : 1551089700,
+ "astronomical" : 1551093960,
+ "civil" : 1551090960,
+ "nautical" : 1551092460
+ }
+ },
+ "timezone" : {
+ "name" : "Asia/Makassar",
+ "now_in_dst" : 0,
+ "offset_sec" : 28800,
+ "offset_string" : 800,
+ "short_name" : "WITA"
+ },
+ "what3words" : {
+ "words" : "farmlands.overtaken.orbits"
+ }
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : -8.6364145,
+ "lng" : 122.0904884
+ },
+ "southwest" : {
+ "lat" : -8.6764145,
+ "lng" : 122.0504884
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "ID",
+ "ISO_3166-1_alpha-3" : "IDN",
+ "_type" : "village",
+ "continent" : "Asia",
+ "country" : "Indonesia",
+ "country_code" : "id",
+ "county" : "Gera",
+ "state" : "East Nusa Tenggara",
+ "village" : "Gera"
+ },
+ "confidence" : 7,
+ "formatted" : "Gera, Indonesia",
+ "geometry" : {
+ "lat" : -8.6564145,
+ "lng" : 122.0704884
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "50\u00b0 53' 24.36000'' N",
+ "lng" : "12\u00b0 6' 6.84000'' E"
+ },
+ "MGRS" : "33UTS9617941605",
+ "Maidenhead" : "JO60bv23fo",
+ "Mercator" : {
+ "x" : 1347177.346,
+ "y" : 6568701.319
+ },
+ "OSM" : {
+ "url" : "https://www.openstreetmap.org/?mlat=50.89010&mlon=12.10190#map=17/50.89010/12.10190"
+ },
+ "callingcode" : 49,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ",",
+ "html_entity" : "€",
+ "iso_code" : "EUR",
+ "iso_numeric" : 978,
+ "name" : "Euro",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20ac",
+ "symbol_first" : 1,
+ "thousands_separator" : "."
+ },
+ "flag" : "\ud83c\udde9\ud83c\uddea",
+ "geohash" : "u309e2k26dnxjg0gc3kr",
+ "qibla" : 133.36,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551074640,
+ "astronomical" : 1551068100,
+ "civil" : 1551072660,
+ "nautical" : 1551070380
+ },
+ "set" : {
+ "apparent" : 1551113160,
+ "astronomical" : 1551119760,
+ "civil" : 1551115140,
+ "nautical" : 1551117420
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Berlin",
+ "now_in_dst" : 0,
+ "offset_sec" : 3600,
+ "offset_string" : 100,
+ "short_name" : "CET"
+ },
+ "what3words" : {
+ "words" : "flinch.stubble.radar"
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "DE",
+ "ISO_3166-1_alpha-3" : "DEU",
+ "_type" : "postcode",
+ "continent" : "Europe",
+ "country" : "Germany",
+ "country_code" : "de",
+ "political_union" : "European Union",
+ "postcode" : "07546"
+ },
+ "confidence" : 7,
+ "formatted" : "07546, Germany",
+ "geometry" : {
+ "lat" : 50.8901,
+ "lng" : 12.1019
+ }
+ }
+ ],
+ "status" : {
+ "code" : 200,
+ "message" : "OK"
+ },
+ "stay_informed" : {
+ "blog" : "https://blog.opencagedata.com",
+ "twitter" : "https://twitter.com/opencagedata"
+ },
+ "thanks" : "For using an OpenCage Data API",
+ "timestamp" : {
+ "created_http" : "Mon, 25 Feb 2019 14:22:01 GMT",
+ "created_unix" : 1551104521
+ },
+ "total_results" : 2
+}
+";
\ No newline at end of file
diff --git a/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_d65542385285ca7f475e2a337596aecfc93207aa b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_d65542385285ca7f475e2a337596aecfc93207aa
new file mode 100644
index 000000000..da6b566d4
--- /dev/null
+++ b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_d65542385285ca7f475e2a337596aecfc93207aa
@@ -0,0 +1,496 @@
+s:16129:"{
+ "documentation" : "https://opencagedata.com/api",
+ "licenses" : [
+ {
+ "name" : "CC-BY-SA",
+ "url" : "https://creativecommons.org/licenses/by-sa/3.0/"
+ },
+ {
+ "name" : "ODbL",
+ "url" : "https://opendatacommons.org/licenses/odbl/summary/"
+ }
+ ],
+ "results" : [
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "31\u00b0 50' 44.57364'' N",
+ "lng" : "102\u00b0 22' 3.67320'' W"
+ },
+ "FIPS" : {
+ "county" : "48135",
+ "state" : "48"
+ },
+ "MGRS" : "13RGR4909026354",
+ "Maidenhead" : "DM81tu52vx",
+ "Mercator" : {
+ "x" : -11395518.791,
+ "y" : 3720532.562
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?relation=2336480#map=17/31.84571/-102.36769",
+ "url" : "https://www.openstreetmap.org/?mlat=31.84571&mlon=-102.36769#map=17/31.84571/-102.36769"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "9txjnt64sp80vmh11ctj",
+ "qibla" : 39.25,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551187080,
+ "astronomical" : 1551182220,
+ "civil" : 1551185640,
+ "nautical" : 1551183900
+ },
+ "set" : {
+ "apparent" : 1551141840,
+ "astronomical" : 1551146760,
+ "civil" : 1551143340,
+ "nautical" : 1551145020
+ }
+ },
+ "timezone" : {
+ "name" : "America/Chicago",
+ "now_in_dst" : 0,
+ "offset_sec" : -21600,
+ "offset_string" : -600,
+ "short_name" : "CST"
+ },
+ "what3words" : {
+ "words" : "translated.smudges.nimbly"
+ },
+ "wikidata" : "Q128361"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 31.9567349,
+ "lng" : -102.2481799
+ },
+ "southwest" : {
+ "lat" : 31.79747,
+ "lng" : -102.4420576
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "city",
+ "city" : "Odessa",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "Ector County",
+ "state" : "Texas",
+ "state_code" : "TX"
+ },
+ "confidence" : 5,
+ "formatted" : "Odessa, TX, United States of America",
+ "geometry" : {
+ "lat" : 31.8457149,
+ "lng" : -102.367687
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "39\u00b0 27' 26.40240'' N",
+ "lng" : "75\u00b0 39' 40.74624'' W"
+ },
+ "FIPS" : {
+ "county" : "10003",
+ "state" : "10"
+ },
+ "MGRS" : "18SVJ4310567738",
+ "Maidenhead" : "FM29ek09ps",
+ "Mercator" : {
+ "x" : -8422579.437,
+ "y" : 4760235.066
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?way=376077146#map=17/39.45733/-75.66132",
+ "url" : "https://www.openstreetmap.org/?mlat=39.45733&mlon=-75.66132#map=17/39.45733/-75.66132"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "dr40qqcpekpvmwj736ch",
+ "qibla" : 57.44,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551181080,
+ "astronomical" : 1551175740,
+ "civil" : 1551179460,
+ "nautical" : 1551177600
+ },
+ "set" : {
+ "apparent" : 1551221460,
+ "astronomical" : 1551140460,
+ "civil" : 1551223080,
+ "nautical" : 1551225000
+ }
+ },
+ "timezone" : {
+ "name" : "America/New_York",
+ "now_in_dst" : 0,
+ "offset_sec" : -18000,
+ "offset_string" : -500,
+ "short_name" : "EST"
+ },
+ "what3words" : {
+ "words" : "eyeliner.telegrams.caves"
+ },
+ "wikidata" : "Q754420"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 39.4626109,
+ "lng" : -75.6489727
+ },
+ "southwest" : {
+ "lat" : 39.4496208,
+ "lng" : -75.6722776
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "city",
+ "city" : "Odessa",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "New Castle County",
+ "postcode" : "19730",
+ "state" : "Delaware",
+ "state_code" : "DE"
+ },
+ "confidence" : 8,
+ "formatted" : "Odessa, DE 19730, United States of America",
+ "geometry" : {
+ "lat" : 39.457334,
+ "lng" : -75.6613184
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "46\u00b0 29' 6.87084'' N",
+ "lng" : "30\u00b0 44' 36.20184'' E"
+ },
+ "MGRS" : "36TUS2680150437",
+ "Maidenhead" : "KN56il96ek",
+ "Mercator" : {
+ "x" : 3422338.453,
+ "y" : 5827452.859
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?node=26150437#map=17/46.48524/30.74339",
+ "url" : "https://www.openstreetmap.org/?mlat=46.48524&mlon=30.74339#map=17/46.48524/30.74339"
+ },
+ "callingcode" : 380,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ".",
+ "html_entity" : "₴",
+ "iso_code" : "UAH",
+ "iso_numeric" : 980,
+ "name" : "Ukrainian Hryvnia",
+ "smallest_denomination" : 1,
+ "subunit" : "Kopiyka",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20b4",
+ "symbol_first" : 0,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\udde6",
+ "geohash" : "u8mb7w6shvhth9jw97wd",
+ "qibla" : 160.51,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551156120,
+ "astronomical" : 1551150120,
+ "civil" : 1551154320,
+ "nautical" : 1551152220
+ },
+ "set" : {
+ "apparent" : 1551195540,
+ "astronomical" : 1551201540,
+ "civil" : 1551197340,
+ "nautical" : 1551199440
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Kiev",
+ "now_in_dst" : 0,
+ "offset_sec" : 7200,
+ "offset_string" : 200,
+ "short_name" : "EET"
+ },
+ "what3words" : {
+ "words" : "uncle.gifts.padlock"
+ },
+ "wikidata" : "Q1874"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 46.6452419,
+ "lng" : 30.9033894
+ },
+ "southwest" : {
+ "lat" : 46.3252419,
+ "lng" : 30.5833894
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "UA",
+ "ISO_3166-1_alpha-3" : "UKR",
+ "_type" : "city",
+ "city" : "Odesa",
+ "continent" : "Europe",
+ "country" : "Ukraine",
+ "country_code" : "ua",
+ "county" : "Prymors'kyi Rayon",
+ "postcode" : "65026",
+ "state" : "Odesa Oblast"
+ },
+ "confidence" : 3,
+ "formatted" : "Odesa, 65026, Ukraine",
+ "geometry" : {
+ "lat" : 46.4852419,
+ "lng" : 30.7433894
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "46\u00b0 29' 9.19788'' N",
+ "lng" : "30\u00b0 41' 1.14360'' E"
+ },
+ "MGRS" : "36TUS2221850642",
+ "Maidenhead" : "KN56il26ao",
+ "Mercator" : {
+ "x" : 3415688.406,
+ "y" : 5827557.033
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?relation=1413934#map=17/46.48589/30.68365",
+ "url" : "https://www.openstreetmap.org/?mlat=46.48589&mlon=30.68365#map=17/46.48589/30.68365"
+ },
+ "callingcode" : 380,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ".",
+ "html_entity" : "₴",
+ "iso_code" : "UAH",
+ "iso_numeric" : 980,
+ "name" : "Ukrainian Hryvnia",
+ "smallest_denomination" : 1,
+ "subunit" : "Kopiyka",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20b4",
+ "symbol_first" : 0,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\udde6",
+ "geohash" : "u8mb6nrpuvskzny5435e",
+ "qibla" : 160.38,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551156180,
+ "astronomical" : 1551150120,
+ "civil" : 1551154320,
+ "nautical" : 1551152220
+ },
+ "set" : {
+ "apparent" : 1551195540,
+ "astronomical" : 1551201540,
+ "civil" : 1551197340,
+ "nautical" : 1551199440
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Kiev",
+ "now_in_dst" : 0,
+ "offset_sec" : 7200,
+ "offset_string" : 200,
+ "short_name" : "EET"
+ },
+ "what3words" : {
+ "words" : "teaches.successor.reissued"
+ },
+ "wikidata" : "Q1874"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 46.6291187,
+ "lng" : 30.8313753
+ },
+ "southwest" : {
+ "lat" : 46.342707,
+ "lng" : 30.6114013
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "UA",
+ "ISO_3166-1_alpha-3" : "UKR",
+ "_type" : "county",
+ "continent" : "Europe",
+ "country" : "Ukraine",
+ "country_code" : "ua",
+ "county" : "Odesa",
+ "state" : "Odesa Oblast"
+ },
+ "confidence" : 4,
+ "formatted" : "Odesa, Ukraine",
+ "geometry" : {
+ "lat" : 46.4858883,
+ "lng" : 30.683651
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "28\u00b0 10' 59.59920'' N",
+ "lng" : "82\u00b0 33' 14.50044'' W"
+ },
+ "FIPS" : {
+ "county" : "12101",
+ "state" : "12"
+ },
+ "MGRS" : "17RLM4745218476",
+ "Maidenhead" : "EL88re33mx",
+ "Mercator" : {
+ "x" : -9189872.351,
+ "y" : 3251917.748
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?way=33612675#map=17/28.18322/-82.55403",
+ "url" : "https://www.openstreetmap.org/?mlat=28.18322&mlon=-82.55403#map=17/28.18322/-82.55403"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "djj236tgptr5y0r83sbe",
+ "qibla" : 54.66,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551182160,
+ "astronomical" : 1551177480,
+ "civil" : 1551180780,
+ "nautical" : 1551179160
+ },
+ "set" : {
+ "apparent" : 1551223680,
+ "astronomical" : 1551141960,
+ "civil" : 1551225120,
+ "nautical" : 1551140340
+ }
+ },
+ "timezone" : {
+ "name" : "America/New_York",
+ "now_in_dst" : 0,
+ "offset_sec" : -18000,
+ "offset_string" : -500,
+ "short_name" : "EST"
+ },
+ "what3words" : {
+ "words" : "january.popping.tilt"
+ },
+ "wikidata" : "Q2149988"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 28.194999,
+ "lng" : -82.515912
+ },
+ "southwest" : {
+ "lat" : 28.1727024,
+ "lng" : -82.592524
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "village",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "Pasco County",
+ "hamlet" : "Gulf Pine",
+ "locality" : "Odessa",
+ "state" : "Florida",
+ "state_code" : "FL"
+ },
+ "confidence" : 7,
+ "formatted" : "Gulf Pine, FL, United States of America",
+ "geometry" : {
+ "lat" : 28.183222,
+ "lng" : -82.5540279
+ }
+ }
+ ],
+ "status" : {
+ "code" : 200,
+ "message" : "OK"
+ },
+ "stay_informed" : {
+ "blog" : "https://blog.opencagedata.com",
+ "twitter" : "https://twitter.com/opencagedata"
+ },
+ "thanks" : "For using an OpenCage Data API",
+ "timestamp" : {
+ "created_http" : "Tue, 26 Feb 2019 07:46:30 GMT",
+ "created_unix" : 1551167190
+ },
+ "total_results" : 5
+}
+";
\ No newline at end of file
diff --git a/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_dbd9478d843c14f73f89986daf57a40250925942 b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_dbd9478d843c14f73f89986daf57a40250925942
new file mode 100644
index 000000000..674285793
--- /dev/null
+++ b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_dbd9478d843c14f73f89986daf57a40250925942
@@ -0,0 +1,105 @@
+s:3186:"{
+ "documentation" : "https://opencagedata.com/api",
+ "licenses" : [
+ {
+ "name" : "CC-BY-SA",
+ "url" : "https://creativecommons.org/licenses/by-sa/3.0/"
+ },
+ {
+ "name" : "ODbL",
+ "url" : "https://opendatacommons.org/licenses/odbl/summary/"
+ }
+ ],
+ "results" : [
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "50\u00b0 53' 24.36000'' N",
+ "lng" : "12\u00b0 6' 6.84000'' E"
+ },
+ "MGRS" : "33UTS9617941605",
+ "Maidenhead" : "JO60bv23fo",
+ "Mercator" : {
+ "x" : 1347177.346,
+ "y" : 6568701.319
+ },
+ "OSM" : {
+ "url" : "https://www.openstreetmap.org/?mlat=50.89010&mlon=12.10190#map=17/50.89010/12.10190"
+ },
+ "callingcode" : 49,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ",",
+ "html_entity" : "€",
+ "iso_code" : "EUR",
+ "iso_numeric" : 978,
+ "name" : "Euro",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20ac",
+ "symbol_first" : 1,
+ "thousands_separator" : "."
+ },
+ "flag" : "\ud83c\udde9\ud83c\uddea",
+ "geohash" : "u309e2k26dnxjg0gc3kr",
+ "qibla" : 133.36,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551074640,
+ "astronomical" : 1551068100,
+ "civil" : 1551072660,
+ "nautical" : 1551070380
+ },
+ "set" : {
+ "apparent" : 1551113160,
+ "astronomical" : 1551119760,
+ "civil" : 1551115140,
+ "nautical" : 1551117420
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Berlin",
+ "now_in_dst" : 0,
+ "offset_sec" : 3600,
+ "offset_string" : 100,
+ "short_name" : "CET"
+ },
+ "what3words" : {
+ "words" : "flinch.stubble.radar"
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "DE",
+ "ISO_3166-1_alpha-3" : "DEU",
+ "_type" : "postcode",
+ "continent" : "Europe",
+ "country" : "Germany",
+ "country_code" : "de",
+ "political_union" : "European Union",
+ "postcode" : "07546"
+ },
+ "confidence" : 7,
+ "formatted" : "07546, Germany",
+ "geometry" : {
+ "lat" : 50.8901,
+ "lng" : 12.1019
+ }
+ }
+ ],
+ "status" : {
+ "code" : 200,
+ "message" : "OK"
+ },
+ "stay_informed" : {
+ "blog" : "https://blog.opencagedata.com",
+ "twitter" : "https://twitter.com/opencagedata"
+ },
+ "thanks" : "For using an OpenCage Data API",
+ "timestamp" : {
+ "created_http" : "Mon, 25 Feb 2019 14:22:01 GMT",
+ "created_unix" : 1551104521
+ },
+ "total_results" : 1
+}
+";
\ No newline at end of file
diff --git a/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_fe2c4178e95d6ffc0a296b72c20d69ee5b7cdd52 b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_fe2c4178e95d6ffc0a296b72c20d69ee5b7cdd52
new file mode 100644
index 000000000..bfd8c8b04
--- /dev/null
+++ b/src/Provider/OpenCage/Tests/.cached_responses/api.opencagedata.com_fe2c4178e95d6ffc0a296b72c20d69ee5b7cdd52
@@ -0,0 +1,496 @@
+s:16129:"{
+ "documentation" : "https://opencagedata.com/api",
+ "licenses" : [
+ {
+ "name" : "CC-BY-SA",
+ "url" : "https://creativecommons.org/licenses/by-sa/3.0/"
+ },
+ {
+ "name" : "ODbL",
+ "url" : "https://opendatacommons.org/licenses/odbl/summary/"
+ }
+ ],
+ "results" : [
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "46\u00b0 29' 6.87084'' N",
+ "lng" : "30\u00b0 44' 36.20184'' E"
+ },
+ "MGRS" : "36TUS2680150437",
+ "Maidenhead" : "KN56il96ek",
+ "Mercator" : {
+ "x" : 3422338.453,
+ "y" : 5827452.859
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?node=26150437#map=17/46.48524/30.74339",
+ "url" : "https://www.openstreetmap.org/?mlat=46.48524&mlon=30.74339#map=17/46.48524/30.74339"
+ },
+ "callingcode" : 380,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ".",
+ "html_entity" : "₴",
+ "iso_code" : "UAH",
+ "iso_numeric" : 980,
+ "name" : "Ukrainian Hryvnia",
+ "smallest_denomination" : 1,
+ "subunit" : "Kopiyka",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20b4",
+ "symbol_first" : 0,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\udde6",
+ "geohash" : "u8mb7w6shvhth9jw97wd",
+ "qibla" : 160.51,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551156120,
+ "astronomical" : 1551150120,
+ "civil" : 1551154320,
+ "nautical" : 1551152220
+ },
+ "set" : {
+ "apparent" : 1551195540,
+ "astronomical" : 1551201540,
+ "civil" : 1551197340,
+ "nautical" : 1551199440
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Kiev",
+ "now_in_dst" : 0,
+ "offset_sec" : 7200,
+ "offset_string" : 200,
+ "short_name" : "EET"
+ },
+ "what3words" : {
+ "words" : "uncle.gifts.padlock"
+ },
+ "wikidata" : "Q1874"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 46.6452419,
+ "lng" : 30.9033894
+ },
+ "southwest" : {
+ "lat" : 46.3252419,
+ "lng" : 30.5833894
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "UA",
+ "ISO_3166-1_alpha-3" : "UKR",
+ "_type" : "city",
+ "city" : "Odesa",
+ "continent" : "Europe",
+ "country" : "Ukraine",
+ "country_code" : "ua",
+ "county" : "Prymors'kyi Rayon",
+ "postcode" : "65026",
+ "state" : "Odesa Oblast"
+ },
+ "confidence" : 3,
+ "formatted" : "Odesa, 65026, Ukraine",
+ "geometry" : {
+ "lat" : 46.4852419,
+ "lng" : 30.7433894
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "46\u00b0 29' 9.19788'' N",
+ "lng" : "30\u00b0 41' 1.14360'' E"
+ },
+ "MGRS" : "36TUS2221850642",
+ "Maidenhead" : "KN56il26ao",
+ "Mercator" : {
+ "x" : 3415688.406,
+ "y" : 5827557.033
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?relation=1413934#map=17/46.48589/30.68365",
+ "url" : "https://www.openstreetmap.org/?mlat=46.48589&mlon=30.68365#map=17/46.48589/30.68365"
+ },
+ "callingcode" : 380,
+ "currency" : {
+ "alternate_symbols" : [],
+ "decimal_mark" : ".",
+ "html_entity" : "₴",
+ "iso_code" : "UAH",
+ "iso_numeric" : 980,
+ "name" : "Ukrainian Hryvnia",
+ "smallest_denomination" : 1,
+ "subunit" : "Kopiyka",
+ "subunit_to_unit" : 100,
+ "symbol" : "\u20b4",
+ "symbol_first" : 0,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\udde6",
+ "geohash" : "u8mb6nrpuvskzny5435e",
+ "qibla" : 160.38,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551156180,
+ "astronomical" : 1551150120,
+ "civil" : 1551154320,
+ "nautical" : 1551152220
+ },
+ "set" : {
+ "apparent" : 1551195540,
+ "astronomical" : 1551201540,
+ "civil" : 1551197340,
+ "nautical" : 1551199440
+ }
+ },
+ "timezone" : {
+ "name" : "Europe/Kiev",
+ "now_in_dst" : 0,
+ "offset_sec" : 7200,
+ "offset_string" : 200,
+ "short_name" : "EET"
+ },
+ "what3words" : {
+ "words" : "teaches.successor.reissued"
+ },
+ "wikidata" : "Q1874"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 46.6291187,
+ "lng" : 30.8313753
+ },
+ "southwest" : {
+ "lat" : 46.342707,
+ "lng" : 30.6114013
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "UA",
+ "ISO_3166-1_alpha-3" : "UKR",
+ "_type" : "county",
+ "continent" : "Europe",
+ "country" : "Ukraine",
+ "country_code" : "ua",
+ "county" : "Odesa",
+ "state" : "Odesa Oblast"
+ },
+ "confidence" : 4,
+ "formatted" : "Odesa, Ukraine",
+ "geometry" : {
+ "lat" : 46.4858883,
+ "lng" : 30.683651
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "39\u00b0 27' 26.40240'' N",
+ "lng" : "75\u00b0 39' 40.74624'' W"
+ },
+ "FIPS" : {
+ "county" : "10003",
+ "state" : "10"
+ },
+ "MGRS" : "18SVJ4310567738",
+ "Maidenhead" : "FM29ek09ps",
+ "Mercator" : {
+ "x" : -8422579.437,
+ "y" : 4760235.066
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?way=376077146#map=17/39.45733/-75.66132",
+ "url" : "https://www.openstreetmap.org/?mlat=39.45733&mlon=-75.66132#map=17/39.45733/-75.66132"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "dr40qqcpekpvmwj736ch",
+ "qibla" : 57.44,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551181080,
+ "astronomical" : 1551175740,
+ "civil" : 1551179460,
+ "nautical" : 1551177600
+ },
+ "set" : {
+ "apparent" : 1551221460,
+ "astronomical" : 1551140460,
+ "civil" : 1551223080,
+ "nautical" : 1551225000
+ }
+ },
+ "timezone" : {
+ "name" : "America/New_York",
+ "now_in_dst" : 0,
+ "offset_sec" : -18000,
+ "offset_string" : -500,
+ "short_name" : "EST"
+ },
+ "what3words" : {
+ "words" : "eyeliner.telegrams.caves"
+ },
+ "wikidata" : "Q754420"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 39.4626109,
+ "lng" : -75.6489727
+ },
+ "southwest" : {
+ "lat" : 39.4496208,
+ "lng" : -75.6722776
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "city",
+ "city" : "Odessa",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "New Castle County",
+ "postcode" : "19730",
+ "state" : "Delaware",
+ "state_code" : "DE"
+ },
+ "confidence" : 8,
+ "formatted" : "Odessa, DE 19730, United States of America",
+ "geometry" : {
+ "lat" : 39.457334,
+ "lng" : -75.6613184
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "31\u00b0 50' 44.57364'' N",
+ "lng" : "102\u00b0 22' 3.67320'' W"
+ },
+ "FIPS" : {
+ "county" : "48135",
+ "state" : "48"
+ },
+ "MGRS" : "13RGR4909026354",
+ "Maidenhead" : "DM81tu52vx",
+ "Mercator" : {
+ "x" : -11395518.791,
+ "y" : 3720532.562
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?relation=2336480#map=17/31.84571/-102.36769",
+ "url" : "https://www.openstreetmap.org/?mlat=31.84571&mlon=-102.36769#map=17/31.84571/-102.36769"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "9txjnt64sp80vmh11ctj",
+ "qibla" : 39.25,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551187080,
+ "astronomical" : 1551182220,
+ "civil" : 1551185640,
+ "nautical" : 1551183900
+ },
+ "set" : {
+ "apparent" : 1551141840,
+ "astronomical" : 1551146760,
+ "civil" : 1551143340,
+ "nautical" : 1551145020
+ }
+ },
+ "timezone" : {
+ "name" : "America/Chicago",
+ "now_in_dst" : 0,
+ "offset_sec" : -21600,
+ "offset_string" : -600,
+ "short_name" : "CST"
+ },
+ "what3words" : {
+ "words" : "translated.smudges.nimbly"
+ },
+ "wikidata" : "Q128361"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 31.9567349,
+ "lng" : -102.2481799
+ },
+ "southwest" : {
+ "lat" : 31.79747,
+ "lng" : -102.4420576
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "city",
+ "city" : "Odessa",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "Ector County",
+ "state" : "Texas",
+ "state_code" : "TX"
+ },
+ "confidence" : 5,
+ "formatted" : "Odessa, TX, United States of America",
+ "geometry" : {
+ "lat" : 31.8457149,
+ "lng" : -102.367687
+ }
+ },
+ {
+ "annotations" : {
+ "DMS" : {
+ "lat" : "28\u00b0 10' 59.59920'' N",
+ "lng" : "82\u00b0 33' 14.50044'' W"
+ },
+ "FIPS" : {
+ "county" : "12101",
+ "state" : "12"
+ },
+ "MGRS" : "17RLM4745218476",
+ "Maidenhead" : "EL88re33mx",
+ "Mercator" : {
+ "x" : -9189872.351,
+ "y" : 3251917.748
+ },
+ "OSM" : {
+ "edit_url" : "https://www.openstreetmap.org/edit?way=33612675#map=17/28.18322/-82.55403",
+ "url" : "https://www.openstreetmap.org/?mlat=28.18322&mlon=-82.55403#map=17/28.18322/-82.55403"
+ },
+ "callingcode" : 1,
+ "currency" : {
+ "alternate_symbols" : [
+ "US$"
+ ],
+ "decimal_mark" : ".",
+ "disambiguate_symbol" : "US$",
+ "html_entity" : "$",
+ "iso_code" : "USD",
+ "iso_numeric" : 840,
+ "name" : "United States Dollar",
+ "smallest_denomination" : 1,
+ "subunit" : "Cent",
+ "subunit_to_unit" : 100,
+ "symbol" : "$",
+ "symbol_first" : 1,
+ "thousands_separator" : ","
+ },
+ "flag" : "\ud83c\uddfa\ud83c\uddf8",
+ "geohash" : "djj236tgptr5y0r83sbe",
+ "qibla" : 54.66,
+ "sun" : {
+ "rise" : {
+ "apparent" : 1551182160,
+ "astronomical" : 1551177480,
+ "civil" : 1551180780,
+ "nautical" : 1551179160
+ },
+ "set" : {
+ "apparent" : 1551223680,
+ "astronomical" : 1551141960,
+ "civil" : 1551225120,
+ "nautical" : 1551140340
+ }
+ },
+ "timezone" : {
+ "name" : "America/New_York",
+ "now_in_dst" : 0,
+ "offset_sec" : -18000,
+ "offset_string" : -500,
+ "short_name" : "EST"
+ },
+ "what3words" : {
+ "words" : "january.popping.tilt"
+ },
+ "wikidata" : "Q2149988"
+ },
+ "bounds" : {
+ "northeast" : {
+ "lat" : 28.194999,
+ "lng" : -82.515912
+ },
+ "southwest" : {
+ "lat" : 28.1727024,
+ "lng" : -82.592524
+ }
+ },
+ "components" : {
+ "ISO_3166-1_alpha-2" : "US",
+ "ISO_3166-1_alpha-3" : "USA",
+ "_type" : "village",
+ "continent" : "North America",
+ "country" : "USA",
+ "country_code" : "us",
+ "county" : "Pasco County",
+ "hamlet" : "Gulf Pine",
+ "locality" : "Odessa",
+ "state" : "Florida",
+ "state_code" : "FL"
+ },
+ "confidence" : 7,
+ "formatted" : "Gulf Pine, FL, United States of America",
+ "geometry" : {
+ "lat" : 28.183222,
+ "lng" : -82.5540279
+ }
+ }
+ ],
+ "status" : {
+ "code" : 200,
+ "message" : "OK"
+ },
+ "stay_informed" : {
+ "blog" : "https://blog.opencagedata.com",
+ "twitter" : "https://twitter.com/opencagedata"
+ },
+ "thanks" : "For using an OpenCage Data API",
+ "timestamp" : {
+ "created_http" : "Tue, 26 Feb 2019 07:46:30 GMT",
+ "created_unix" : 1551167190
+ },
+ "total_results" : 5
+}
+";
\ No newline at end of file
diff --git a/src/Provider/OpenCage/Tests/IntegrationTest.php b/src/Provider/OpenCage/Tests/IntegrationTest.php
index 8efd92309..3ad4930c7 100644
--- a/src/Provider/OpenCage/Tests/IntegrationTest.php
+++ b/src/Provider/OpenCage/Tests/IntegrationTest.php
@@ -14,31 +14,32 @@
use Geocoder\IntegrationTest\ProviderIntegrationTest;
use Geocoder\Provider\OpenCage\OpenCage;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Tobias Nyholm
*/
class IntegrationTest extends ProviderIntegrationTest
{
- protected $skippedTests = [
+ protected array $skippedTests = [
'testReverseQueryWithNoResults' => 'There is a null island',
];
- protected $testIpv4 = false;
- protected $testIpv6 = false;
+ protected bool $testIpv4 = false;
- protected function createProvider(HttpClient $httpClient)
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
{
return new OpenCage($httpClient, $this->getApiKey());
}
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- protected function getApiKey()
+ protected function getApiKey(): string
{
return $_SERVER['OPENCAGE_API_KEY'];
}
diff --git a/src/Provider/OpenCage/Tests/OpenCageTest.php b/src/Provider/OpenCage/Tests/OpenCageTest.php
index 0a856916d..acd91e2ac 100644
--- a/src/Provider/OpenCage/Tests/OpenCageTest.php
+++ b/src/Provider/OpenCage/Tests/OpenCageTest.php
@@ -14,28 +14,30 @@
use Geocoder\Collection;
use Geocoder\IntegrationTest\BaseTestCase;
-use Geocoder\Location;
+use Geocoder\Model\AddressCollection;
+use Geocoder\Model\Bounds;
+use Geocoder\Provider\OpenCage\Model\OpenCageAddress;
+use Geocoder\Provider\OpenCage\OpenCage;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Provider\OpenCage\OpenCage;
/**
* @author mtm
*/
class OpenCageTest extends BaseTestCase
{
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- public function testGetName()
+ public function testGetName(): void
{
$provider = new OpenCage($this->getMockedHttpClient(), 'api_key');
$this->assertEquals('opencage', $provider->getName());
}
- public function testSslSchema()
+ public function testSslSchema(): void
{
$provider = new OpenCage($this->getMockedHttpClient('{}'), 'api_key');
$result = $provider->geocodeQuery(GeocodeQuery::create('foobar'));
@@ -44,7 +46,7 @@ public function testSslSchema()
$this->assertEquals(0, $result->count());
}
- public function testGeocodeWithRealAddress()
+ public function testGeocodeWithRealAddress(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -53,14 +55,14 @@ public function testGeocodeWithRealAddress()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('10 avenue Gambetta, Paris, France'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(2, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(48.866205, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.389089, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(48.866205, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.389089, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
$this->assertEquals(48.863142699999997, $result->getBounds()->getSouth());
$this->assertEquals(2.3890394000000001, $result->getBounds()->getWest());
@@ -76,9 +78,15 @@ public function testGeocodeWithRealAddress()
$this->assertEquals('France', $result->getCountry()->getName());
$this->assertEquals('FR', $result->getCountry()->getCode());
$this->assertEquals('Europe/Paris', $result->getTimezone());
+ $this->assertEquals('31UDQ5519412427', $result->getMGRS());
+ $this->assertEquals('JN18eu67qd', $result->getMaidenhead());
+ $this->assertEquals('u09tyr78tz64jdcgfnhe', $result->getGeohash());
+ $this->assertEquals('listed.emphasis.greeting', $result->getWhat3words());
+ $this->assertEquals('10 Avenue Gambetta, 75020 Paris, France', $result->getFormattedAddress());
+ $this->assertEquals(10, $result->getConfidence());
}
- public function testReverseWithRealCoordinates()
+ public function testReverseWithRealCoordinates(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -87,21 +95,21 @@ public function testReverseWithRealCoordinates()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(54.0484068, -2.7990345));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(1, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(54.0484068, $result->getCoordinates()->getLatitude(), '', 0.001);
- $this->assertEquals(-2.7990345, $result->getCoordinates()->getLongitude(), '', 0.001);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(54.0484068, $result->getCoordinates()->getLatitude(), 0.001);
+ $this->assertEqualsWithDelta(-2.7990345, $result->getCoordinates()->getLongitude(), 0.001);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(54.0484068, $result->getBounds()->getSouth(), '', 0.001);
- $this->assertEquals(-2.7998815, $result->getBounds()->getWest(), '', 0.001);
- $this->assertEquals(54.049472, $result->getBounds()->getNorth(), '', 0.001);
- $this->assertEquals(-2.7980925, $result->getBounds()->getEast(), '', 0.001);
+ $this->assertEqualsWithDelta(54.0484068, $result->getBounds()->getSouth(), 0.001);
+ $this->assertEqualsWithDelta(-2.7998815, $result->getBounds()->getWest(), 0.001);
+ $this->assertEqualsWithDelta(54.049472, $result->getBounds()->getNorth(), 0.001);
+ $this->assertEqualsWithDelta(-2.7980925, $result->getBounds()->getEast(), 0.001);
$this->assertNull($result->getStreetNumber());
- $this->assertNull($result->getStreetName());
+ $this->assertEquals('Lancaster Gate', $result->getStreetName());
$this->assertEquals('LA1 1LZ', $result->getPostalCode());
$this->assertEquals('Lancaster', $result->getLocality());
$this->assertCount(2, $result->getAdminLevels());
@@ -110,9 +118,14 @@ public function testReverseWithRealCoordinates()
$this->assertEquals('United Kingdom', $result->getCountry()->getName());
$this->assertEquals('GB', $result->getCountry()->getCode());
$this->assertEquals('Europe/London', $result->getTimezone());
+ $this->assertEquals('30UWE1316588979', $result->getMGRS());
+ $this->assertEquals('IO84ob41dr', $result->getMaidenhead());
+ $this->assertEquals('gcw52r3csd02c23bwucn', $result->getGeohash());
+ $this->assertEquals('heave.dock.wage', $result->getWhat3words());
+ $this->assertEquals('Saint Nicholas Arcades, Lancaster Gate, Lancaster LA1 1LZ, United Kingdom', $result->getFormattedAddress());
}
- public function testReverseWithVillage()
+ public function testReverseWithVillage(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -121,16 +134,16 @@ public function testReverseWithVillage()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(49.1390924, 1.6572462));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(1, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
$this->assertEquals('Bray-et-Lû', $result->getLocality());
}
- public function testGeocodeWithCity()
+ public function testGeocodeWithCity(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -139,35 +152,35 @@ public function testGeocodeWithCity()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('Hanover'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(52.374478, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(9.738553, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(52.374478, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(9.738553, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertEquals('Hanover', $result->getLocality());
$this->assertCount(2, $result->getAdminLevels());
$this->assertEquals('Region Hannover', $result->getAdminLevels()->get(2)->getName());
$this->assertEquals('Lower Saxony', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Germany', $result->getCountry()->getName());
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->get(1);
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(18.3840489, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(-78.131485, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(18.3840489, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-78.131485, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNull($result->getLocality());
$this->assertTrue($result->getAdminLevels()->has(2));
$this->assertEquals('Hanover', $result->getAdminLevels()->get(2)->getName());
$this->assertEquals('Jamaica', $result->getCountry()->getName());
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->get(2);
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(43.7033073, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(-72.2885663, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(43.7033073, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-72.2885663, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertEquals('Hanover', $result->getLocality());
$this->assertCount(2, $result->getAdminLevels());
$this->assertEquals('Grafton County', $result->getAdminLevels()->get(2)->getName());
@@ -175,7 +188,7 @@ public function testGeocodeWithCity()
$this->assertEquals('United States of America', $result->getCountry()->getName());
}
- public function testGeocodeWithCityDistrict()
+ public function testGeocodeWithCityDistrict(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -184,14 +197,14 @@ public function testGeocodeWithCityDistrict()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('Kalbacher Hauptstraße 10, 60437 Frankfurt, Germany'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(2, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(50.189062, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(8.636567, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
+ $this->assertEqualsWithDelta(50.189062, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(8.636567, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertEquals(10, $result->getStreetNumber());
$this->assertEquals('Kalbacher Hauptstraße', $result->getStreetName());
$this->assertEquals(60437, $result->getPostalCode());
@@ -204,7 +217,7 @@ public function testGeocodeWithCityDistrict()
$this->assertEquals('Europe/Berlin', $result->getTimezone());
}
- public function testGeocodeWithLocale()
+ public function testGeocodeWithLocale(): void
{
if (!isset($_SERVER['OPENCAGE_API_KEY'])) {
$this->markTestSkipped('You need to configure the OPENCAGE_API_KEY value in phpunit.xml');
@@ -213,12 +226,12 @@ public function testGeocodeWithLocale()
$provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('London')->withLocale('es'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var OpenCageAddress $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
+ $this->assertInstanceOf(OpenCageAddress::class, $result);
$this->assertEquals('Londres', $result->getLocality());
$this->assertCount(2, $result->getAdminLevels());
$this->assertEquals('Londres', $result->getAdminLevels()->get(2)->getName());
@@ -227,12 +240,64 @@ public function testGeocodeWithLocale()
$this->assertEquals('GB', $result->getCountry()->getCode());
}
- /**
- * @expectedException \Geocoder\Exception\QuotaExceeded
- * @expectedExceptionMessage Valid request but quota exceeded.
- */
- public function testGeocodeQuotaExceeded()
+ public function testAmbiguousResultCountryCode(): void
{
+ $provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Gera-Ost Gera 07546 DE'));
+
+ $this->assertCount(2, $results);
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('ID', $result->getCountry()->getCode());
+
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Gera-Ost Gera 07546 DE')->withData('countrycode', 'DE'));
+
+ $this->assertCount(1, $results);
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('DE', $result->getCountry()->getCode());
+ }
+
+ public function testAmbiguousResultBounds(): void
+ {
+ $provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Gera-Ost Gera 07546 DE'));
+
+ $this->assertCount(2, $results);
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('ID', $result->getCountry()->getCode());
+
+ $bounds = new Bounds(50.8613807, 11.7525627, 50.8850706, 12.511183);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Gera-Ost Gera 07546 DE')->withBounds($bounds));
+
+ $this->assertCount(1, $results);
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('DE', $result->getCountry()->getCode());
+ }
+
+ public function testAmbiguousResultProximity(): void
+ {
+ $provider = new OpenCage($this->getHttpClient($_SERVER['OPENCAGE_API_KEY']), $_SERVER['OPENCAGE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('odessa'));
+
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('UA', $result->getCountry()->getCode());
+
+ $results = $provider->geocodeQuery(GeocodeQuery::create('odessa')->withData('proximity', '31.918807,-102.474021'));
+
+ /** @var OpenCageAddress $result */
+ $result = $results->first();
+ $this->assertEquals('US', $result->getCountry()->getCode());
+ }
+
+ public function testGeocodeQuotaExceeded(): void
+ {
+ $this->expectException(\Geocoder\Exception\QuotaExceeded::class);
+ $this->expectExceptionMessage('Valid request but quota exceeded.');
+
$provider = new OpenCage(
$this->getMockedHttpClient(
'{
@@ -247,12 +312,11 @@ public function testGeocodeQuotaExceeded()
$provider->geocodeQuery(GeocodeQuery::create('New York'));
}
- /**
- * @expectedException \Geocoder\Exception\InvalidCredentials
- * @expectedExceptionMessage Invalid or missing api key.
- */
- public function testGeocodeInvalidApiKey()
+ public function testGeocodeInvalidApiKey(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidCredentials::class);
+ $this->expectExceptionMessage('Invalid or missing api key.');
+
$provider = new OpenCage(
$this->getMockedHttpClient(
'{
@@ -267,42 +331,38 @@ public function testGeocodeInvalidApiKey()
$provider->geocodeQuery(GeocodeQuery::create('New York'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The OpenCage provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv4()
+ public function testGeocodeWithLocalhostIPv4(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The OpenCage provider does not support IP addresses, only street addresses.');
+
$provider = new OpenCage($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The OpenCage provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv6()
+ public function testGeocodeWithLocalhostIPv6(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The OpenCage provider does not support IP addresses, only street addresses.');
+
$provider = new OpenCage($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('::1'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The OpenCage provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithRealIPv4()
+ public function testGeocodeWithRealIPv4(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The OpenCage provider does not support IP addresses, only street addresses.');
+
$provider = new OpenCage($this->getHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('74.200.247.59'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The OpenCage provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithRealIPv6()
+ public function testGeocodeWithRealIPv6(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The OpenCage provider does not support IP addresses, only street addresses.');
+
$provider = new OpenCage($this->getHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('::ffff:74.200.247.59'));
}
diff --git a/src/Provider/OpenCage/composer.json b/src/Provider/OpenCage/composer.json
index c3011fb25..91033445c 100644
--- a/src/Provider/OpenCage/composer.json
+++ b/src/Provider/OpenCage/composer.json
@@ -12,34 +12,35 @@
}
],
"require": {
- "php": "^7.0",
+ "php": "^8.0",
"geocoder-php/common-http": "^4.0",
- "willdurand/geocoder": "^4.0"
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
},
"require-dev": {
- "phpunit/phpunit": "^6.1",
- "geocoder-php/provider-integration-tests": "^1.0",
+ "geocoder-php/provider-integration-tests": "^1.6.3",
"php-http/message": "^1.0",
- "php-http/curl-client": "^1.7",
- "nyholm/psr7": "^0.2.2"
+ "phpunit/phpunit": "^9.6.11"
},
- "provide": {
- "geocoder-php/provider-implementation": "1.0"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
},
"autoload": {
- "psr-4": { "Geocoder\\Provider\\OpenCage\\": "" },
+ "psr-4": {
+ "Geocoder\\Provider\\OpenCage\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
}
}
diff --git a/src/Provider/OpenCage/phpunit.xml.dist b/src/Provider/OpenCage/phpunit.xml.dist
index ea1a1fe43..5e2acc6bc 100644
--- a/src/Provider/OpenCage/phpunit.xml.dist
+++ b/src/Provider/OpenCage/phpunit.xml.dist
@@ -1,29 +1,21 @@
-
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
- ./Tests
- ./vendor
-
-
-
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
diff --git a/src/Provider/OpenRouteService/.gitattributes b/src/Provider/OpenRouteService/.gitattributes
new file mode 100644
index 000000000..d04504afd
--- /dev/null
+++ b/src/Provider/OpenRouteService/.gitattributes
@@ -0,0 +1,4 @@
+.gitattributes export-ignore
+.travis.yml export-ignore
+phpunit.xml.dist export-ignore
+Tests/ export-ignore
diff --git a/src/Provider/OpenRouteService/.github/workflows/provider.yml b/src/Provider/OpenRouteService/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/OpenRouteService/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/OpenRouteService/.gitignore b/src/Provider/OpenRouteService/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/src/Provider/OpenRouteService/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Provider/OpenRouteService/CHANGELOG.md b/src/Provider/OpenRouteService/CHANGELOG.md
new file mode 100644
index 000000000..3f1181c91
--- /dev/null
+++ b/src/Provider/OpenRouteService/CHANGELOG.md
@@ -0,0 +1,48 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 1.4.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 1.3.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 1.2.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 1.1.0
+
+### Removed
+
+- Drop support for PHP < 7.2
+
+## 1.0.0
+
+First release of this provider.
diff --git a/src/Provider/OpenRouteService/LICENSE b/src/Provider/OpenRouteService/LICENSE
new file mode 100644
index 000000000..8aa8246ef
--- /dev/null
+++ b/src/Provider/OpenRouteService/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 — William Durand
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/Provider/OpenRouteService/OpenRouteService.php b/src/Provider/OpenRouteService/OpenRouteService.php
new file mode 100644
index 000000000..d0cf704a6
--- /dev/null
+++ b/src/Provider/OpenRouteService/OpenRouteService.php
@@ -0,0 +1,80 @@
+apiKey = $apiKey;
+ parent::__construct($client, self::API_URL, self::API_VERSION);
+
+ /*
+ * Openrouteservice does not use /v1 in first version, but plan to add
+ * /v2 in next version.
+ *
+ * @see https://ask.openrouteservice.org/t/pelias-version-in-api-url/1021
+ */
+ if (self::API_VERSION === 1) {
+ $this->root = self::API_URL;
+ }
+ }
+
+ public function geocodeQuery(GeocodeQuery $query): Collection
+ {
+ $url = $this->getGeocodeQueryUrl($query, [
+ 'api_key' => $this->apiKey,
+ ]);
+
+ return $this->executeQuery($url);
+ }
+
+ public function reverseQuery(ReverseQuery $query): Collection
+ {
+ $url = $this->getReverseQueryUrl($query, [
+ 'api_key' => $this->apiKey,
+ ]);
+
+ return $this->executeQuery($url);
+ }
+
+ public function getName(): string
+ {
+ return 'openrouteservice';
+ }
+}
diff --git a/src/Provider/OpenRouteService/README.md b/src/Provider/OpenRouteService/README.md
new file mode 100644
index 000000000..2b09cb9cd
--- /dev/null
+++ b/src/Provider/OpenRouteService/README.md
@@ -0,0 +1,27 @@
+# OpenRouteService Geocoder provider
+[](http://travis-ci.org/geocoder-php/openrouteservice-provider)
+[](https://packagist.org/packages/geocoder-php/openrouteservice-provider)
+[](https://packagist.org/packages/geocoder-php/openrouteservice-provider)
+[](https://packagist.org/packages/geocoder-php/openrouteservice-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/openrouteservice-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/openrouteservice-provider)
+[](LICENSE)
+
+This is the OpenRouteService 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.
+
+### Install
+
+```bash
+composer require geocoder-php/openrouteservice-provider
+```
+
+### API Documentation
+
+OpenRouteService uses the Pelias Geocoder under the hood. You can view it's [documentation here](https://github.com/pelias/documentation).
+The base API endpoint is .
+
+### Contribute
+
+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).
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_2852fa5ac257c58d7f8f587eb758c32afd493773 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_2852fa5ac257c58d7f8f587eb758c32afd493773
new file mode 100644
index 000000000..d6cc6e525
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_2852fa5ac257c58d7f8f587eb758c32afd493773
@@ -0,0 +1 @@
+s:6307:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"size":5,"private":false,"point.lat":49.1390924,"point.lon":1.6572462,"boundary.circle.lat":49.1390924,"boundary.circle.lon":1.6572462,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":10},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398940002},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[1.657246,49.139092]},"properties":{"id":"node/1564292039","gid":"openstreetmap:venue:node/1564292039","layer":"venue","source":"openstreetmap","source_id":"node/1564292039","name":"Les Jardins d'Épicure","confidence":1,"distance":0,"accuracy":"point","country":"France","country_gid":"whosonfirst:country:85633147","country_a":"FRA","macroregion":"Ile-of-France","macroregion_gid":"whosonfirst:macroregion:404227465","macroregion_a":"IF","region":"Val-d'Oise","region_gid":"whosonfirst:region:85683451","region_a":"VO","macrocounty":"Pontoise","macrocounty_gid":"whosonfirst:macrocounty:404228193","county":"Magny-En-Vexin","county_gid":"whosonfirst:county:102067333","localadmin":"Bray-Et-Lu","localadmin_gid":"whosonfirst:localadmin:404408311","locality":"Bray-et-Lû","locality_gid":"whosonfirst:locality:1125876391","neighbourhood":"Bus-Saint-Rémy","neighbourhood_gid":"whosonfirst:neighbourhood:1360698119","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Les Jardins d'Épicure, Bray-et-Lû, France"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.656323,49.139355]},"properties":{"id":"polyline:27466661","gid":"openstreetmap:street:polyline:27466661","layer":"street","source":"openstreetmap","source_id":"polyline:27466661","name":"Grande Rue","street":"Grande Rue","confidence":0.8,"distance":0.073,"accuracy":"centroid","country":"France","country_gid":"whosonfirst:country:85633147","country_a":"FRA","macroregion":"Ile-of-France","macroregion_gid":"whosonfirst:macroregion:404227465","macroregion_a":"IF","region":"Val-d'Oise","region_gid":"whosonfirst:region:85683451","region_a":"VO","macrocounty":"Pontoise","macrocounty_gid":"whosonfirst:macrocounty:404228193","county":"Magny-En-Vexin","county_gid":"whosonfirst:county:102067333","localadmin":"Bray-Et-Lu","localadmin_gid":"whosonfirst:localadmin:404408311","locality":"Bray-et-Lû","locality_gid":"whosonfirst:locality:1125876391","neighbourhood":"Bus-Saint-Rémy","neighbourhood_gid":"whosonfirst:neighbourhood:1360698119","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Grande Rue, Bray-et-Lû, France"},"bbox":[1.653428,49.138891,1.65918,49.139931]},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.657561,49.139926]},"properties":{"id":"polyline:6138715","gid":"openstreetmap:street:polyline:6138715","layer":"street","source":"openstreetmap","source_id":"polyline:6138715","name":"Impasse du Square","street":"Impasse du Square","confidence":0.8,"distance":0.096,"accuracy":"centroid","country":"France","country_gid":"whosonfirst:country:85633147","country_a":"FRA","macroregion":"Ile-of-France","macroregion_gid":"whosonfirst:macroregion:404227465","macroregion_a":"IF","region":"Val-d'Oise","region_gid":"whosonfirst:region:85683451","region_a":"VO","macrocounty":"Pontoise","macrocounty_gid":"whosonfirst:macrocounty:404228193","county":"Magny-En-Vexin","county_gid":"whosonfirst:county:102067333","localadmin":"Bray-Et-Lu","localadmin_gid":"whosonfirst:localadmin:404408311","locality":"Bray-et-Lû","locality_gid":"whosonfirst:locality:1125876391","neighbourhood":"Bus-Saint-Rémy","neighbourhood_gid":"whosonfirst:neighbourhood:1360698119","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Impasse du Square, Bray-et-Lû, France"},"bbox":[1.657446,49.139626,1.65768,49.140226]},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.655873,49.138949]},"properties":{"id":"polyline:6137636","gid":"openstreetmap:street:polyline:6137636","layer":"street","source":"openstreetmap","source_id":"polyline:6137636","name":"Impasse de l'Église","street":"Impasse de l'Église","confidence":0.7,"distance":0.101,"accuracy":"centroid","country":"France","country_gid":"whosonfirst:country:85633147","country_a":"FRA","macroregion":"Ile-of-France","macroregion_gid":"whosonfirst:macroregion:404227465","macroregion_a":"IF","region":"Val-d'Oise","region_gid":"whosonfirst:region:85683451","region_a":"VO","macrocounty":"Pontoise","macrocounty_gid":"whosonfirst:macrocounty:404228193","county":"Magny-En-Vexin","county_gid":"whosonfirst:county:102067333","localadmin":"Bray-Et-Lu","localadmin_gid":"whosonfirst:localadmin:404408311","locality":"Bray-et-Lû","locality_gid":"whosonfirst:locality:1125876391","neighbourhood":"Bus-Saint-Rémy","neighbourhood_gid":"whosonfirst:neighbourhood:1360698119","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Impasse de l'Église, Bray-et-Lû, France"},"bbox":[1.655817,49.138628,1.655926,49.13927]},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.658245,49.139867]},"properties":{"id":"node/463219576","gid":"openstreetmap:venue:node/463219576","layer":"venue","source":"openstreetmap","source_id":"node/463219576","name":"Pharmacie de l'Epte","confidence":0.7,"distance":0.113,"accuracy":"point","country":"France","country_gid":"whosonfirst:country:85633147","country_a":"FRA","macroregion":"Ile-of-France","macroregion_gid":"whosonfirst:macroregion:404227465","macroregion_a":"IF","region":"Val-d'Oise","region_gid":"whosonfirst:region:85683451","region_a":"VO","macrocounty":"Pontoise","macrocounty_gid":"whosonfirst:macrocounty:404228193","county":"Magny-En-Vexin","county_gid":"whosonfirst:county:102067333","localadmin":"Bray-Et-Lu","localadmin_gid":"whosonfirst:localadmin:404408311","locality":"Bray-et-Lû","locality_gid":"whosonfirst:locality:1125876391","neighbourhood":"Bus-Saint-Rémy","neighbourhood_gid":"whosonfirst:neighbourhood:1360698119","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Pharmacie de l'Epte, Bray-et-Lû, France","addendum":{"osm":{"wheelchair":"yes"}}}}],"bbox":[1.653428,49.138628,1.65918,49.140226]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_40238ca9d898547616baa907da6fa485f5e96f87 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_40238ca9d898547616baa907da6fa485f5e96f87
new file mode 100644
index 000000000..73dd552de
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_40238ca9d898547616baa907da6fa485f5e96f87
@@ -0,0 +1 @@
+s:4725:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"text":"Hanover","size":5,"layers":["venue","street","country","macroregion","region","county","localadmin","locality","borough","neighbourhood","continent","empire","dependency","macrocounty","macrohood","microhood","disputed","postalcode","ocean","marinearea"],"private":false,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":20,"parser":"libpostal","parsed_text":{"city":"hanover"}},"warnings":["performance optimization: excluding 'address' layer"],"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398940244},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[9.787455,52.379952]},"properties":{"id":"101748819","gid":"whosonfirst:locality:101748819","layer":"locality","source":"whosonfirst","source_id":"101748819","name":"Hanover","confidence":1,"match_type":"exact","accuracy":"centroid","country":"Germany","country_gid":"whosonfirst:country:85633111","country_a":"DEU","region":"Lower Saxony","region_gid":"whosonfirst:region:85682555","region_a":"NI","county":"Hannover","county_gid":"whosonfirst:county:102063941","county_a":"HA","localadmin":"Hannover","localadmin_gid":"whosonfirst:localadmin:1377686265","locality":"Hanover","locality_gid":"whosonfirst:locality:101748819","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Hanover, NI, Germany"},"bbox":[9.604728,52.305911,9.91856,52.453608]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-78.107687,18.401137]},"properties":{"id":"85672563","gid":"whosonfirst:region:85672563","layer":"region","source":"whosonfirst","source_id":"85672563","name":"Hanover","confidence":0.3,"match_type":"fallback","accuracy":"centroid","country":"Jamaica","country_gid":"whosonfirst:country:85632215","country_a":"JAM","region":"Hanover","region_gid":"whosonfirst:region:85672563","region_a":"HA","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Hanover, Jamaica"},"bbox":[-78.344668,18.307772,-77.909728,18.462863]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-76.72414,39.19289]},"properties":{"id":"1125874787","gid":"whosonfirst:locality:1125874787","layer":"locality","source":"whosonfirst","source_id":"1125874787","name":"Hanover","confidence":1,"match_type":"exact","accuracy":"centroid","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Maryland","region_gid":"whosonfirst:region:85688501","region_a":"MD","county":"Howard County","county_gid":"whosonfirst:county:102084263","county_a":"HO","locality":"Hanover","locality_gid":"whosonfirst:locality:1125874787","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Hanover, MD, USA"},"bbox":[-76.74414,39.17289,-76.70414,39.21289]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-70.81199,42.11316]},"properties":{"id":"1125765125","gid":"whosonfirst:locality:1125765125","layer":"locality","source":"whosonfirst","source_id":"1125765125","name":"Hanover","confidence":1,"match_type":"exact","accuracy":"centroid","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Massachusetts","region_gid":"whosonfirst:region:85688645","region_a":"MA","county":"Plymouth County","county_gid":"whosonfirst:county:102084367","county_a":"PL","localadmin":"Hanover","localadmin_gid":"whosonfirst:localadmin:404476511","locality":"Hanover","locality_gid":"whosonfirst:locality:1125765125","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Hanover, MA, USA"},"bbox":[-70.83199,42.09316,-70.79199,42.13316]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-76.983956,39.811775]},"properties":{"id":"101717245","gid":"whosonfirst:locality:101717245","layer":"locality","source":"whosonfirst","source_id":"101717245","name":"Hanover","confidence":1,"match_type":"exact","accuracy":"centroid","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"Pennsylvania","region_gid":"whosonfirst:region:85688481","region_a":"PA","county":"York County","county_gid":"whosonfirst:county:102080953","county_a":"YO","localadmin":"Hanover","localadmin_gid":"whosonfirst:localadmin:404487771","locality":"Hanover","locality_gid":"whosonfirst:locality:101717245","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Hanover, PA, USA"},"bbox":[-76.99965,39.791156,-76.963061,39.831769]}],"bbox":[-78.344668,18.307772,9.91856,52.453608]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_65c6b6dd290cf433627e7b6e72b6e3ae717356a1 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_65c6b6dd290cf433627e7b6e72b6e3ae717356a1
new file mode 100644
index 000000000..9c07f02bb
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_65c6b6dd290cf433627e7b6e72b6e3ae717356a1
@@ -0,0 +1 @@
+s:1737:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"text":"10 Downing St, London, UK","size":5,"private":false,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":20,"parser":"libpostal","parsed_text":{"housenumber":"10","street":"downing st","city":"london","country":"uk"}},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398939196},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-0.127611,51.503335]},"properties":{"id":"relation/1879842","gid":"openstreetmap:address:relation/1879842","layer":"address","source":"openstreetmap","source_id":"relation/1879842","name":"10 Downing Street","housenumber":"10","street":"Downing Street","postalcode":"SW1A 2AA","confidence":1,"match_type":"exact","accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Greater London","region_gid":"whosonfirst:region:1360698645","locality":"London","locality_gid":"whosonfirst:locality:101750367","borough":"Westminster","borough_gid":"whosonfirst:borough:1158857245","neighbourhood":"Whitehall","neighbourhood_gid":"whosonfirst:neighbourhood:85793317","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"10 Downing Street, London, England, United Kingdom","addendum":{"osm":{"wikidata":"Q169101","wikipedia":"en:10 Downing Street","website":"https://www.gov.uk/government/organisations/prime-ministers-office-10-downing-street"}}}}],"bbox":[-0.127611,51.503335,-0.127611,51.503335]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_6d63e36f25e65929745eafcd73d0ea36a038dad7 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_6d63e36f25e65929745eafcd73d0ea36a038dad7
new file mode 100644
index 000000000..e2602c5ad
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_6d63e36f25e65929745eafcd73d0ea36a038dad7
@@ -0,0 +1 @@
+s:5410:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"size":5,"private":false,"point.lat":38.900206,"point.lon":-77.036991,"boundary.circle.lat":38.900206,"boundary.circle.lon":-77.036991,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":10},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398939496},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.0367,38.9003]},"properties":{"id":"6482379","gid":"geonames:venue:6482379","layer":"venue","source":"geonames","source_id":"6482379","name":"The Hay Adams across from the White House","confidence":0.8,"distance":0.027,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"District of Columbia","region_gid":"whosonfirst:region:85688741","region_a":"DC","county":"District of Columbia","county_gid":"whosonfirst:county:1377370667","county_a":"DI","locality":"Washington","locality_gid":"whosonfirst:locality:85931779","neighbourhood":"Downtown","neighbourhood_gid":"whosonfirst:neighbourhood:85866033","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"The Hay Adams across from the White House, Washington, DC, USA","addendum":{"geonames":{"feature_code":"HTL"}}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.0372,38.90039]},"properties":{"id":"4140517","gid":"geonames:venue:4140517","layer":"venue","source":"geonames","source_id":"4140517","name":"Slidell House (historical)","confidence":0.8,"distance":0.027,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"District of Columbia","region_gid":"whosonfirst:region:85688741","region_a":"DC","county":"District of Columbia","county_gid":"whosonfirst:county:1377370667","county_a":"DI","locality":"Washington","locality_gid":"whosonfirst:locality:85931779","neighbourhood":"Downtown","neighbourhood_gid":"whosonfirst:neighbourhood:85866033","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Slidell House (historical), Washington, DC, USA","addendum":{"geonames":{"feature_code":"BLDG"}}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.03664,38.90011]},"properties":{"id":"4137678","gid":"geonames:venue:4137678","layer":"venue","source":"geonames","source_id":"4137678","name":"Bancroft House (historical)","confidence":0.8,"distance":0.032,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"District of Columbia","region_gid":"whosonfirst:region:85688741","region_a":"DC","county":"District of Columbia","county_gid":"whosonfirst:county:1377370667","county_a":"DI","locality":"Washington","locality_gid":"whosonfirst:locality:85931779","neighbourhood":"Lafayette Square","neighbourhood_gid":"whosonfirst:neighbourhood:1108724047","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Bancroft House (historical), Washington, DC, USA","addendum":{"geonames":{"feature_code":"BLDG"}}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.036945,38.900522]},"properties":{"id":"way/55326891","gid":"openstreetmap:venue:way/55326891","layer":"venue","source":"openstreetmap","source_id":"way/55326891","name":"Hay-Adams Hotel","housenumber":"800","street":"16th Street NW","postalcode":"20006","confidence":0.8,"distance":0.035,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"District of Columbia","region_gid":"whosonfirst:region:85688741","region_a":"DC","county":"District of Columbia","county_gid":"whosonfirst:county:1377370667","county_a":"DI","locality":"Washington","locality_gid":"whosonfirst:locality:85931779","neighbourhood":"Downtown","neighbourhood_gid":"whosonfirst:neighbourhood:85866033","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"Hay-Adams Hotel, Washington, DC, USA","addendum":{"osm":{"wikidata":"Q11861763","website":"https://www.hayadams.com/","phone":"202-638-6600"}}},"bbox":[-77.0371738,38.9003173,-77.0367231,38.9006934]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.036945,38.900522]},"properties":{"id":"way/55326891","gid":"openstreetmap:address:way/55326891","layer":"address","source":"openstreetmap","source_id":"way/55326891","name":"800 16th Street NW","housenumber":"800","street":"16th Street NW","postalcode":"20006","confidence":0.8,"distance":0.035,"accuracy":"point","country":"United States","country_gid":"whosonfirst:country:85633793","country_a":"USA","region":"District of Columbia","region_gid":"whosonfirst:region:85688741","region_a":"DC","county":"District of Columbia","county_gid":"whosonfirst:county:1377370667","county_a":"DI","locality":"Washington","locality_gid":"whosonfirst:locality:85931779","neighbourhood":"Downtown","neighbourhood_gid":"whosonfirst:neighbourhood:85866033","continent":"North America","continent_gid":"whosonfirst:continent:102191575","label":"800 16th Street NW, Washington, DC, USA","addendum":{"osm":{"wikidata":"Q11861763","website":"https://www.hayadams.com/","phone":"202-638-6600"}}}}],"bbox":[-77.0372,38.90011,-77.03664,38.9006934]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_894335d693a40d07a78d1835696d84e8c84e2419 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_894335d693a40d07a78d1835696d84e8c84e2419
new file mode 100644
index 000000000..ffdb25522
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_894335d693a40d07a78d1835696d84e8c84e2419
@@ -0,0 +1 @@
+s:1718:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"text":"242 Acklam Road, London, United Kingdom","size":5,"private":false,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":20,"parser":"libpostal","parsed_text":{"housenumber":"242","street":"acklam road","city":"london","country":"united kingdom"}},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398939660},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-0.202887,51.521315]},"properties":{"id":"way/526221246","gid":"openstreetmap:address:way/526221246","layer":"address","source":"openstreetmap","source_id":"way/526221246","name":"242 Acklam Road","housenumber":"242","street":"Acklam Road","postalcode":"W10 5JJ","confidence":1,"match_type":"exact","accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Greater London","region_gid":"whosonfirst:region:1360698645","locality":"London","locality_gid":"whosonfirst:locality:101750367","borough":"Kensington and Chelsea","borough_gid":"whosonfirst:borough:1158857317","neighbourhood":"North Kensington","neighbourhood_gid":"whosonfirst:neighbourhood:85789461","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"242 Acklam Road, London, England, United Kingdom","addendum":{"osm":{"wikidata":"Q7987154","wikipedia":"en:Westbourne Studios","website":"http://www.westbournestudios.com/"}}}}],"bbox":[-0.202887,51.521315,-0.202887,51.521315]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_95866f6596d9d58110747df278ad62043a53fccb b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_95866f6596d9d58110747df278ad62043a53fccb
new file mode 100644
index 000000000..c3be64b1a
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_95866f6596d9d58110747df278ad62043a53fccb
@@ -0,0 +1 @@
+s:809:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"text":"jsajhgsdkfjhsfkjhaldkadjaslgldasd","size":5,"layers":["venue","street","country","macroregion","region","county","localadmin","locality","borough","neighbourhood","continent","empire","dependency","macrocounty","macrohood","microhood","disputed","postalcode","ocean","marinearea"],"private":false,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":20,"parser":"pelias","parsed_text":{"subject":"jsajhgsdkfjhsfkjhaldkadjaslgldasd"}},"warnings":["performance optimization: excluding 'address' layer"],"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398939331},"type":"FeatureCollection","features":[]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_a58413da25f17fd91174690b172feeccb250cca4 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_a58413da25f17fd91174690b172feeccb250cca4
new file mode 100644
index 000000000..46aa8c9d7
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_a58413da25f17fd91174690b172feeccb250cca4
@@ -0,0 +1 @@
+s:5535:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"size":5,"private":false,"point.lat":54.0484068,"point.lon":-2.7990345,"boundary.circle.lat":54.0484068,"boundary.circle.lon":-2.7990345,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":10},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398939834},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.79913,54.048378]},"properties":{"id":"node/3930587961","gid":"openstreetmap:address:node/3930587961","layer":"address","source":"openstreetmap","source_id":"node/3930587961","name":"11 Ffrances Passage","housenumber":"11","street":"Ffrances Passage","postalcode":"LA1 1UG","confidence":0.9,"distance":0.007,"accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Lancashire","region_gid":"whosonfirst:region:1360698567","county":"Lancashire","county_gid":"whosonfirst:county:1360698817","county_a":"LAN","locality":"Lancaster","locality_gid":"whosonfirst:locality:101873271","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"11 Ffrances Passage, Lancaster, England, United Kingdom","addendum":{"osm":{"website":"https://stableendcurios.co.uk/","opening_hours":"Mo-Fr 10:00-16:30; Sa 09:30-16:30"}}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.79913,54.048378]},"properties":{"id":"node/3930587961","gid":"openstreetmap:venue:node/3930587961","layer":"venue","source":"openstreetmap","source_id":"node/3930587961","name":"Stable End Curios","housenumber":"11","street":"Ffrances Passage","postalcode":"LA1 1UG","confidence":0.9,"distance":0.007,"accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Lancashire","region_gid":"whosonfirst:region:1360698567","county":"Lancashire","county_gid":"whosonfirst:county:1360698817","county_a":"LAN","locality":"Lancaster","locality_gid":"whosonfirst:locality:101873271","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Stable End Curios, Lancaster, England, United Kingdom","addendum":{"osm":{"website":"https://stableendcurios.co.uk/","opening_hours":"Mo-Fr 10:00-16:30; Sa 09:30-16:30"}}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.798864,54.048381]},"properties":{"id":"way/568787583","gid":"openstreetmap:venue:way/568787583","layer":"venue","source":"openstreetmap","source_id":"way/568787583","name":"Collegian W.M.C","housenumber":"1","street":"Gage Street","postalcode":"LA1 1UH","confidence":0.8,"distance":0.012,"accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Lancashire","region_gid":"whosonfirst:region:1360698567","county":"Lancashire","county_gid":"whosonfirst:county:1360698817","county_a":"LAN","locality":"Lancaster","locality_gid":"whosonfirst:locality:101873271","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Collegian W.M.C, Lancaster, England, United Kingdom"},"bbox":[-2.7991032,54.0483796,-2.7988259,54.0484971]},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.798864,54.048381]},"properties":{"id":"way/568787583","gid":"openstreetmap:address:way/568787583","layer":"address","source":"openstreetmap","source_id":"way/568787583","name":"1 Gage Street","housenumber":"1","street":"Gage Street","postalcode":"LA1 1UH","confidence":0.8,"distance":0.012,"accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Lancashire","region_gid":"whosonfirst:region:1360698567","county":"Lancashire","county_gid":"whosonfirst:county:1360698817","county_a":"LAN","locality":"Lancaster","locality_gid":"whosonfirst:locality:101873271","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"1 Gage Street, Lancaster, England, United Kingdom"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.799226,54.048388]},"properties":{"id":"node/7972660790","gid":"openstreetmap:venue:node/7972660790","layer":"venue","source":"openstreetmap","source_id":"node/7972660790","name":"Evolve Tattoos","housenumber":"9","street":"Ffrances Passage","postalcode":"LA1 1UG","confidence":0.8,"distance":0.013,"accuracy":"point","country":"United Kingdom","country_gid":"whosonfirst:country:85633159","country_a":"GBR","macroregion":"England","macroregion_gid":"whosonfirst:macroregion:404227469","region":"Lancashire","region_gid":"whosonfirst:region:1360698567","county":"Lancashire","county_gid":"whosonfirst:county:1360698817","county_a":"LAN","locality":"Lancaster","locality_gid":"whosonfirst:locality:101873271","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Evolve Tattoos, Lancaster, England, United Kingdom","addendum":{"osm":{"website":"https://www.facebook.com/pages/category/Tattoo---Piercing-Shop/Evolve-Tattoo-and-Body-Arts-Studio-123157141032019/","opening_hours":"Tu-Sa 10:00-17:00"}}}}],"bbox":[-2.799226,54.048378,-2.7988259,54.0484971]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_dbef321627efcc45bf0d7463af0fd7dde4f7fda5 b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_dbef321627efcc45bf0d7463af0fd7dde4f7fda5
new file mode 100644
index 000000000..185314b37
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/.cached_responses/api.openrouteservice.org_dbef321627efcc45bf0d7463af0fd7dde4f7fda5
@@ -0,0 +1 @@
+s:2860:"{"geocoding":{"version":"0.2","attribution":"https://openrouteservice.org/terms-of-service/#attribution-geocode","query":{"text":"Kalbacher Hauptstraße 10, 60437 Frankfurt, Germany","size":5,"private":false,"lang":{"name":"English","iso6391":"en","iso6393":"eng","via":"default","defaulted":true},"querySize":20,"parser":"libpostal","parsed_text":{"street":"kalbacher hauptstraße","housenumber":"10","postalcode":"60437","city":"frankfurt","country":"germany"}},"engine":{"name":"Pelias","author":"Mapzen","version":"1.0"},"timestamp":1742398940428},"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[8.636816,50.189008]},"properties":{"id":"way/315515483","gid":"openstreetmap:address:way/315515483","layer":"address","source":"openstreetmap","source_id":"way/315515483","name":"Kalbacher Hauptstraße 10a","housenumber":"10a","street":"Kalbacher Hauptstraße","postalcode":"60437","confidence":1,"match_type":"exact","accuracy":"point","country":"Germany","country_gid":"whosonfirst:country:85633111","country_a":"DEU","region":"Hesse","region_gid":"whosonfirst:region:85682531","region_a":"HE","macrocounty":"Darmstadt Government Region","macrocounty_gid":"whosonfirst:macrocounty:404227581","county":"Frankfurt","county_gid":"whosonfirst:county:102063589","county_a":"FA","localadmin":"Frankfurt am Main","localadmin_gid":"whosonfirst:localadmin:1377692799","locality":"Frankfurt","locality_gid":"whosonfirst:locality:101913837","neighbourhood":"Römerstadt","neighbourhood_gid":"whosonfirst:neighbourhood:85796311","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Kalbacher Hauptstraße 10a, Frankfurt, HE, Germany"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[8.63661,50.189046]},"properties":{"id":"way/109282123","gid":"openstreetmap:address:way/109282123","layer":"address","source":"openstreetmap","source_id":"way/109282123","name":"Kalbacher Hauptstraße 10","housenumber":"10","street":"Kalbacher Hauptstraße","postalcode":"60437","confidence":1,"match_type":"exact","accuracy":"point","country":"Germany","country_gid":"whosonfirst:country:85633111","country_a":"DEU","region":"Hesse","region_gid":"whosonfirst:region:85682531","region_a":"HE","macrocounty":"Darmstadt Government Region","macrocounty_gid":"whosonfirst:macrocounty:404227581","county":"Frankfurt","county_gid":"whosonfirst:county:102063589","county_a":"FA","localadmin":"Frankfurt am Main","localadmin_gid":"whosonfirst:localadmin:1377692799","locality":"Frankfurt","locality_gid":"whosonfirst:locality:101913837","neighbourhood":"Römerstadt","neighbourhood_gid":"whosonfirst:neighbourhood:85796311","continent":"Europe","continent_gid":"whosonfirst:continent:102191581","label":"Kalbacher Hauptstraße 10, Frankfurt, HE, Germany"}}],"bbox":[8.63661,50.189008,8.636816,50.189046]}";
\ No newline at end of file
diff --git a/src/Provider/OpenRouteService/Tests/IntegrationTest.php b/src/Provider/OpenRouteService/Tests/IntegrationTest.php
new file mode 100644
index 000000000..a9c3bbaf8
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/IntegrationTest.php
@@ -0,0 +1,46 @@
+
+ */
+class IntegrationTest extends ProviderIntegrationTest
+{
+ protected array $skippedTests = [
+ 'testReverseQueryWithNoResults' => 'We weirdly find stuff here...',
+ ];
+
+ protected bool $testIpv4 = false;
+
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
+ {
+ return new OpenRouteService($httpClient, $this->getApiKey());
+ }
+
+ protected function getCacheDir(): string
+ {
+ return __DIR__.'/.cached_responses';
+ }
+
+ protected function getApiKey(): string
+ {
+ return $_SERVER['OPEN_ROUTE_SERVICE_API_KEY'];
+ }
+}
diff --git a/src/Provider/OpenRouteService/Tests/OpenRouteServiceTest.php b/src/Provider/OpenRouteService/Tests/OpenRouteServiceTest.php
new file mode 100644
index 000000000..d06fb457a
--- /dev/null
+++ b/src/Provider/OpenRouteService/Tests/OpenRouteServiceTest.php
@@ -0,0 +1,272 @@
+getMockedHttpClient(), 'api_key');
+ $this->assertEquals('openrouteservice', $provider->getName());
+ }
+
+ public function testGeocode(): void
+ {
+ $provider = new OpenRouteService($this->getMockedHttpClient('{}'), 'api_key');
+ $result = $provider->geocodeQuery(GeocodeQuery::create('foobar'));
+
+ $this->assertInstanceOf(Collection::class, $result);
+ $this->assertEquals(0, $result->count());
+ }
+
+ public function testGeocodeWithRealAddress(): void
+ {
+ if (!isset($_SERVER['OPEN_ROUTE_SERVICE_API_KEY'])) {
+ $this->markTestSkipped('You need to configure the OPEN_ROUTE_SERVICE_API_KEY value in phpunit.xml');
+ }
+
+ $provider = new OpenRouteService($this->getHttpClient($_SERVER['OPEN_ROUTE_SERVICE_API_KEY']), $_SERVER['OPEN_ROUTE_SERVICE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('242 Acklam Road, London, United Kingdom'));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(51.521124, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-0.20360200000000001, $result->getCoordinates()->getLongitude(), 0.01);
+ $this->assertEquals('Acklam Road', $result->getStreetName());
+ $this->assertEquals('London', $result->getLocality());
+ $this->assertCount(4, $result->getAdminLevels());
+ $this->assertEquals('London', $result->getAdminLevels()->get(3)->getName());
+ $this->assertEquals('United Kingdom', $result->getCountry()->getName());
+ $this->assertEquals('GBR', $result->getCountry()->getCode());
+ }
+
+ public function testReverseWithRealCoordinates(): void
+ {
+ if (!isset($_SERVER['OPEN_ROUTE_SERVICE_API_KEY'])) {
+ $this->markTestSkipped('You need to configure the OPEN_ROUTE_SERVICE_API_KEY value in phpunit.xml');
+ }
+
+ $provider = new OpenRouteService($this->getHttpClient($_SERVER['OPEN_ROUTE_SERVICE_API_KEY']), $_SERVER['OPEN_ROUTE_SERVICE_API_KEY']);
+ $results = $provider->reverseQuery(ReverseQuery::fromCoordinates(54.0484068, -2.7990345));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(5, $results);
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(54.048411999999999, $result->getCoordinates()->getLatitude(), 0.001);
+ $this->assertEqualsWithDelta(-2.7989549999999999, $result->getCoordinates()->getLongitude(), 0.001);
+ $this->assertEquals(11, $result->getStreetNumber());
+ $this->assertEquals('Ffrances Passage', $result->getStreetName());
+ $this->assertEquals('LA1 1UG', $result->getPostalCode());
+ $this->assertEquals('Lancaster', $result->getLocality());
+ $this->assertCount(4, $result->getAdminLevels());
+ $this->assertEquals('Lancashire', $result->getAdminLevels()->get(3)->getName());
+ $this->assertEquals('England', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('United Kingdom', $result->getCountry()->getName());
+ $this->assertEquals('GBR', $result->getCountry()->getCode());
+ }
+
+ public function testReverseWithVillage(): void
+ {
+ if (!isset($_SERVER['OPEN_ROUTE_SERVICE_API_KEY'])) {
+ $this->markTestSkipped('You need to configure the OPEN_ROUTE_SERVICE_API_KEY value in phpunit.xml');
+ }
+
+ $provider = new OpenRouteService($this->getHttpClient($_SERVER['OPEN_ROUTE_SERVICE_API_KEY']), $_SERVER['OPEN_ROUTE_SERVICE_API_KEY']);
+ $results = $provider->reverseQuery(ReverseQuery::fromCoordinates(49.1390924, 1.6572462));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(5, $results);
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEquals('Bray-et-Lû', $result->getLocality());
+ }
+
+ public function testGeocodeWithCity(): void
+ {
+ if (!isset($_SERVER['OPEN_ROUTE_SERVICE_API_KEY'])) {
+ $this->markTestSkipped('You need to configure the OPEN_ROUTE_SERVICE_API_KEY value in phpunit.xml');
+ }
+
+ $provider = new OpenRouteService($this->getHttpClient($_SERVER['OPEN_ROUTE_SERVICE_API_KEY']), $_SERVER['OPEN_ROUTE_SERVICE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Hanover'));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(5, $results);
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(52.379952, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(9.787455, $result->getCoordinates()->getLongitude(), 0.01);
+ $this->assertEquals('Hanover', $result->getLocality());
+ $this->assertCount(4, $result->getAdminLevels());
+ $this->assertEquals('Lower Saxony', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('Hanover', $result->getAdminLevels()->get(3)->getName());
+ $this->assertEquals('Germany', $result->getCountry()->getName());
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->get(1);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(18.393428, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-78.107687, $result->getCoordinates()->getLongitude(), 0.01);
+ $this->assertNull($result->getLocality());
+ $this->assertCount(1, $result->getAdminLevels());
+ $this->assertEquals('Hanover', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('Jamaica', $result->getCountry()->getName());
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->get(2);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(39.192889999999998, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-76.724140000000006, $result->getCoordinates()->getLongitude(), 0.01);
+ $this->assertEquals('Hanover', $result->getLocality());
+ $this->assertCount(3, $result->getAdminLevels());
+ $this->assertEquals('Hanover', $result->getAdminLevels()->get(3)->getName());
+ $this->assertEquals('United States', $result->getCountry()->getName());
+ }
+
+ public function testGeocodeWithCityDistrict(): void
+ {
+ if (!isset($_SERVER['OPEN_ROUTE_SERVICE_API_KEY'])) {
+ $this->markTestSkipped('You need to configure the OPEN_ROUTE_SERVICE_API_KEY value in phpunit.xml');
+ }
+
+ $provider = new OpenRouteService($this->getHttpClient($_SERVER['OPEN_ROUTE_SERVICE_API_KEY']), $_SERVER['OPEN_ROUTE_SERVICE_API_KEY']);
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Kalbacher Hauptstraße 10, 60437 Frankfurt, Germany'));
+
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(2, $results);
+
+ /** @var \Geocoder\Model\Address $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(50.189017, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(8.6367809999999992, $result->getCoordinates()->getLongitude(), 0.01);
+ $this->assertEquals('10a', $result->getStreetNumber());
+ $this->assertEquals('Kalbacher Hauptstraße', $result->getStreetName());
+ $this->assertEquals(60437, $result->getPostalCode());
+ $this->assertEquals('Frankfurt', $result->getLocality());
+ $this->assertCount(5, $result->getAdminLevels());
+ $this->assertEquals('Frankfurt', $result->getAdminLevels()->get(4)->getName());
+ $this->assertEquals('Hesse', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('HE', $result->getAdminLevels()->get(1)->getCode());
+ $this->assertEquals('Germany', $result->getCountry()->getName());
+ $this->assertEquals('DEU', $result->getCountry()->getCode());
+ }
+
+ public function testGeocodeQuotaExceeded(): void
+ {
+ $this->expectException(\Geocoder\Exception\QuotaExceeded::class);
+ $this->expectExceptionMessage('Valid request but quota exceeded.');
+
+ $provider = new OpenRouteService(
+ $this->getMockedHttpClient(
+ '{
+ "meta": {
+ "version": 1,
+ "status_code": 429
+ },
+ "results": {
+ "error": {
+ "type": "QpsExceededError",
+ "message": "Queries per second exceeded: Queries exceeded (6 allowed)."
+ }
+ }
+ }'
+ ),
+ 'api_key'
+ );
+ $provider->geocodeQuery(GeocodeQuery::create('New York'));
+ }
+
+ public function testGeocodeInvalidApiKey(): void
+ {
+ $this->expectException(\Geocoder\Exception\InvalidCredentials::class);
+ $this->expectExceptionMessage('Invalid or missing api key.');
+
+ $provider = new OpenRouteService(
+ $this->getMockedHttpClient(
+ '{
+ "meta": {
+ "version": 1,
+ "status_code": 403
+ },
+ "results": {
+ "error": {
+ "type": "KeyError",
+ "message": "No api_key specified."
+ }
+ }
+ }'
+ ),
+ 'api_key'
+ );
+ $provider->geocodeQuery(GeocodeQuery::create('New York'));
+ }
+
+ public function testGeocodeWithLocalhostIPv4(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The openrouteservice provider does not support IP addresses, only street addresses.');
+
+ $provider = new OpenRouteService($this->getMockedHttpClient(), 'api_key');
+ $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
+ }
+
+ public function testGeocodeWithLocalhostIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The openrouteservice provider does not support IP addresses, only street addresses.');
+
+ $provider = new OpenRouteService($this->getMockedHttpClient(), 'api_key');
+ $provider->geocodeQuery(GeocodeQuery::create('::1'));
+ }
+
+ public function testGeocodeWithRealIPv4(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The openrouteservice provider does not support IP addresses, only street addresses.');
+
+ $provider = new OpenRouteService($this->getMockedHttpClient(), 'api_key');
+ $provider->geocodeQuery(GeocodeQuery::create('74.200.247.59'));
+ }
+
+ public function testGeocodeWithRealIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The openrouteservice provider does not support IP addresses, only street addresses.');
+
+ $provider = new OpenRouteService($this->getMockedHttpClient(), 'api_key');
+ $provider->geocodeQuery(GeocodeQuery::create('::ffff:74.200.247.59'));
+ }
+}
diff --git a/src/Provider/OpenRouteService/composer.json b/src/Provider/OpenRouteService/composer.json
new file mode 100644
index 000000000..db71a816d
--- /dev/null
+++ b/src/Provider/OpenRouteService/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "geocoder-php/openrouteservice-provider",
+ "type": "library",
+ "description": "Geocoder OpenRouteService adapter",
+ "keywords": [],
+ "homepage": "http://geocoder-php.org/Geocoder/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Gaël Gosset"
+ }
+ ],
+ "require": {
+ "php": "^8.0",
+ "geocoder-php/common-http": "^4.0",
+ "geocoder-php/pelias-provider": "^1.0",
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
+ },
+ "require-dev": {
+ "geocoder-php/provider-integration-tests": "^1.6.3",
+ "php-http/message": "^1.0",
+ "phpunit/phpunit": "^9.6.11"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Geocoder\\Provider\\OpenRouteService\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
+ }
+}
diff --git a/src/Provider/OpenRouteService/phpunit.xml.dist b/src/Provider/OpenRouteService/phpunit.xml.dist
new file mode 100644
index 000000000..fd86edb1f
--- /dev/null
+++ b/src/Provider/OpenRouteService/phpunit.xml.dist
@@ -0,0 +1,21 @@
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
diff --git a/src/Provider/Pelias/.gitattributes b/src/Provider/Pelias/.gitattributes
new file mode 100644
index 000000000..d04504afd
--- /dev/null
+++ b/src/Provider/Pelias/.gitattributes
@@ -0,0 +1,4 @@
+.gitattributes export-ignore
+.travis.yml export-ignore
+phpunit.xml.dist export-ignore
+Tests/ export-ignore
diff --git a/src/Provider/Pelias/.github/workflows/provider.yml b/src/Provider/Pelias/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/Pelias/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/Pelias/.gitignore b/src/Provider/Pelias/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/src/Provider/Pelias/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Provider/Pelias/CHANGELOG.md b/src/Provider/Pelias/CHANGELOG.md
new file mode 100644
index 000000000..c92952b0f
--- /dev/null
+++ b/src/Provider/Pelias/CHANGELOG.md
@@ -0,0 +1,73 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 1.6.1
+
+### Fixed
+
+- Fix nullable properties
+
+## 1.6.0
+
+### Added
+
+- Add support for `id`, `layer`, `source`, `name`, `confidence`, and `accuracy` fields
+- Add support for locale
+- Add support for `layers` and `boundary.country` filters
+- Improve extraction of admin levels
+
+## 1.5.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 1.4.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 1.3.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 1.2.0
+
+### Fixed
+
+- Fix `NULL` value for bounds
+
+### Removed
+
+- Drop support for PHP < 7.2
+
+## 1.1.0
+
+### Changed
+
+- Change `$root` visibility to `protected`
+
+## 1.0.0
+
+First release of this provider.
diff --git a/src/Provider/Pelias/LICENSE b/src/Provider/Pelias/LICENSE
new file mode 100644
index 000000000..8aa8246ef
--- /dev/null
+++ b/src/Provider/Pelias/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 — William Durand
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/Provider/Pelias/Model/PeliasAddress.php b/src/Provider/Pelias/Model/PeliasAddress.php
new file mode 100644
index 000000000..14beb71f5
--- /dev/null
+++ b/src/Provider/Pelias/Model/PeliasAddress.php
@@ -0,0 +1,129 @@
+
+ */
+final class PeliasAddress extends Address
+{
+ /**
+ * @var string|null
+ */
+ private $id;
+
+ /**
+ * @var string|null
+ */
+ private $layer;
+
+ /**
+ * @var string|null
+ */
+ private $source;
+
+ /**
+ * @var string|null
+ */
+ private $name;
+
+ /**
+ * @var float|null
+ */
+ private $confidence;
+
+ /**
+ * @var string|null
+ */
+ private $accuracy;
+
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+ public function withId(?string $id): self
+ {
+ $new = clone $this;
+ $new->id = $id;
+
+ return $new;
+ }
+
+ public function getSource(): ?string
+ {
+ return $this->source;
+ }
+
+ public function withSource(?string $source): self
+ {
+ $new = clone $this;
+ $new->source = $source;
+
+ return $new;
+ }
+
+ public function getLayer(): ?string
+ {
+ return $this->layer;
+ }
+
+ public function withLayer(?string $layer): self
+ {
+ $new = clone $this;
+ $new->layer = $layer;
+
+ return $new;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function withName(?string $name): self
+ {
+ $new = clone $this;
+ $new->name = $name;
+
+ return $new;
+ }
+
+ public function getConfidence(): ?float
+ {
+ return $this->confidence;
+ }
+
+ public function withConfidence(?float $confidence): self
+ {
+ $new = clone $this;
+ $new->confidence = $confidence;
+
+ return $new;
+ }
+
+ public function getAccuracy(): ?string
+ {
+ return $this->accuracy;
+ }
+
+ public function withAccuracy(?string $accuracy): self
+ {
+ $new = clone $this;
+ $new->accuracy = $accuracy;
+
+ return $new;
+ }
+}
diff --git a/src/Provider/Pelias/Pelias.php b/src/Provider/Pelias/Pelias.php
new file mode 100644
index 000000000..bb55a95d3
--- /dev/null
+++ b/src/Provider/Pelias/Pelias.php
@@ -0,0 +1,242 @@
+root = sprintf('%s/v%d', rtrim($root, '/'), $version);
+
+ parent::__construct($client);
+ }
+
+ /**
+ * @param array $query_data additional query data (API key for instance)
+ *
+ * @throws \Geocoder\Exception\Exception
+ */
+ protected function getGeocodeQueryUrl(GeocodeQuery $query, array $query_data = []): string
+ {
+ $address = $query->getText();
+
+ // This API doesn't handle IPs
+ if (filter_var($address, FILTER_VALIDATE_IP)) {
+ throw new UnsupportedOperation(sprintf('The %s provider does not support IP addresses, only street addresses.', $this->getName()));
+ }
+
+ $data = [
+ 'text' => $address,
+ 'size' => $query->getLimit(),
+ 'layers' => null !== $query->getData('layers') ? implode(',', $query->getData('layers')) : null,
+ 'boundary.country' => null !== $query->getData('boundary.country') ? implode(',', $query->getData('boundary.country')) : null,
+ ];
+
+ return sprintf('%s/search?%s', $this->root, http_build_query(array_merge($data, $query_data)));
+ }
+
+ public function geocodeQuery(GeocodeQuery $query): Collection
+ {
+ return $this->executeQuery($this->getGeocodeQueryUrl($query), $query->getLocale());
+ }
+
+ /**
+ * @param array $query_data additional query data (API key for instance)
+ *
+ * @throws \Geocoder\Exception\Exception
+ */
+ protected function getReverseQueryUrl(ReverseQuery $query, array $query_data = []): string
+ {
+ $coordinates = $query->getCoordinates();
+ $longitude = $coordinates->getLongitude();
+ $latitude = $coordinates->getLatitude();
+
+ $data = [
+ 'point.lat' => $latitude,
+ 'point.lon' => $longitude,
+ 'size' => $query->getLimit(),
+ 'layers' => null !== $query->getData('layers') ? implode(',', $query->getData('layers')) : null,
+ 'boundary.country' => null !== $query->getData('boundary.country') ? implode(',', $query->getData('boundary.country')) : null,
+ ];
+
+ return sprintf('%s/reverse?%s', $this->root, http_build_query(array_merge($data, $query_data)));
+ }
+
+ public function reverseQuery(ReverseQuery $query): Collection
+ {
+ return $this->executeQuery($this->getReverseQueryUrl($query), $query->getLocale());
+ }
+
+ public function getName(): string
+ {
+ return 'pelias';
+ }
+
+ protected function executeQuery(string $url, ?string $locale = null): AddressCollection
+ {
+ $headers = [];
+ if (null !== $locale) {
+ $headers['Accept-Language'] = $locale;
+ }
+
+ $request = $this->createRequest('GET', $url, $headers);
+ $content = $this->getParsedResponse($request);
+ $json = json_decode($content, true);
+
+ if (isset($json['meta'])) {
+ switch ($json['meta']['status_code']) {
+ case 401:
+ case 403:
+ throw new InvalidCredentials('Invalid or missing api key.');
+ case 429:
+ throw new QuotaExceeded('Valid request but quota exceeded.');
+ }
+ }
+
+ if (
+ !isset($json['type'])
+ || 'FeatureCollection' !== $json['type']
+ || !isset($json['features'])
+ || [] === $json['features']
+ ) {
+ return new AddressCollection([]);
+ }
+
+ $features = $json['features'];
+
+ if (empty($features)) {
+ return new AddressCollection([]);
+ }
+
+ $results = [];
+ foreach ($features as $feature) {
+ $builder = new AddressBuilder($this->getName());
+ $builder->setCoordinates($feature['geometry']['coordinates'][1], $feature['geometry']['coordinates'][0]);
+ $builder->setStreetNumber($feature['properties']['housenumber'] ?? null);
+ $builder->setStreetName($this->guessStreetName($feature['properties']));
+ $builder->setSubLocality($this->guessSubLocality($feature['properties']));
+ $builder->setLocality($this->guessLocality($feature['properties']));
+ $builder->setPostalCode($feature['properties']['postalcode'] ?? null);
+ $builder->setCountry($feature['properties']['country'] ?? null);
+ $builder->setCountryCode(
+ isset($feature['properties']['country_code']) ? strtoupper($feature['properties']['country_code']) :
+ (isset($feature['properties']['country_a']) ? strtoupper($feature['properties']['country_a']) : null));
+ $builder->setTimezone($feature['properties']['timezone'] ?? null);
+
+ if (isset($feature['bbox'])) {
+ $builder->setBounds($feature['bbox'][3], $feature['bbox'][2], $feature['bbox'][1], $feature['bbox'][0]);
+ }
+
+ $level = 1;
+ foreach (['macroregion', 'region', 'macrocounty', 'county', 'locality', 'localadmin', 'borough'] as $component) {
+ if (isset($feature['properties'][$component])) {
+ $builder->addAdminLevel($level++, $feature['properties'][$component], $feature['properties'][$component.'_a'] ?? null);
+ }
+ // Administrative level should be an integer in [1,5].
+ if ($level > 5) {
+ break;
+ }
+ }
+
+ /** @var PeliasAddress $location */
+ $location = $builder->build(PeliasAddress::class);
+
+ $location = $location->withId($feature['properties']['id'] ?? null);
+ $location = $location->withLayer($feature['properties']['layer'] ?? null);
+ $location = $location->withSource($feature['properties']['source'] ?? null);
+ $location = $location->withName($feature['properties']['name'] ?? null);
+ $location = $location->withConfidence($feature['properties']['confidence'] ?? null);
+ $location = $location->withAccuracy($feature['properties']['accuracy'] ?? null);
+
+ $results[] = $location;
+ }
+
+ return new AddressCollection($results);
+ }
+
+ /**
+ * @param array $components
+ *
+ * @return string|null
+ */
+ protected static function guessLocality(array $components)
+ {
+ $localityKeys = ['locality', 'localadmin', 'city', 'town', 'village', 'hamlet'];
+
+ return self::guessBestComponent($components, $localityKeys);
+ }
+
+ /**
+ * @param array $components
+ *
+ * @return string|null
+ */
+ protected static function guessSubLocality(array $components)
+ {
+ $subLocalityKeys = ['neighbourhood', 'city_district'];
+
+ return self::guessBestComponent($components, $subLocalityKeys);
+ }
+
+ /**
+ * @param array $components
+ *
+ * @return string|null
+ */
+ protected static function guessStreetName(array $components)
+ {
+ $streetNameKeys = ['road', 'street', 'street_name', 'residential'];
+
+ return self::guessBestComponent($components, $streetNameKeys);
+ }
+
+ /**
+ * @param array $components
+ * @param string[] $keys
+ *
+ * @return string|null
+ */
+ protected static function guessBestComponent(array $components, array $keys)
+ {
+ foreach ($keys as $key) {
+ if (isset($components[$key]) && !empty($components[$key])) {
+ return $components[$key];
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Provider/Pelias/README.md b/src/Provider/Pelias/README.md
new file mode 100644
index 000000000..ba60bb5cd
--- /dev/null
+++ b/src/Provider/Pelias/README.md
@@ -0,0 +1,35 @@
+# Pelias Geocoder provider
+
+[](http://travis-ci.org/geocoder-php/pelias-provider)
+[](https://packagist.org/packages/geocoder-php/pelias-provider)
+[](https://packagist.org/packages/geocoder-php/pelias-provider)
+[](https://packagist.org/packages/geocoder-php/pelias-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/pelias-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/pelias-provider)
+[](LICENSE)
+
+This is the Pelias 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.
+
+## Pelias
+
+Pelias is an open-source geocoder. You can find its [documentation here](https://github.com/pelias/documentation).
+
+Pelias does not provide an API per se but other API (and providers) are based on Pelias and extend this provider.
+You can run your own Pelias instance on your server, see [documentation](https://github.com/pelias/documentation/blob/master/getting_started_install.md) to install it.
+
+For instance:
+
+- Geocode Earth ([Website](https://geocode.earth/) & [Provider](https://github.com/geocoder-php/geocode-earth-provider))
+- OpenRouteService ([Website](https://openrouteservice.org/) & [Provider](https://github.com/geocoder-php/openrouteservice-provider))
+
+## Install
+
+```bash
+composer require geocoder-php/pelias-provider
+```
+
+## Contribute
+
+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).
diff --git a/src/Provider/Pelias/Tests/IntegrationTest.php b/src/Provider/Pelias/Tests/IntegrationTest.php
new file mode 100644
index 000000000..01592f670
--- /dev/null
+++ b/src/Provider/Pelias/Tests/IntegrationTest.php
@@ -0,0 +1,49 @@
+
+ */
+class IntegrationTest extends ProviderIntegrationTest
+{
+ protected array $skippedTests = [
+ 'testGeocodeQuery' => 'No Pelias "default" instance.',
+ 'testGeocodeQueryWithNoResults' => 'No Pelias "default" instance.',
+ 'testReverseQuery' => 'No Pelias "default" instance.',
+ 'testReverseQueryWithNoResults' => 'No Pelias "default" instance.',
+ ];
+
+ protected bool $testIpv4 = false;
+
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
+ {
+ return new Pelias($httpClient, 'http://localhost/');
+ }
+
+ protected function getCacheDir(): string
+ {
+ return __DIR__.'/.cached_responses';
+ }
+
+ protected function getApiKey(): string
+ {
+ return '';
+ }
+}
diff --git a/src/Provider/Pelias/Tests/PeliasTest.php b/src/Provider/Pelias/Tests/PeliasTest.php
new file mode 100644
index 000000000..409c3adb7
--- /dev/null
+++ b/src/Provider/Pelias/Tests/PeliasTest.php
@@ -0,0 +1,88 @@
+getMockedHttpClient(), 'http://localhost/');
+ $this->assertEquals('pelias', $provider->getName());
+ }
+
+ public function testGeocode(): void
+ {
+ $provider = new Pelias($this->getMockedHttpClient('{}'), 'http://localhost/');
+ $result = $provider->geocodeQuery(GeocodeQuery::create('foobar'));
+
+ $this->assertInstanceOf(Collection::class, $result);
+ $this->assertEquals(0, $result->count());
+ }
+
+ public function testReverse(): void
+ {
+ $provider = new Pelias($this->getMockedHttpClient('{}'), 'http://localhost/');
+ $result = $provider->reverseQuery(ReverseQuery::fromCoordinates(0, 0));
+
+ $this->assertInstanceOf(Collection::class, $result);
+ $this->assertEquals(0, $result->count());
+ }
+
+ public function testGeocodeWithLocalhostIPv4(): void
+ {
+ $this->expectException(UnsupportedOperation::class);
+ $this->expectExceptionMessage('The pelias provider does not support IP addresses, only street addresses.');
+
+ $provider = new Pelias($this->getMockedHttpClient(), 'http://localhost/');
+ $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
+ }
+
+ public function testGeocodeWithLocalhostIPv6(): void
+ {
+ $this->expectException(UnsupportedOperation::class);
+ $this->expectExceptionMessage('The pelias provider does not support IP addresses, only street addresses.');
+
+ $provider = new Pelias($this->getMockedHttpClient(), 'http://localhost/');
+ $provider->geocodeQuery(GeocodeQuery::create('::1'));
+ }
+
+ public function testGeocodeWithRealIPv4(): void
+ {
+ $this->expectException(UnsupportedOperation::class);
+ $this->expectExceptionMessage('The pelias provider does not support IP addresses, only street addresses.');
+
+ $provider = new Pelias($this->getMockedHttpClient(), 'http://localhost/');
+ $provider->geocodeQuery(GeocodeQuery::create('74.200.247.59'));
+ }
+
+ public function testGeocodeWithRealIPv6(): void
+ {
+ $this->expectException(UnsupportedOperation::class);
+ $this->expectExceptionMessage('The pelias provider does not support IP addresses, only street addresses.');
+
+ $provider = new Pelias($this->getMockedHttpClient(), 'http://localhost/');
+ $provider->geocodeQuery(GeocodeQuery::create('::ffff:74.200.247.59'));
+ }
+}
diff --git a/src/Provider/Pelias/composer.json b/src/Provider/Pelias/composer.json
new file mode 100644
index 000000000..60c1c541e
--- /dev/null
+++ b/src/Provider/Pelias/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "geocoder-php/pelias-provider",
+ "type": "library",
+ "description": "Geocoder Pelias abstract adapter",
+ "keywords": [],
+ "homepage": "http://geocoder-php.org/Geocoder/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "William Durand",
+ "email": "william.durand1@gmail.com"
+ }
+ ],
+ "require": {
+ "php": "^8.0",
+ "geocoder-php/common-http": "^4.0",
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
+ },
+ "require-dev": {
+ "geocoder-php/provider-integration-tests": "^1.6.3",
+ "php-http/message": "^1.7",
+ "phpunit/phpunit": "^9.6.11"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Geocoder\\Provider\\Pelias\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
+ }
+}
diff --git a/src/Provider/Pelias/phpunit.xml.dist b/src/Provider/Pelias/phpunit.xml.dist
new file mode 100644
index 000000000..26265855a
--- /dev/null
+++ b/src/Provider/Pelias/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
diff --git a/src/Provider/Photon/.gitattributes b/src/Provider/Photon/.gitattributes
new file mode 100644
index 000000000..d04504afd
--- /dev/null
+++ b/src/Provider/Photon/.gitattributes
@@ -0,0 +1,4 @@
+.gitattributes export-ignore
+.travis.yml export-ignore
+phpunit.xml.dist export-ignore
+Tests/ export-ignore
diff --git a/src/Provider/Photon/.github/workflows/provider.yml b/src/Provider/Photon/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/Photon/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/Photon/.gitignore b/src/Provider/Photon/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/src/Provider/Photon/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Provider/Photon/CHANGELOG.md b/src/Provider/Photon/CHANGELOG.md
new file mode 100644
index 000000000..2162715df
--- /dev/null
+++ b/src/Provider/Photon/CHANGELOG.md
@@ -0,0 +1,95 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 0.11.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 0.10.0
+
+### Added
+
+- Add support for multiple `layer` parameters
+
+## 0.9.0
+
+### Added
+
+- Add support for `lat`, `lon` parameters
+- Add support for `bbox` parameter
+
+## 0.8.0
+
+### Added
+
+- Add support for `layer` and `radius` parameters
+- Improve locality extraction
+
+## 0.7.0
+
+### Added
+
+- Add support for PHP 8.3 and 8.4
+- Add support for OpenStreetMap filters
+
+### Removed
+
+- Drop support for PHP 7.3
+
+## 0.6.0
+
+### Added
+
+- Adds support for `state`, `county`, and `district` properties
+
+## 0.5.0
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 0.4.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+- Add name proprety
+- Add country code property
+
+## 0.3.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 0.2.1
+
+### Changed
+
+- Update Komoot TLD (`photon.komoot.io` instead of `photon.komoot.de`)
+
+## 0.2.0
+
+### Removed
+
+- Drop support for PHP < 7.2
+
+## 0.1.0
+
+First release of this library.
diff --git a/src/Provider/Photon/LICENSE b/src/Provider/Photon/LICENSE
new file mode 100644
index 000000000..8aa8246ef
--- /dev/null
+++ b/src/Provider/Photon/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 — William Durand
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/Provider/Photon/Model/PhotonAddress.php b/src/Provider/Photon/Model/PhotonAddress.php
new file mode 100644
index 000000000..049b2c706
--- /dev/null
+++ b/src/Provider/Photon/Model/PhotonAddress.php
@@ -0,0 +1,204 @@
+
+ */
+final class PhotonAddress extends Address
+{
+ /**
+ * @var string|null
+ */
+ private $name;
+
+ /**
+ * @var int|null
+ */
+ private $osmId;
+
+ /**
+ * @var string|null
+ */
+ private $osmType;
+
+ /**
+ * @var \stdclass|null
+ */
+ private $osmTag;
+
+ /**
+ * @var string|null
+ */
+ private $state;
+
+ /**
+ * @var string|null
+ */
+ private $county;
+
+ /**
+ * @var string|null
+ */
+ private $district;
+
+ /**
+ * @var string|null
+ */
+ private $type;
+
+ public function getLocality(): ?string
+ {
+ $locality = parent::getLocality();
+ if (null === $locality && 'city' === $this->type && null !== $this->name) {
+ $locality = $this->name;
+ }
+
+ return $locality;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function withName(?string $name = null): self
+ {
+ $new = clone $this;
+ $new->name = $name;
+
+ return $new;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getOSMId()
+ {
+ return $this->osmId;
+ }
+
+ public function withOSMId(?int $osmId = null): self
+ {
+ $new = clone $this;
+ $new->osmId = $osmId;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getOSMType()
+ {
+ return $this->osmType;
+ }
+
+ public function withOSMType(?string $osmType = null): self
+ {
+ $new = clone $this;
+ $new->osmType = $osmType;
+
+ return $new;
+ }
+
+ /**
+ * @return object|null
+ */
+ public function getOSMTag()
+ {
+ return $this->osmTag;
+ }
+
+ public function withOSMTag(?string $key = null, ?string $value = null): self
+ {
+ $new = clone $this;
+
+ if (!is_null($key) && !is_null($value)) {
+ $new->osmTag = (object) [
+ 'key' => $key,
+ 'value' => $value,
+ ];
+ } else {
+ $new->osmTag = null;
+ }
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function withState(?string $state = null): self
+ {
+ $new = clone $this;
+ $new->state = $state;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCounty()
+ {
+ return $this->county;
+ }
+
+ public function withCounty(?string $county = null): self
+ {
+ $new = clone $this;
+ $new->county = $county;
+
+ return $new;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDistrict()
+ {
+ return $this->district;
+ }
+
+ public function withDistrict(?string $district = null): self
+ {
+ $new = clone $this;
+ $new->district = $district;
+
+ return $new;
+ }
+
+ public function getType(): ?string
+ {
+ return $this->type;
+ }
+
+ public function withType(?string $type = null): self
+ {
+ $new = clone $this;
+ $new->type = $type;
+
+ return $new;
+ }
+}
diff --git a/src/Provider/Photon/Photon.php b/src/Provider/Photon/Photon.php
new file mode 100644
index 000000000..d9d423c4e
--- /dev/null
+++ b/src/Provider/Photon/Photon.php
@@ -0,0 +1,245 @@
+
+ * @author Jonathan Beliën
+ */
+final class Photon extends AbstractHttpProvider implements Provider
+{
+ /**
+ * @var string
+ */
+ private $rootUrl;
+
+ /**
+ * @param ClientInterface $client an HTTP client
+ */
+ public static function withKomootServer(ClientInterface $client): self
+ {
+ return new self($client, 'https://photon.komoot.io');
+ }
+
+ /**
+ * @param ClientInterface $client an HTTP client
+ * @param string $rootUrl Root URL of the photon server
+ */
+ public function __construct(ClientInterface $client, $rootUrl)
+ {
+ parent::__construct($client);
+
+ $this->rootUrl = rtrim($rootUrl, '/');
+ }
+
+ public function geocodeQuery(GeocodeQuery $query): Collection
+ {
+ $address = $query->getText();
+
+ // This API doesn't handle IPs
+ if (filter_var($address, FILTER_VALIDATE_IP)) {
+ throw new UnsupportedOperation('The Photon provider does not support IP addresses.');
+ }
+
+ $url = $this->rootUrl
+ .'/api?'
+ .http_build_query([
+ 'q' => $address,
+ 'limit' => $query->getLimit(),
+ 'lang' => $query->getLocale(),
+ 'lat' => $query->getData('lat'),
+ 'lon' => $query->getData('lon'),
+ ]);
+ $url .= $this->buildLayerFilterQuery($query->getData('layer'));
+ $osmTagFilters = $this->buildOsmTagFilterQuery($query->getData('osm_tag'));
+ if (!empty($osmTagFilters)) {
+ $url .= $osmTagFilters;
+ }
+ $bboxQueryString = $this->buildBboxFilterQuery($query);
+ if (!is_null($bboxQueryString)) {
+ $url .= $bboxQueryString;
+ }
+
+ $json = $this->executeQuery($url);
+
+ if (!isset($json->features) || empty($json->features)) {
+ return new AddressCollection([]);
+ }
+
+ $results = [];
+ foreach ($json->features as $feature) {
+ $results[] = $this->featureToAddress($feature);
+ }
+
+ return new AddressCollection($results);
+ }
+
+ public function reverseQuery(ReverseQuery $query): Collection
+ {
+ $coordinates = $query->getCoordinates();
+
+ $longitude = $coordinates->getLongitude();
+ $latitude = $coordinates->getLatitude();
+
+ $url = $this->rootUrl
+ .'/reverse?'
+ .http_build_query([
+ 'lat' => $latitude,
+ 'lon' => $longitude,
+ 'radius' => $query->getData('radius'),
+ 'limit' => $query->getLimit(),
+ 'lang' => $query->getLocale(),
+ ]);
+ $url .= $this->buildLayerFilterQuery($query->getData('layer'));
+ $osmTagFilters = $this->buildOsmTagFilterQuery($query->getData('osm_tag'));
+ if (!empty($osmTagFilters)) {
+ $url .= $osmTagFilters;
+ }
+
+ $json = $this->executeQuery($url);
+
+ if (!isset($json->features) || empty($json->features)) {
+ return new AddressCollection([]);
+ }
+
+ $results = [];
+ foreach ($json->features as $feature) {
+ $results[] = $this->featureToAddress($feature);
+ }
+
+ return new AddressCollection($results);
+ }
+
+ private function featureToAddress(\stdClass $feature): Location
+ {
+ $builder = new AddressBuilder($this->getName());
+
+ $coordinates = $feature->geometry->coordinates;
+ $properties = $feature->properties;
+
+ $builder->setCoordinates(floatval($coordinates[1]), floatval($coordinates[0]));
+
+ $builder->setStreetName($properties->street ?? null);
+ $builder->setStreetNumber($properties->housenumber ?? null);
+ $builder->setPostalCode($properties->postcode ?? null);
+ $builder->setLocality($properties->city ?? null);
+ $builder->setCountry($properties->country ?? null);
+ $builder->setCountryCode($properties->countrycode ?? null);
+
+ if (isset($properties->extent)) {
+ $builder->setBounds($properties->extent[0], $properties->extent[2], $properties->extent[1], $properties->extent[3]);
+ }
+
+ /** @var PhotonAddress $address */
+ $address = $builder->build(PhotonAddress::class);
+
+ $address = $address
+ ->withOSMId($properties->osm_id ?? null)
+ ->withOSMType($properties->osm_type ?? null)
+ ->withOSMTag(
+ $properties->osm_key ?? null,
+ $properties->osm_value ?? null
+ )
+ ->withName($properties->name ?? null)
+ ->withState($properties->state ?? null)
+ ->withCounty($properties->county ?? null)
+ ->withDistrict($properties->district ?? null)
+ ->withType($properties->type ?? null);
+
+ return $address;
+ }
+
+ public function getName(): string
+ {
+ return 'photon';
+ }
+
+ /**
+ * @param string|string[]|null $layers
+ */
+ private function buildLayerFilterQuery(mixed $layers): string
+ {
+ $query = '';
+ if (null === $layers) {
+ return $query;
+ }
+ if (is_string($layers)) {
+ return '&layer='.urlencode($layers);
+ }
+ foreach ($layers as $layer) {
+ $query .= '&layer='.urlencode($layer);
+ }
+
+ return $query;
+ }
+
+ /**
+ * @param string|array|null $filters
+ */
+ private function buildOsmTagFilterQuery($filters): string
+ {
+ $query = '';
+ if (null === $filters) {
+ return $query;
+ }
+ if (is_string($filters)) {
+ return '&osm_tag='.urlencode($filters);
+ }
+ foreach ($filters as $filter) {
+ $query .= '&osm_tag='.urlencode($filter);
+ }
+
+ return $query;
+ }
+
+ private function buildBboxFilterQuery(GeocodeQuery $query): ?string
+ {
+ if (null === $query->getBounds()) {
+ return null;
+ }
+
+ return '&bbox='.sprintf('%f,%f,%f,%f',
+ $query->getBounds()->getWest(),
+ $query->getBounds()->getSouth(),
+ $query->getBounds()->getEast(),
+ $query->getBounds()->getNorth()
+ );
+ }
+
+ private function executeQuery(string $url): \stdClass
+ {
+ $content = $this->getUrlContents($url);
+
+ $json = json_decode($content);
+
+ // API error
+ if (is_null($json)) {
+ throw InvalidServerResponse::create($url);
+ }
+
+ return $json;
+ }
+}
diff --git a/src/Provider/Photon/Readme.md b/src/Provider/Photon/Readme.md
new file mode 100644
index 000000000..c684245f7
--- /dev/null
+++ b/src/Provider/Photon/Readme.md
@@ -0,0 +1,65 @@
+# photon Geocoder provider
+[](http://travis-ci.org/geocoder-php/photon-provider)
+[](https://packagist.org/packages/geocoder-php/photon-provider)
+[](https://packagist.org/packages/geocoder-php/photon-provider)
+[](https://packagist.org/packages/geocoder-php/photon-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/photon-provider)
+[](https://scrutinizer-ci.com/g/geocoder-php/photon-provider)
+[](LICENSE)
+
+This is the photon 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.
+
+## Install
+```bash
+composer require geocoder-php/photon-provider
+```
+
+## API Documentation
+https://photon.komoot.io
+https://github.com/komoot/photon
+
+## Usage
+
+### Basic usage
+You can use your own photon instance :
+```php
+// New instance of the provider :
+$provider = new Geocoder\Provider\Photon\Photon($httpClient, 'https://your-photon-root-url');
+// Run geocode or reverse query
+$query = $provider->geocodeQuery(\Geocoder\Query\GeocodeQuery::create('Paris'));
+$reverseQuery = $provider->reverseQuery(\Geocoder\Query\ReverseQuery::fromCoordinates(48.86036 ,2.33852));
+```
+
+### OSM Tag Feature
+You can search for location data based on osm tag filters.
+
+For example, you can filter a geocode query to only include results of type 'place'. You can even restrict it to only have places of type 'city'.
+In the reverse geocoding context you can search for the 3 pharmacies closest to a location.
+
+To see what you can do with this feature, check [the official photon documentation](https://github.com/komoot/photon#filter-results-by-tags-and-values)
+
+Below is an example to query the 3 pharmacies closest to a location :
+```php
+$provider = new Geocoder\Provider\Photon\Photon($httpClient, 'https://your-photon-root-url');
+$reverseQuery = \Geocoder\Query\ReverseQuery::fromCoordinates(52.51644, 13.38890)
+ ->withData('osm_tag', 'amenity:pharmacy')
+ ->withLimit(3);
+
+$results = $provider->reverseQuery($reverseQuery);
+```
+
+You can combine multiple osm tag filters :
+```php
+$provider = new Geocoder\Provider\Photon\Photon($httpClient, 'https://your-photon-root-url');
+$reverseQuery = \Geocoder\Query\GeocodeQuery::create('Paris')
+ ->withData('osm_tag', ['tourism:museum', 'tourism:gallery'])
+ ->withLimit(5);
+// Here we get 5 tourism results in Paris which are either museum or art gallery
+$results = $provider->reverseQuery($reverseQuery);
+```
+
+
+## Contribute
+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).
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_3c362ba852ee2323f9f25ca02c28cc5238959dbc b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_3c362ba852ee2323f9f25ca02c28cc5238959dbc
new file mode 100644
index 000000000..4bf2c6c13
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_3c362ba852ee2323f9f25ca02c28cc5238959dbc
@@ -0,0 +1 @@
+s:1219:"{"features":[{"geometry":{"coordinates":[13.3879579,52.5185603],"type":"Point"},"type":"Feature","properties":{"osm_id":380498298,"country":"Deutschland","city":"Berlin","countrycode":"DE","postcode":"10117","locality":"Dorotheenstadt","type":"house","osm_type":"N","osm_key":"amenity","housenumber":"151","street":"Friedrichstraße","district":"Mitte","osm_value":"pharmacy","name":"Dorotheenstadt Apotheke"}},{"geometry":{"coordinates":[13.3874475,52.5196854],"type":"Point"},"type":"Feature","properties":{"osm_id":3331787468,"country":"Deutschland","city":"Berlin","countrycode":"DE","postcode":"10117","locality":"Dorotheenstadt","type":"house","osm_type":"N","osm_key":"amenity","housenumber":"25","street":"Georgenstraße","district":"Mitte","osm_value":"pharmacy","name":"Aschenbachs Apotheke"}},{"geometry":{"coordinates":[13.3903812,52.5122639],"type":"Point"},"type":"Feature","properties":{"osm_id":956306643,"country":"Deutschland","city":"Berlin","countrycode":"DE","postcode":"10117","locality":"Dorotheenstadt","type":"house","osm_type":"N","osm_key":"amenity","housenumber":"68","street":"Friedrichstraße","district":"Mitte","osm_value":"pharmacy","name":"Apotheke Q205"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594
new file mode 100644
index 000000000..bc7c5a595
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594
@@ -0,0 +1 @@
+s:359:"{"features":[{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":388255,"extent":[20.870461,49.185185,21.485864,48.810739],"country":"Slovensko","osm_key":"place","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"city"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_6ab584da133fb849cb0cffafad872b65d3da3de1 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_6ab584da133fb849cb0cffafad872b65d3da3de1
new file mode 100644
index 000000000..bd0964e26
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_6ab584da133fb849cb0cffafad872b65d3da3de1
@@ -0,0 +1 @@
+s:334:"{"features":[{"geometry":{"coordinates":[13.3989367,52.510885],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":62422,"extent":[13.088345,52.6755087,13.7611609,52.3382448],"country":"Deutschland","osm_key":"place","countrycode":"DE","osm_value":"city","name":"Berlin","type":"city"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_723a82c29164f0fc32d4c774f86c4569afdd804b b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_723a82c29164f0fc32d4c774f86c4569afdd804b
new file mode 100644
index 000000000..1b5fe2382
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_723a82c29164f0fc32d4c774f86c4569afdd804b
@@ -0,0 +1 @@
+s:455:"{"features":[{"geometry":{"coordinates":[9.998645,51.9982968],"type":"Point"},"type":"Feature","properties":{"osm_id":693697564,"country":"Deutschland","city":"Lamspringe","countrycode":"DE","postcode":"31195","county":"Landkreis Hildesheim","type":"house","osm_type":"N","osm_key":"tourism","street":"Evensener Dorfstraße","district":"Sehlem","osm_value":"information","name":"Geographischer Punkt","state":"Niedersachsen"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_77590bd663047da83d48c67d9101f04a82e48976 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_77590bd663047da83d48c67d9101f04a82e48976
new file mode 100644
index 000000000..b9e1d963f
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_77590bd663047da83d48c67d9101f04a82e48976
@@ -0,0 +1 @@
+s:2127:"{"features":[{"geometry":{"coordinates":[-77.0372192,38.9002248],"type":"Point"},"type":"Feature","properties":{"osm_type":"W","osm_id":1049842804,"extent":[-77.0372192,38.9002248,-77.0370581,38.9002243],"country":"United States","osm_key":"highway","city":"Washington","countrycode":"US","osm_value":"secondary","postcode":"20006","name":"H Street Northwest","state":"District of Columbia","type":"street"}},{"geometry":{"coordinates":[-77.0366811,38.9002231],"type":"Point"},"type":"Feature","properties":{"osm_type":"W","osm_id":589539534,"extent":[-77.0370581,38.9002243,-77.0365521,38.9002187],"country":"United States","osm_key":"highway","city":"Washington","countrycode":"US","osm_value":"secondary","postcode":"20006","name":"H Street Northwest","state":"District of Columbia","type":"street"}},{"geometry":{"coordinates":[-77.03689992471391,38.90050395],"type":"Point"},"type":"Feature","properties":{"osm_id":55326891,"extent":[-77.0371738,38.9006934,-77.0367231,38.9003173],"country":"United States","city":"Washington","countrycode":"US","postcode":"20006","locality":"Golden Triangle","type":"house","osm_type":"W","osm_key":"tourism","housenumber":"800","street":"Black Lives Matter Plaza Northwest","osm_value":"hotel","name":"Hay-Adams Hotel","state":"District of Columbia"}},{"geometry":{"coordinates":[-77.0374769,38.9003895],"type":"Point"},"type":"Feature","properties":{"osm_type":"N","osm_id":367142942,"country":"United States","osm_key":"building","city":"Washington","street":"H Street Northwest","countrycode":"US","osm_value":"public","postcode":"20006","name":"United States Chamber of Commerce Building","state":"District of Columbia","type":"house"}},{"geometry":{"coordinates":[-77.0364753,38.9003613],"type":"Point"},"type":"Feature","properties":{"osm_id":4957653991,"country":"United States","city":"Washington","countrycode":"US","postcode":"20062","locality":"Golden Triangle","type":"house","osm_type":"N","osm_key":"tourism","street":"Black Lives Matter Plaza Northwest","osm_value":"information","name":"16th Street Meridian","state":"District of Columbia"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_83d30d7d0b77d2526ba7d750ad29a2daae002647 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_83d30d7d0b77d2526ba7d750ad29a2daae002647
new file mode 100644
index 000000000..7296f064e
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_83d30d7d0b77d2526ba7d750ad29a2daae002647
@@ -0,0 +1 @@
+s:535:"{"features":[{"geometry":{"coordinates":[2.3200410217200766,48.8588897],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":7444,"extent":[2.224122,48.902156,2.4697602,48.8155755],"country":"France","osm_key":"boundary","city":"Paris","countrycode":"FR","osm_value":"administrative","postcode":"75000;75001;75002;75003;75004;75005;75006;75007;75008;75009;75010;75011;75012;75013;75014;75015;75016;75017;75018;75019;75020;75116","name":"Paris","state":"Île-de-France","type":"district"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_8cd3447834c3ae8316dda602714b4ca4959eae65 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_8cd3447834c3ae8316dda602714b4ca4959eae65
new file mode 100644
index 000000000..6e057cd72
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_8cd3447834c3ae8316dda602714b4ca4959eae65
@@ -0,0 +1 @@
+s:2187:"{"features":[{"geometry":{"coordinates":[2.2978602225671843,48.8643133],"type":"Point"},"type":"Feature","properties":{"osm_id":79219308,"extent":[2.2971088,48.8647083,2.2984772,48.8639024],"country":"France","city":"Paris","countrycode":"FR","postcode":"75116","locality":"Quartier de Chaillot","type":"house","osm_type":"W","osm_key":"tourism","street":"Rue Gaston de Saint-Paul","district":"Paris","osm_value":"museum","name":"Musée d'Art Moderne de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3518758,48.850724],"type":"Point"},"type":"Feature","properties":{"osm_id":237003117,"country":"France","city":"Paris","countrycode":"FR","postcode":"75005","locality":"Quartier Saint-Victor","type":"house","osm_type":"N","osm_key":"tourism","street":"Quai de la Tournelle","district":"Paris","osm_value":"museum","name":"Musée de l'Assistance Publique Hôpitaux de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3450724,48.8640506],"type":"Point"},"type":"Feature","properties":{"osm_id":3087374948,"country":"France","city":"Paris","countrycode":"FR","postcode":"75001","locality":"Les Halles","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue du Jour","district":"Paris","osm_value":"museum","name":"Musée du Barreau de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3153496472839956,48.866042],"type":"Point"},"type":"Feature","properties":{"osm_id":2778854,"extent":[2.3143339,48.866628,2.3156049,48.8654594],"country":"France","city":"Paris","countrycode":"FR","postcode":"75008","locality":"Quartier des Champs-Élysées","type":"house","osm_type":"R","osm_key":"tourism","street":"Avenue Winston Churchill","district":"Paris","osm_value":"museum","name":"Petit Palais","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3453019,48.8625016],"type":"Point"},"type":"Feature","properties":{"osm_id":1028569468,"country":"France","city":"Paris","countrycode":"FR","postcode":"75001","locality":"Les Halles","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue du Cinéma","district":"Paris","osm_value":"museum","name":"Salle des collections","state":"Île-de-France"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_9a8795bdf85535eb727c3b6003cf2ae96440f52b b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_9a8795bdf85535eb727c3b6003cf2ae96440f52b
new file mode 100644
index 000000000..cdc7eb428
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_9a8795bdf85535eb727c3b6003cf2ae96440f52b
@@ -0,0 +1 @@
+s:473:"{"features":[{"geometry":{"coordinates":[-0.1584743,51.5237629],"type":"Point"},"type":"Feature","properties":{"osm_id":3916613190,"country":"United Kingdom","city":"London","countrycode":"GB","postcode":"NW1 6XE","county":"Greater London","type":"house","osm_type":"N","osm_key":"tourism","housenumber":"221b","street":"Baker Street","district":"Marylebone","osm_value":"museum","name":"The Sherlock Holmes Museum and shop","state":"England"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ab0a3e352306e701cef79478d26bdd56f4598e81 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ab0a3e352306e701cef79478d26bdd56f4598e81
new file mode 100644
index 000000000..edcf21a38
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ab0a3e352306e701cef79478d26bdd56f4598e81
@@ -0,0 +1 @@
+s:2001:"{"features":[{"geometry":{"coordinates":[2.3200410217200766,48.8588897],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":7444,"extent":[2.224122,48.902156,2.4697602,48.8155755],"country":"France","osm_key":"boundary","city":"Paris","countrycode":"FR","osm_value":"administrative","postcode":"75000;75001;75002;75003;75004;75005;75006;75007;75008;75009;75010;75011;75012;75013;75014;75015;75016;75017;75018;75019;75020;75116","name":"Paris","state":"Île-de-France","type":"district"}},{"geometry":{"coordinates":[2.3483915,48.8534951],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":71525,"extent":[2.224122,48.902156,2.4697602,48.8155755],"country":"France","osm_key":"place","countrycode":"FR","osm_value":"city","name":"Paris","state":"Île-de-France","type":"city"}},{"geometry":{"coordinates":[2.3200410217200766,48.8588897],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":1641193,"extent":[2.224122,48.902156,2.4697602,48.8155755],"country":"France","osm_key":"boundary","city":"Paris","countrycode":"FR","osm_value":"administrative","name":"Paris","state":"Île-de-France","type":"district"}},{"geometry":{"coordinates":[-95.555513,33.6617962],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":115357,"extent":[-95.6279396,33.7383866,-95.4354115,33.6206345],"country":"United States","osm_key":"place","countrycode":"US","osm_value":"town","name":"Paris","county":"Lamar","state":"Texas","type":"city"}},{"geometry":{"coordinates":[2.3365253984179155,48.8365091],"type":"Point"},"type":"Feature","properties":{"osm_id":79611305,"extent":[2.3358691,48.8366578,2.3371706,48.836243],"country":"France","city":"Paris","countrycode":"FR","postcode":"75014","locality":"Quartier du Montparnasse","type":"house","osm_type":"W","osm_key":"building","street":"Avenue de l'Observatoire","district":"Paris","osm_value":"historic","name":"Observatoire de Paris","state":"Île-de-France"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_befc5e828aebefb41ad8dca3453898cf26e6fae4 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_befc5e828aebefb41ad8dca3453898cf26e6fae4
new file mode 100644
index 000000000..5e7236e04
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_befc5e828aebefb41ad8dca3453898cf26e6fae4
@@ -0,0 +1 @@
+s:426:"{"features":[{"geometry":{"coordinates":[2.3890894,48.8631927],"type":"Point"},"type":"Feature","properties":{"osm_id":1988097192,"country":"France","city":"Paris","countrycode":"FR","postcode":"75020","locality":"Quartier Saint-Fargeau","type":"house","osm_type":"N","osm_key":"place","housenumber":"10","street":"Avenue Gambetta","district":"Paris","osm_value":"house","state":"Île-de-France"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_c2f6f4f6c6ffb2f99a8aa51d6e45f49cc34d4903 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_c2f6f4f6c6ffb2f99a8aa51d6e45f49cc34d4903
new file mode 100644
index 000000000..b3be97922
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_c2f6f4f6c6ffb2f99a8aa51d6e45f49cc34d4903
@@ -0,0 +1 @@
+s:42:"{"features":[],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_e564cba10f78e298ecde7f804fbfb15d7d430ebc b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_e564cba10f78e298ecde7f804fbfb15d7d430ebc
new file mode 100644
index 000000000..80cb7b8b9
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_e564cba10f78e298ecde7f804fbfb15d7d430ebc
@@ -0,0 +1 @@
+s:373:"{"features":[{"geometry":{"coordinates":[-95.555513,33.6617962],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":115357,"extent":[-95.6279396,33.7383866,-95.4354115,33.6206345],"country":"United States","osm_key":"place","countrycode":"US","osm_value":"town","name":"Paris","county":"Lamar","state":"Texas","type":"city"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f
new file mode 100644
index 000000000..19cda6b8d
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f
@@ -0,0 +1 @@
+s:703:"{"features":[{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":388255,"extent":[20.870461,49.185185,21.485864,48.810739],"country":"Slovensko","osm_key":"place","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"city"}},{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":2320257,"extent":[21.1569866,49.0468381,21.3354271,48.9449997],"country":"Slovensko","osm_key":"place","city":"Prešov","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"district"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_efc348818ff5a7d34fc7305bb97f5738b2ea8f8d b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_efc348818ff5a7d34fc7305bb97f5738b2ea8f8d
new file mode 100644
index 000000000..bc6d78d4c
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_efc348818ff5a7d34fc7305bb97f5738b2ea8f8d
@@ -0,0 +1 @@
+s:813:"{"features":[{"geometry":{"coordinates":[8.1147545,49.833289],"type":"Point"},"type":"Feature","properties":{"osm_id":1310664730,"extent":[8.1147525,49.8336895,8.1147673,49.8331048],"country":"Deutschland","city":"Wörrstadt","countrycode":"DE","postcode":"55286","county":"Alzey-Worms","type":"street","osm_type":"W","osm_key":"highway","osm_value":"primary","name":"Pariser Straße","state":"Rheinland-Pfalz"}},{"geometry":{"coordinates":[13.378690821250334,52.51635135],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":181198,"extent":[13.3777517,52.5169588,13.3798039,52.5157489],"country":"Deutschland","osm_key":"place","city":"Berlin","countrycode":"DE","district":"Mitte","osm_value":"square","postcode":"10117","name":"Pariser Platz","type":"locality"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_f02c930445a15eb88482c0cc3c22a782c9ee6005 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_f02c930445a15eb88482c0cc3c22a782c9ee6005
new file mode 100644
index 000000000..d23d4a4ec
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_f02c930445a15eb88482c0cc3c22a782c9ee6005
@@ -0,0 +1 @@
+s:4208:"{"features":[{"geometry":{"coordinates":[2.2978602225671843,48.8643133],"type":"Point"},"type":"Feature","properties":{"osm_id":79219308,"extent":[2.2971088,48.8647083,2.2984772,48.8639024],"country":"France","city":"Paris","countrycode":"FR","postcode":"75116","locality":"Chaillot","type":"house","osm_type":"W","osm_key":"tourism","street":"Rue Gaston de Saint-Paul","district":"Paris","osm_value":"museum","name":"Musée d'Art Moderne de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3153496472839956,48.866042],"type":"Point"},"type":"Feature","properties":{"osm_id":2778854,"extent":[2.3143339,48.866628,2.3156049,48.8654594],"country":"France","city":"Paris","countrycode":"FR","postcode":"75008","locality":"Quartier des Champs-Élysées","type":"house","osm_type":"R","osm_key":"tourism","street":"Avenue Winston Churchill","district":"Paris","osm_value":"museum","name":"Musée des beaux-arts de la Ville de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3518758,48.850724],"type":"Point"},"type":"Feature","properties":{"osm_id":237003117,"country":"France","city":"Paris","countrycode":"FR","postcode":"75005","locality":"Quartier Saint-Victor","type":"house","osm_type":"N","osm_key":"tourism","street":"Quai de la Tournelle","district":"Paris","osm_value":"museum","name":"Musée de l'Assistance Publique Hôpitaux de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3450724,48.8640506],"type":"Point"},"type":"Feature","properties":{"osm_id":3087374948,"country":"France","city":"Paris","countrycode":"FR","postcode":"75001","locality":"Les Halles","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue du Jour","district":"Paris","osm_value":"museum","name":"Musée du Barreau de Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3453019,48.8625016],"type":"Point"},"type":"Feature","properties":{"osm_id":1028569468,"country":"France","city":"Paris 1er Arrondissement","countrycode":"FR","postcode":"75001","locality":"Les Halles","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue du cinéma","district":"Paris","osm_value":"museum","name":"Salle des collections","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3587471,48.865943],"type":"Point"},"type":"Feature","properties":{"osm_id":5275610309,"country":"France","city":"Paris","countrycode":"FR","postcode":"75003","locality":"Quartier des Arts-et-Métiers","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue de Turbigo","district":"Paris","osm_value":"gallery","name":"Paris-B","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3314642,48.881227],"type":"Point"},"type":"Feature","properties":{"osm_id":10677716841,"country":"France","city":"Paris","countrycode":"FR","postcode":"75009","locality":"Quartier Saint-Georges","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue Blanche","district":"Paris","osm_value":"gallery","name":"Mu Gallery Paris","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3736064,48.8623128],"type":"Point"},"type":"Feature","properties":{"osm_id":10130759032,"country":"France","city":"Paris","countrycode":"FR","postcode":"75011","locality":"Quartier Saint-Ambroise","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue Saint-Sébastien","district":"Paris","osm_value":"gallery","name":"Paris-New York","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3644211,48.8602831],"type":"Point"},"type":"Feature","properties":{"osm_id":3210924575,"country":"France","city":"Paris","countrycode":"FR","postcode":"75003","locality":"Quartier des Archives","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue Debelleyme","district":"Le Marais","osm_value":"gallery","name":"lecœur-paris.com","state":"Île-de-France"}},{"geometry":{"coordinates":[2.3525427,48.86361],"type":"Point"},"type":"Feature","properties":{"osm_id":10744217145,"country":"France","city":"Paris","countrycode":"FR","postcode":"75003","locality":"Quartier Sainte-Avoye","type":"house","osm_type":"N","osm_key":"tourism","street":"Rue Saint-Martin","district":"Paris","osm_value":"gallery","name":"Galerie Paris Horizon","state":"Île-de-France"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/IntegrationTest.php b/src/Provider/Photon/Tests/IntegrationTest.php
new file mode 100644
index 000000000..cc9200e16
--- /dev/null
+++ b/src/Provider/Photon/Tests/IntegrationTest.php
@@ -0,0 +1,49 @@
+
+ */
+class IntegrationTest extends ProviderIntegrationTest
+{
+ protected bool $testAddress = true;
+
+ protected bool $testReverse = true;
+
+ protected bool $testIpv4 = false;
+
+ protected bool $testIpv6 = false;
+
+ protected array $skippedTests = [
+ 'testGeocodeQuery' => 'Photon API returns "Great George Street" for "10 Downing St, London, UK" query.',
+ 'testReverseQueryWithNoResults' => 'Photon API returns "Atlas Buoy 0.00E 0.00N" for reverse query at 0,0.',
+ ];
+
+ protected function createProvider(ClientInterface $httpClient)
+ {
+ return Photon::withKomootServer($httpClient);
+ }
+
+ protected function getCacheDir(): string
+ {
+ return __DIR__.'/.cached_responses';
+ }
+
+ protected function getApiKey(): string
+ {
+ return '';
+ }
+}
diff --git a/src/Provider/Photon/Tests/PhotonTest.php b/src/Provider/Photon/Tests/PhotonTest.php
new file mode 100644
index 000000000..3afd74861
--- /dev/null
+++ b/src/Provider/Photon/Tests/PhotonTest.php
@@ -0,0 +1,270 @@
+expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Photon provider does not support IP addresses.');
+
+ $provider = Photon::withKomootServer($this->getMockedHttpClient());
+ $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
+ }
+
+ public function testGeocodeWithLocalhostIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Photon provider does not support IP addresses.');
+
+ $provider = Photon::withKomootServer($this->getMockedHttpClient());
+ $provider->geocodeQuery(GeocodeQuery::create('::1'));
+ }
+
+ public function testGeocodeWithRealIPv6(): void
+ {
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Photon provider does not support IP addresses.');
+
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $provider->geocodeQuery(GeocodeQuery::create('::ffff:88.188.221.14'));
+ }
+
+ public function testGeocodeQuery(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $results = $provider->geocodeQuery(GeocodeQuery::create('10 avenue Gambetta, Paris, France'));
+
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var PhotonAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(48.8631927, $result->getCoordinates()->getLatitude(), 0.00001);
+ $this->assertEqualsWithDelta(2.3890894, $result->getCoordinates()->getLongitude(), 0.00001);
+ $this->assertEquals('10', $result->getStreetNumber());
+ $this->assertEquals('Avenue Gambetta', $result->getStreetName());
+ $this->assertEquals('75020', $result->getPostalCode());
+ $this->assertEquals('Paris', $result->getLocality());
+ $this->assertEquals('France', $result->getCountry()->getName());
+ $this->assertEquals('FR', $result->getCountry()->getCode());
+
+ $this->assertEquals(1988097192, $result->getOSMId());
+ $this->assertEquals('N', $result->getOSMType());
+ $this->assertEquals('place', $result->getOSMTag()->key);
+ $this->assertEquals('house', $result->getOSMTag()->value);
+ $this->assertEquals('Île-de-France', $result->getState());
+ $this->assertNull($result->getCounty());
+ $this->assertEquals('Paris', $result->getDistrict());
+ }
+
+ public function testGeocodeQueryWithNamedResult(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $results = $provider->geocodeQuery(GeocodeQuery::create('Sherlock Holmes Museum, 221B Baker St, London, England'));
+
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var PhotonAddress $result */
+ $result = $results->first();
+
+ $this->assertEquals('The Sherlock Holmes Museum and shop', $result->getName());
+ }
+
+ public function testGeocodeQueryWithOsmTagFilter(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $query = GeocodeQuery::create('Paris')
+ ->withData('osm_tag', 'tourism:museum')
+ ->withLimit(5);
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertCount(5, $results);
+ foreach ($results as $result) {
+ $this->assertInstanceOf(PhotonAddress::class, $result);
+ $this->assertEquals('tourism', $result->getOSMTag()->key);
+ $this->assertEquals('museum', $result->getOSMTag()->value);
+ }
+ }
+
+ public function testGeocodeQueryWithMultipleOsmTagFilter(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $query = GeocodeQuery::create('Paris')
+ ->withData('osm_tag', ['tourism:museum', 'tourism:gallery'])
+ ->withLimit(10);
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertCount(10, $results);
+ $countMuseums = $countGalleries = 0;
+ foreach ($results as $result) {
+ $this->assertInstanceOf(PhotonAddress::class, $result);
+ $this->assertEquals('tourism', $result->getOSMTag()->key);
+ $this->assertContains($result->getOSMTag()->value, ['museum', 'gallery']);
+ if ('museum' === $result->getOSMTag()->value) {
+ ++$countMuseums;
+ } elseif ('gallery' === $result->getOSMTag()->value) {
+ ++$countGalleries;
+ }
+ }
+ $this->assertGreaterThan(0, $countMuseums);
+ $this->assertGreaterThan(0, $countGalleries);
+ }
+
+ public function testGeocodeQueryWithLatLon(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+
+ $query = GeocodeQuery::create('Paris')->withLimit(1);
+ $results = $provider->geocodeQuery($query);
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+ $this->assertEquals('France', $results->first()->getCountry());
+
+ $query = $query
+ ->withData('lat', 33.661426)
+ ->withData('lon', -95.556321);
+ $results = $provider->geocodeQuery($query);
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+ $this->assertEquals('United States', $results->first()->getCountry());
+ }
+
+ public function testReverseQuery(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $reverseQuery = ReverseQuery::fromCoordinates(52, 10)->withLimit(1);
+ $results = $provider->reverseQuery($reverseQuery);
+
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+
+ /** @var PhotonAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(51.9982968, $result->getCoordinates()->getLatitude(), 0.00001);
+ $this->assertEqualsWithDelta(9.998645, $result->getCoordinates()->getLongitude(), 0.00001);
+ $this->assertEquals('31195', $result->getPostalCode());
+ $this->assertEquals('Lamspringe', $result->getLocality());
+ $this->assertEquals('Deutschland', $result->getCountry()->getName());
+ $this->assertEquals('DE', $result->getCountry()->getCode());
+
+ $this->assertEquals(693697564, $result->getOSMId());
+ $this->assertEquals('N', $result->getOSMType());
+ $this->assertEquals('tourism', $result->getOSMTag()->key);
+ $this->assertEquals('information', $result->getOSMTag()->value);
+ $this->assertEquals('Niedersachsen', $result->getState());
+ $this->assertEquals('Landkreis Hildesheim', $result->getCounty());
+ $this->assertEquals('Sehlem', $result->getDistrict());
+ }
+
+ public function testReverseQueryWithOsmTagFilter(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $reverseQuery = ReverseQuery::fromCoordinates(52.51644, 13.38890)
+ ->withData('osm_tag', 'amenity:pharmacy')
+ ->withLimit(3);
+ $results = $provider->reverseQuery($reverseQuery);
+
+ $this->assertCount(3, $results);
+ foreach ($results as $result) {
+ $this->assertInstanceOf(PhotonAddress::class, $result);
+ $this->assertEquals('amenity', $result->getOSMTag()->key);
+ $this->assertEquals('pharmacy', $result->getOSMTag()->value);
+ }
+ }
+
+ public function testReverseQueryWithLayerCityAndRadiusFilter(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $reverseQuery = ReverseQuery::fromCoordinates(52.51644, 13.38890)
+ ->withData('layer', 'city')
+ ->withData('radius', 20)
+ ->withLimit(1);
+ $result = $provider->reverseQuery($reverseQuery)->first();
+
+ $this->assertInstanceOf(PhotonAddress::class, $result);
+ $this->assertEquals('city', $result->getType());
+ $this->assertEquals('Berlin', $result->getLocality());
+ }
+
+ public function testReverseQueryWithMultipleLayers(): void
+ {
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $reverseQuery = ReverseQuery::fromCoordinates(49.001831, 21.239311)
+ ->withData('layer', 'city')
+ ->withLimit(2);
+
+ $results = $provider->reverseQuery($reverseQuery);
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(1, $results);
+ $result0 = $results->get(0);
+ $this->assertInstanceOf(PhotonAddress::class, $result0);
+ $this->assertEquals('city', $result0->getType());
+
+ $reverseQuery = $reverseQuery->withData('layer', ['city', 'district']);
+ $results = $provider->reverseQuery($reverseQuery);
+ $this->assertInstanceOf(AddressCollection::class, $results);
+ $this->assertCount(2, $results);
+ $result0 = $results->get(0);
+ $this->assertInstanceOf(PhotonAddress::class, $result0);
+ $this->assertEquals('city', $result0->getType());
+ $result1 = $results->get(1);
+ $this->assertInstanceOf(PhotonAddress::class, $result1);
+ $this->assertEquals('district', $result1->getType());
+ }
+
+ public function testGeocodeQueryWithBbox(): void
+ {
+ // Germany
+ $bounds = new \Geocoder\Model\Bounds(
+ south: 47.2701,
+ west: 5.8663,
+ north: 55.992,
+ east: 15.0419
+ );
+
+ $provider = Photon::withKomootServer($this->getHttpClient());
+ $query = GeocodeQuery::create('Paris')
+ ->withLimit(5);
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertCount(5, $results);
+ $this->assertEquals('France', $results->first()->getCountry());
+ $this->assertEquals('Paris', $results->first()->getLocality());
+
+ $query = GeocodeQuery::create('Paris')
+ ->withBounds($bounds)
+ ->withLimit(5);
+ $results = $provider->geocodeQuery($query);
+
+ $this->assertCount(2, $results);
+ $this->assertEquals('Deutschland', $results->first()->getCountry());
+ $this->assertEquals('Wörrstadt', $results->first()->getLocality());
+ }
+}
diff --git a/src/Provider/Photon/composer.json b/src/Provider/Photon/composer.json
new file mode 100644
index 000000000..390fafee7
--- /dev/null
+++ b/src/Provider/Photon/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "geocoder-php/photon-provider",
+ "type": "library",
+ "description": "Geocoder Photon adapter",
+ "keywords": [],
+ "homepage": "http://geocoder-php.org/Geocoder/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jonathan Beliën"
+ }
+ ],
+ "require": {
+ "php": "^8.0",
+ "geocoder-php/common-http": "^4.1",
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
+ },
+ "require-dev": {
+ "geocoder-php/provider-integration-tests": "^1.6.3",
+ "php-http/message": "^1.0",
+ "phpunit/phpunit": "^9.6.11"
+ },
+ "autoload": {
+ "psr-4": {
+ "Geocoder\\Provider\\Photon\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
+ }
+}
diff --git a/src/Provider/Photon/phpunit.xml.dist b/src/Provider/Photon/phpunit.xml.dist
new file mode 100644
index 000000000..26265855a
--- /dev/null
+++ b/src/Provider/Photon/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
diff --git a/src/Provider/PickPoint/.github/workflows/provider.yml b/src/Provider/PickPoint/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/PickPoint/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/PickPoint/.travis.yml b/src/Provider/PickPoint/.travis.yml
deleted file mode 100644
index 648662e93..000000000
--- a/src/Provider/PickPoint/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: php
-sudo: false
-
-php: 7.0
-
-
-install:
- - composer update --prefer-stable --prefer-dist
-
-script:
- - composer test-ci
-
-after_success:
- - wget https://scrutinizer-ci.com/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
-
diff --git a/src/Provider/PickPoint/CHANGELOG.md b/src/Provider/PickPoint/CHANGELOG.md
index 77016a46d..f3d6f2677 100644
--- a/src/Provider/PickPoint/CHANGELOG.md
+++ b/src/Provider/PickPoint/CHANGELOG.md
@@ -2,6 +2,47 @@
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+## 4.4.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 4.3.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 4.2.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 4.1.0
+
+### Removed
+
+- Drop support for PHP < 7.2
+
## 4.0.0
-First release of this library.
+First release of this library.
diff --git a/src/Provider/PickPoint/PickPoint.php b/src/Provider/PickPoint/PickPoint.php
index 8cfae0a5c..ff8515363 100644
--- a/src/Provider/PickPoint/PickPoint.php
+++ b/src/Provider/PickPoint/PickPoint.php
@@ -13,16 +13,16 @@
namespace Geocoder\Provider\PickPoint;
use Geocoder\Collection;
-use Geocoder\Exception\InvalidServerResponse;
use Geocoder\Exception\InvalidCredentials;
+use Geocoder\Exception\InvalidServerResponse;
+use Geocoder\Http\Provider\AbstractHttpProvider;
use Geocoder\Location;
use Geocoder\Model\AddressBuilder;
use Geocoder\Model\AddressCollection;
+use Geocoder\Provider\Provider;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Http\Provider\AbstractHttpProvider;
-use Geocoder\Provider\Provider;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Vladimir Kalinkin
@@ -32,7 +32,7 @@ final class PickPoint extends AbstractHttpProvider implements Provider
/**
* @var string
*/
- const BASE_API_URL = 'https://api.pickpoint.io/v1';
+ public const BASE_API_URL = 'https://api.pickpoint.io/v1';
/**
* @var string
@@ -40,10 +40,10 @@ final class PickPoint extends AbstractHttpProvider implements Provider
private $apiKey;
/**
- * @param HttpClient $client an HTTP adapter
- * @param string $apiKey an API key
+ * @param ClientInterface $client an HTTP adapter
+ * @param string $apiKey an API key
*/
- public function __construct(HttpClient $client, string $apiKey)
+ public function __construct(ClientInterface $client, string $apiKey)
{
if (empty($apiKey)) {
throw new InvalidCredentials('No API key provided.');
@@ -53,9 +53,6 @@ public function __construct(HttpClient $client, string $apiKey)
parent::__construct($client);
}
- /**
- * {@inheritdoc}
- */
public function geocodeQuery(GeocodeQuery $query): Collection
{
$address = $query->getText();
@@ -83,9 +80,6 @@ public function geocodeQuery(GeocodeQuery $query): Collection
return new AddressCollection($results);
}
- /**
- * {@inheritdoc}
- */
public function reverseQuery(ReverseQuery $query): Collection
{
$coordinates = $query->getCoordinates();
@@ -106,12 +100,6 @@ public function reverseQuery(ReverseQuery $query): Collection
return new AddressCollection([$this->xmlResultToArray($result, $addressParts)]);
}
- /**
- * @param \DOMElement $resultNode
- * @param \DOMElement $addressNode
- *
- * @return Location
- */
private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressNode): Location
{
$builder = new AddressBuilder($this->getName());
@@ -134,33 +122,24 @@ private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressN
$builder->setSubLocality($this->getNodeValue($addressNode->getElementsByTagName('suburb')));
$builder->setCountry($this->getNodeValue($addressNode->getElementsByTagName('country')));
$builder->setCountryCode(strtoupper($this->getNodeValue($addressNode->getElementsByTagName('country_code'))));
- $builder->setCoordinates($resultNode->getAttribute('lat'), $resultNode->getAttribute('lon'));
+ $builder->setCoordinates((float) $resultNode->getAttribute('lat'), (float) $resultNode->getAttribute('lon'));
$boundsAttr = $resultNode->getAttribute('boundingbox');
if ($boundsAttr) {
$bounds = [];
list($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']) = explode(',', $boundsAttr);
- $builder->setBounds($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']);
+ $builder->setBounds((float) $bounds['south'], (float) $bounds['north'], (float) $bounds['west'], (float) $bounds['east']);
}
return $builder->build();
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'pickpoint';
}
- /**
- * @param string $url
- * @param string|null $locale
- *
- * @return string
- */
- private function executeQuery(string $url, string $locale = null): string
+ private function executeQuery(string $url, ?string $locale = null): string
{
if (null !== $locale) {
$url = sprintf('%s&accept-language=%s', $url, $locale);
@@ -179,7 +158,10 @@ private function getReverseEndpointUrl(): string
return self::BASE_API_URL.'/reverse?format=xml&lat=%F&lon=%F&addressdetails=1&zoom=%d&key='.$this->apiKey;
}
- private function getNodeValue(\DOMNodeList $element)
+ /**
+ * @param \DOMNodeList<\DOMElement> $element
+ */
+ private function getNodeValue(\DOMNodeList $element): ?string
{
return $element->length ? $element->item(0)->nodeValue : null;
}
diff --git a/src/Provider/PickPoint/Tests/IntegrationTest.php b/src/Provider/PickPoint/Tests/IntegrationTest.php
index 6bb7f5ba2..b7f91bc65 100644
--- a/src/Provider/PickPoint/Tests/IntegrationTest.php
+++ b/src/Provider/PickPoint/Tests/IntegrationTest.php
@@ -12,24 +12,24 @@
use Geocoder\IntegrationTest\ProviderIntegrationTest;
use Geocoder\Provider\PickPoint\PickPoint;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Vladimir Kalinkin
*/
class IntegrationTest extends ProviderIntegrationTest
{
- protected function createProvider(HttpClient $httpClient)
+ protected function createProvider(ClientInterface $httpClient)
{
return new PickPoint($httpClient, $this->getApiKey());
}
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- protected function getApiKey()
+ protected function getApiKey(): string
{
return $_SERVER['PICKPOINT_API_KEY'];
}
diff --git a/src/Provider/PickPoint/Tests/PickPointTest.php b/src/Provider/PickPoint/Tests/PickPointTest.php
index fa95055d2..4880ae0c1 100644
--- a/src/Provider/PickPoint/Tests/PickPointTest.php
+++ b/src/Provider/PickPoint/Tests/PickPointTest.php
@@ -15,31 +15,29 @@
use Geocoder\Collection;
use Geocoder\IntegrationTest\BaseTestCase;
use Geocoder\Location;
+use Geocoder\Provider\PickPoint\PickPoint;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Provider\PickPoint\PickPoint;
class PickPointTest extends BaseTestCase
{
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testGeocodeWithAddressGetsEmptyContent()
+ public function testGeocodeWithAddressGetsEmptyContent(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidServerResponse::class);
+
$provider = new PickPoint($this->getMockedHttpClient(''), 'API-KEY');
$provider->geocodeQuery(GeocodeQuery::create('Läntinen Pitkäkatu 35, Turku'));
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testGeocodeWithAddressGetsEmptyXML()
+ public function testGeocodeWithAddressGetsEmptyXML(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidServerResponse::class);
+
$emptyXML = <<<'XML'
XML;
@@ -47,7 +45,7 @@ public function testGeocodeWithAddressGetsEmptyXML()
$provider->geocodeQuery(GeocodeQuery::create('Läntinen Pitkäkatu 35, Turku'));
}
- public function testReverseWithCoordinatesGetsError()
+ public function testReverseWithCoordinatesGetsError(): void
{
$errorXml = <<<'XML'
@@ -63,17 +61,17 @@ public function testReverseWithCoordinatesGetsError()
$this->assertEquals(0, $result->count());
}
- public function testGetNodeStreetName()
+ public function testGetNodeStreetName(): void
{
$provider = new PickPoint($this->getHttpClient($_SERVER['PICKPOINT_API_KEY']), $_SERVER['PICKPOINT_API_KEY']);
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.86, 2.35));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
$this->assertEquals('Rue Quincampoix', $result->getStreetName());
}
}
diff --git a/src/Provider/PickPoint/composer.json b/src/Provider/PickPoint/composer.json
index f35906752..9f346512d 100644
--- a/src/Provider/PickPoint/composer.json
+++ b/src/Provider/PickPoint/composer.json
@@ -12,34 +12,35 @@
}
],
"require": {
- "php": "^7.0",
+ "php": "^8.0",
"geocoder-php/common-http": "^4.0",
- "willdurand/geocoder": "^4.0"
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
},
"require-dev": {
- "phpunit/phpunit": "^6.1",
- "geocoder-php/provider-integration-tests": "^1.0",
+ "geocoder-php/provider-integration-tests": "^1.6.3",
"php-http/message": "^1.0",
- "php-http/curl-client": "^1.7",
- "nyholm/psr7": "^0.2.2"
+ "phpunit/phpunit": "^9.6.11"
},
- "provide": {
- "geocoder-php/provider-implementation": "1.0"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
},
"autoload": {
- "psr-4": { "Geocoder\\Provider\\PickPoint\\": "" },
+ "psr-4": {
+ "Geocoder\\Provider\\PickPoint\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
}
}
diff --git a/src/Provider/PickPoint/phpunit.xml.dist b/src/Provider/PickPoint/phpunit.xml.dist
index 6dda22e49..ba7b53018 100644
--- a/src/Provider/PickPoint/phpunit.xml.dist
+++ b/src/Provider/PickPoint/phpunit.xml.dist
@@ -1,29 +1,21 @@
-
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
- ./Tests
- ./vendor
-
-
-
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
diff --git a/src/Provider/TomTom/.github/workflows/provider.yml b/src/Provider/TomTom/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/TomTom/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/TomTom/.travis.yml b/src/Provider/TomTom/.travis.yml
deleted file mode 100644
index 648662e93..000000000
--- a/src/Provider/TomTom/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: php
-sudo: false
-
-php: 7.0
-
-
-install:
- - composer update --prefer-stable --prefer-dist
-
-script:
- - composer test-ci
-
-after_success:
- - wget https://scrutinizer-ci.com/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
-
diff --git a/src/Provider/TomTom/CHANGELOG.md b/src/Provider/TomTom/CHANGELOG.md
index 77016a46d..fc19de806 100644
--- a/src/Provider/TomTom/CHANGELOG.md
+++ b/src/Provider/TomTom/CHANGELOG.md
@@ -2,6 +2,57 @@
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+## 4.5.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 4.4.0
+
+### Added
+
+- Add support for postal code
+
+## 4.3.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 4.2.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 4.1.0
+
+### Added
+
+- Add `countrySet` support for TomTom
+
+### Removed
+
+- Drop support for PHP < 7.2
+
## 4.0.0
-First release of this library.
+First release of this library.
diff --git a/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_816a71a09f6bc2ff129553a77e40b3a21332d093 b/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_816a71a09f6bc2ff129553a77e40b3a21332d093
index 5fd7b3c85..f4a534e91 100644
--- a/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_816a71a09f6bc2ff129553a77e40b3a21332d093
+++ b/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_816a71a09f6bc2ff129553a77e40b3a21332d093
@@ -1 +1 @@
-s:167:"{"summary":{"query":"jsajhgsdkfjhsfkjhaldkadjaslgldasd","queryType":"NON_NEAR","queryTime":309,"numResults":0,"offset":0,"totalResults":0,"fuzzyLevel":1},"results":[]}";
+s:167:"{"summary":{"query":"jsajhgsdkfjhsfkjhaldkadjaslgldasd","queryType":"NON_NEAR","queryTime":309,"numResults":0,"offset":0,"totalResults":0,"fuzzyLevel":1},"results":[]}";
\ No newline at end of file
diff --git a/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_f2ff375690ab80aced308d0e113a4a1c749f0f15 b/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_f2ff375690ab80aced308d0e113a4a1c749f0f15
index 5f5fc9b3e..94a3c1605 100644
--- a/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_f2ff375690ab80aced308d0e113a4a1c749f0f15
+++ b/src/Provider/TomTom/Tests/.cached_responses/api.tomtom.com_f2ff375690ab80aced308d0e113a4a1c749f0f15
@@ -1 +1 @@
-s:923:"{"summary":{"query":"10 Downing St, London, UK","queryType":"NON_NEAR","queryTime":127,"numResults":1,"offset":0,"totalResults":1,"fuzzyLevel":1},"results":[{"type":"Address Range","id":"GB/ADDR/p0/2935834","score":9.35,"address":{"streetNumber":"10","streetName":"Downing Street","municipalitySubdivision":"London, Greater London","municipality":"London, Westminster","countrySecondarySubdivision":"London, Greater London","countrySubdivision":"ENG","postalCode":"SW1A 2","countryCode":"GB","country":"United Kingdom","countryCodeISO3":"GBR","freeformAddress":"10 Downing Street, London, SW1A 2","countrySubdivisionName":"England"},"position":{"lat":51.50322,"lon":-0.12611},"viewport":{"topLeftPoint":{"lat":51.50317,"lon":-0.12629},"btmRightPoint":{"lat":51.50317,"lon":-0.12611}},"addressRanges":{"rangeLeft":"0 - 0","rangeRight":"10 - 10","from":{"lat":51.50317,"lon":-0.12611},"to":{"lat":51.50317,"lon":-0.12629}}}]}";
+s:923:"{"summary":{"query":"10 Downing St, London, UK","queryType":"NON_NEAR","queryTime":127,"numResults":1,"offset":0,"totalResults":1,"fuzzyLevel":1},"results":[{"type":"Address Range","id":"GB/ADDR/p0/2935834","score":9.35,"address":{"streetNumber":"10","streetName":"Downing Street","municipalitySubdivision":"London, Greater London","municipality":"London, Westminster","countrySecondarySubdivision":"London, Greater London","countrySubdivision":"ENG","postalCode":"SW1A 2","countryCode":"GB","country":"United Kingdom","countryCodeISO3":"GBR","freeformAddress":"10 Downing Street, London, SW1A 2","countrySubdivisionName":"England"},"position":{"lat":51.50322,"lon":-0.12611},"viewport":{"topLeftPoint":{"lat":51.50317,"lon":-0.12629},"btmRightPoint":{"lat":51.50317,"lon":-0.12611}},"addressRanges":{"rangeLeft":"0 - 0","rangeRight":"10 - 10","from":{"lat":51.50317,"lon":-0.12611},"to":{"lat":51.50317,"lon":-0.12629}}}]}";
\ No newline at end of file
diff --git a/src/Provider/TomTom/Tests/IntegrationTest.php b/src/Provider/TomTom/Tests/IntegrationTest.php
index b5a2af2cb..f3be19540 100644
--- a/src/Provider/TomTom/Tests/IntegrationTest.php
+++ b/src/Provider/TomTom/Tests/IntegrationTest.php
@@ -14,31 +14,32 @@
use Geocoder\IntegrationTest\ProviderIntegrationTest;
use Geocoder\Provider\TomTom\TomTom;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Tobias Nyholm
*/
class IntegrationTest extends ProviderIntegrationTest
{
- protected $skippedTests = [
+ protected array $skippedTests = [
'testReverseQueryWithNoResults' => 'Null island exists. ',
];
- protected $testIpv4 = false;
- protected $testIpv6 = false;
+ protected bool $testIpv4 = false;
- protected function createProvider(HttpClient $httpClient)
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
{
return new TomTom($httpClient, $this->getApiKey());
}
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- protected function getApiKey()
+ protected function getApiKey(): string
{
return $_SERVER['TOMTOM_MAP_KEY'];
}
diff --git a/src/Provider/TomTom/Tests/TomTomTest.php b/src/Provider/TomTom/Tests/TomTomTest.php
index ea8f8f866..d4d74d4c4 100644
--- a/src/Provider/TomTom/Tests/TomTomTest.php
+++ b/src/Provider/TomTom/Tests/TomTomTest.php
@@ -15,45 +15,44 @@
use Geocoder\Collection;
use Geocoder\IntegrationTest\BaseTestCase;
use Geocoder\Location;
+use Geocoder\Provider\TomTom\TomTom;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Provider\TomTom\TomTom;
class TomTomTest extends BaseTestCase
{
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- public function testGetName()
+ public function testGetName(): void
{
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$this->assertEquals('tomtom', $provider->getName());
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testGeocodeWithAddress()
+ public function testGeocodeWithAddress(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidServerResponse::class);
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('Tagensvej 47, 2200 København N'));
}
- public function testGeocodeWithRealAddress()
+ public function testGeocodeWithRealAddress(): void
{
$provider = new TomTom($this->getHttpClient($_SERVER['TOMTOM_MAP_KEY']), $_SERVER['TOMTOM_MAP_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('Tagensvej 47, 2200 København N')->withLocale('en-GB'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(55.70, $result->getCoordinates()->getLatitude(), '', 0.001);
- $this->assertEquals(12.5529, $result->getCoordinates()->getLongitude(), '', 0.001);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(55.70, $result->getCoordinates()->getLatitude(), 0.001);
+ $this->assertEqualsWithDelta(12.5529, $result->getCoordinates()->getLongitude(), 0.001);
$this->assertNull($result->getBounds());
$this->assertEquals(47, $result->getStreetNumber());
$this->assertEquals('Tagensvej', $result->getStreetName());
@@ -65,82 +64,84 @@ public function testGeocodeWithRealAddress()
$this->assertNull($result->getTimezone());
}
- public function testGeocodeWithRealAddressWithFrenchLocale()
+ public function testGeocodeWithRealAddressWithFrenchLocale(): void
{
$provider = new TomTom($this->getHttpClient($_SERVER['TOMTOM_MAP_KEY']), $_SERVER['TOMTOM_MAP_KEY']);
$results = $provider->geocodeQuery(GeocodeQuery::create('Tagensvej 47, 2200 København N')->withLocale('fr-FR'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The TomTom provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv4()
+ public function testGeocodeWithLocalhostIPv4(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The TomTom provider does not support IP addresses, only street addresses.');
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The TomTom provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv6()
+ public function testGeocodeWithLocalhostIPv6(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The TomTom provider does not support IP addresses, only street addresses.');
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('::1'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The TomTom provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithIPv4()
+ public function testGeocodeWithIPv4(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The TomTom provider does not support IP addresses, only street addresses.');
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('74.200.247.59'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The TomTom provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithIPv6()
+ public function testGeocodeWithIPv6(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The TomTom provider does not support IP addresses, only street addresses.');
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->geocodeQuery(GeocodeQuery::create('::ffff:74.200.247.59'));
}
- /**
- * @expectedException \Geocoder\Exception\InvalidCredentials
- * @expectedExceptionMessage No API key provided
- */
- public function testWithoutApiKey()
+ public function testWithoutApiKey(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidCredentials::class);
+ $this->expectExceptionMessage('No API key provided');
+
new TomTom($this->getMockedHttpClient(), '');
}
- /**
- * @expectedException \Geocoder\Exception\InvalidServerResponse
- */
- public function testReverse()
+ public function testReverse(): void
{
+ $this->expectException(\Geocoder\Exception\InvalidServerResponse::class);
+
$provider = new TomTom($this->getMockedHttpClient(), 'api_key');
$provider->reverseQuery(ReverseQuery::fromCoordinates(1, 2));
}
- public function testReverseError400()
+ public function testReverseError400(): void
{
- $error400 = <<<'XML'
-
-XML;
+ $error400 = <<<'JSON'
+{
+ "errorText": "Error parsing 'language': Language tag 'en-ES' not supported",
+ "detailedError": {
+ "code": "BadRequest",
+ "message": "Error parsing 'language': Language tag 'en-ES' not supported",
+ "target": "language"
+ },
+ "httpStatusCode": 400
+}
+JSON;
$provider = new TomTom($this->getMockedHttpClient($error400), 'api_key');
$result = $provider->reverseQuery(ReverseQuery::fromCoordinates(1, 2));
@@ -149,7 +150,7 @@ public function testReverseError400()
$this->assertEquals(0, $result->count());
}
- public function testReverseWithRealCoordinates()
+ public function testReverseWithRealCoordinates(): void
{
if (!isset($_SERVER['TOMTOM_MAP_KEY'])) {
$this->markTestSkipped('You need to configure the TOMTOM_MAP_KEY value in phpunit.xml');
@@ -158,17 +159,17 @@ public function testReverseWithRealCoordinates()
$provider = new TomTom($this->getHttpClient($_SERVER['TOMTOM_MAP_KEY']), $_SERVER['TOMTOM_MAP_KEY']);
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.86321648955345, 2.3887719959020615));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(48.86323, $result->getCoordinates()->getLatitude(), '', 0.001);
- $this->assertEquals(2.38877, $result->getCoordinates()->getLongitude(), '', 0.001);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(48.86323, $result->getCoordinates()->getLatitude(), 0.001);
+ $this->assertEqualsWithDelta(2.38877, $result->getCoordinates()->getLongitude(), 0.001);
$this->assertNull($result->getBounds());
$this->assertEquals('Avenue Gambetta', $result->getStreetName());
- $this->assertNull($result->getPostalCode());
+ $this->assertEquals('75020', $result->getPostalCode());
$this->assertEquals('Paris', $result->getLocality());
$this->assertEquals('20e Arrondissement Paris', $result->getSubLocality());
$this->assertCount(0, $result->getAdminLevels());
@@ -177,7 +178,7 @@ public function testReverseWithRealCoordinates()
$this->assertNull($result->getTimezone());
}
- public function testGeocodeWithRealCoordinates()
+ public function testGeocodeWithRealCoordinates(): void
{
if (!isset($_SERVER['TOMTOM_MAP_KEY'])) {
$this->markTestSkipped('You need to configure the TOMTOM_MAP_KEY value in phpunit.xml');
@@ -186,18 +187,18 @@ public function testGeocodeWithRealCoordinates()
$provider = new TomTom($this->getHttpClient($_SERVER['TOMTOM_MAP_KEY']), $_SERVER['TOMTOM_MAP_KEY']);
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(56.5231, 10.0659));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
/** @var Location $result */
$result = $results->first();
- $this->assertInstanceOf('\Geocoder\Model\Address', $result);
- $this->assertEquals(56.52435, $result->getCoordinates()->getLatitude(), '', 0.001);
- $this->assertEquals(10.06744, $result->getCoordinates()->getLongitude(), '', 0.001);
+ $this->assertInstanceOf(\Geocoder\Model\Address::class, $result);
+ $this->assertEqualsWithDelta(56.52435, $result->getCoordinates()->getLatitude(), 0.001);
+ $this->assertEqualsWithDelta(10.06744, $result->getCoordinates()->getLongitude(), 0.001);
$this->assertNull($result->getBounds());
$this->assertEquals(16, $result->getStreetNumber());
$this->assertEquals('Stabelsvej', $result->getStreetName());
- $this->assertNull($result->getPostalCode());
+ $this->assertEquals('8981', $result->getPostalCode());
$this->assertEquals('Spentrup', $result->getLocality());
$this->assertEquals('Spentrup', $result->getSubLocality());
$this->assertCount(0, $result->getAdminLevels());
diff --git a/src/Provider/TomTom/TomTom.php b/src/Provider/TomTom/TomTom.php
index f55c08701..91de36a63 100644
--- a/src/Provider/TomTom/TomTom.php
+++ b/src/Provider/TomTom/TomTom.php
@@ -15,13 +15,13 @@
use Geocoder\Collection;
use Geocoder\Exception\InvalidCredentials;
use Geocoder\Exception\UnsupportedOperation;
+use Geocoder\Http\Provider\AbstractHttpProvider;
use Geocoder\Model\Address;
use Geocoder\Model\AddressCollection;
+use Geocoder\Provider\Provider;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Http\Provider\AbstractHttpProvider;
-use Geocoder\Provider\Provider;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Antoine Corcy
@@ -31,12 +31,12 @@ final class TomTom extends AbstractHttpProvider implements Provider
/**
* @var string
*/
- const GEOCODE_ENDPOINT_URL = 'https://api.tomtom.com/search/2/geocode/%s.json?key=%s&limit=%d';
+ public const GEOCODE_ENDPOINT_URL = 'https://api.tomtom.com/search/2/geocode/%s.json?key=%s&limit=%d';
/**
* @var string
*/
- const REVERSE_ENDPOINT_URL = 'https://api.tomtom.com/search/2/reverseGeocode/%F,%F.json?key=%s';
+ public const REVERSE_ENDPOINT_URL = 'https://api.tomtom.com/search/2/reverseGeocode/%F,%F.json?key=%s';
/**
* @var string
@@ -44,10 +44,10 @@ final class TomTom extends AbstractHttpProvider implements Provider
private $apiKey;
/**
- * @param HttpClient $client an HTTP adapter
- * @param string $apiKey an API key
+ * @param ClientInterface $client an HTTP adapter
+ * @param string $apiKey an API key
*/
- public function __construct(HttpClient $client, string $apiKey)
+ public function __construct(ClientInterface $client, string $apiKey)
{
if (empty($apiKey)) {
throw new InvalidCredentials('No API key provided.');
@@ -57,9 +57,6 @@ public function __construct(HttpClient $client, string $apiKey)
parent::__construct($client);
}
- /**
- * {@inheritdoc}
- */
public function geocodeQuery(GeocodeQuery $query): Collection
{
$address = $query->getText();
@@ -75,6 +72,10 @@ public function geocodeQuery(GeocodeQuery $query): Collection
$url = sprintf('%s&language=%s', $url, $query->getLocale());
}
+ if (null !== $query->getData('countrySet')) {
+ $url = sprintf('%s&countrySet=%s', $url, $query->getData('countrySet'));
+ }
+
$content = $this->getUrlContents($url);
if (false !== stripos($content, 'Developer Inactive')) {
throw new InvalidCredentials('Map API Key provided is not valid.');
@@ -104,9 +105,6 @@ public function geocodeQuery(GeocodeQuery $query): Collection
return new AddressCollection($locations);
}
- /**
- * {@inheritdoc}
- */
public function reverseQuery(ReverseQuery $query): Collection
{
$coordinates = $query->getCoordinates();
@@ -121,12 +119,13 @@ public function reverseQuery(ReverseQuery $query): Collection
}
$json = json_decode($content, true);
- $results = $json['addresses'];
- if (empty($results)) {
+ if (!isset($json['addresses']) || [] === $json['addresses']) {
return new AddressCollection([]);
}
+ $results = $json['addresses'];
+
$locations = [];
foreach ($results as $item) {
list($lat, $lon) = explode(',', $item['position']);
@@ -138,6 +137,7 @@ public function reverseQuery(ReverseQuery $query): Collection
'streetNumber' => $item['address']['streetNumber'] ?? null,
'locality' => $item['address']['municipality'] ?? null,
'subLocality' => $item['address']['municipalitySubdivision'] ?? null,
+ 'postalCode' => $item['address']['postalCode'] ?? null,
'country' => $item['address']['country'] ?? null,
'countryCode' => $item['address']['countryCode'] ?? null,
]);
@@ -146,9 +146,6 @@ public function reverseQuery(ReverseQuery $query): Collection
return new AddressCollection($locations);
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'tomtom';
diff --git a/src/Provider/TomTom/composer.json b/src/Provider/TomTom/composer.json
index 498190005..64069ba4f 100644
--- a/src/Provider/TomTom/composer.json
+++ b/src/Provider/TomTom/composer.json
@@ -12,34 +12,35 @@
}
],
"require": {
- "php": "^7.0",
+ "php": "^8.0",
"geocoder-php/common-http": "^4.0",
- "willdurand/geocoder": "^4.0"
+ "willdurand/geocoder": "^4.0|^5.0"
+ },
+ "provide": {
+ "geocoder-php/provider-implementation": "1.0"
},
"require-dev": {
- "phpunit/phpunit": "^6.1",
- "geocoder-php/provider-integration-tests": "^1.0",
+ "geocoder-php/provider-integration-tests": "^1.6.3",
"php-http/message": "^1.0",
- "php-http/curl-client": "^1.7",
- "nyholm/psr7": "^0.2.2"
+ "phpunit/phpunit": "^9.6.11"
},
- "provide": {
- "geocoder-php/provider-implementation": "1.0"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
},
"autoload": {
- "psr-4": { "Geocoder\\Provider\\TomTom\\": "" },
+ "psr-4": {
+ "Geocoder\\Provider\\TomTom\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
}
}
diff --git a/src/Provider/TomTom/phpunit.xml.dist b/src/Provider/TomTom/phpunit.xml.dist
index 54657b912..46e7ec276 100644
--- a/src/Provider/TomTom/phpunit.xml.dist
+++ b/src/Provider/TomTom/phpunit.xml.dist
@@ -1,29 +1,21 @@
-
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
- ./Tests
- ./vendor
-
-
-
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
diff --git a/src/Provider/Yandex/.github/workflows/provider.yml b/src/Provider/Yandex/.github/workflows/provider.yml
new file mode 100644
index 000000000..590442d49
--- /dev/null
+++ b/src/Provider/Yandex/.github/workflows/provider.yml
@@ -0,0 +1,33 @@
+name: Provider
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ name: PHP ${{ matrix.php-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php-version: ['8.0', '8.1', '8.2', '8.3', '8.4']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: curl
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-progress
+ - name: Run test suite
+ run: composer run-script test-ci
+ - name: Upload Coverage report
+ run: |
+ wget https://scrutinizer-ci.com/ocular.phar
+ php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/Yandex/.travis.yml b/src/Provider/Yandex/.travis.yml
deleted file mode 100644
index 147fe662b..000000000
--- a/src/Provider/Yandex/.travis.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-language: php
-sudo: false
-
-php: 7.0
-
-
-install:
- - composer update --prefer-stable --prefer-dist
-
-script:
- - composer test-ci
-
-after_success:
- - wget https://scrutinizer-ci.com/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
-
-after_success:
- - wget https://scrutinizer-ci.com/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
diff --git a/src/Provider/Yandex/CHANGELOG.md b/src/Provider/Yandex/CHANGELOG.md
index 77016a46d..e527b1bbe 100644
--- a/src/Provider/Yandex/CHANGELOG.md
+++ b/src/Provider/Yandex/CHANGELOG.md
@@ -2,6 +2,69 @@
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+## 4.6.0
+
+### Added
+
+- Add support for PHP Geocoder 5
+
+## 4.5.0
+
+### Added
+
+- Add support for PHP 8.1
+- Add GitHub Actions workflow
+
+### Removed
+
+- Drop support for PHP 7.3
+
+### Changed
+
+- Migrate from PHP-HTTP to PSR-18 client
+
+## 4.4.0
+
+### Added
+
+- Add support for PHP 8.0
+
+### Removed
+
+- Drop support for PHP 7.2
+
+### Changed
+
+- Upgrade PHPUnit to version 9
+
+## 4.3.0
+
+### Removed
+
+- Drop support for PHP < 7.2
+
+## 4.2.0
+
+### Added
+
+- Yandex now requires an API key
+
+## 4.1.0
+
+### Added
+
+- Support for [`kind`](https://tech.yandex.ru/maps/doc/geocoder/desc/reference/kind-docpage/).
+
+### Changed
+
+- Improve tests by using `Geocoder\Provider\Yandex\Model\YandexAddress`
+
+## 4.0.1
+
+### Fixed
+
+- Added `geocoder-php/common-http` dependency
+
## 4.0.0
-First release of this library.
+First release of this library.
diff --git a/src/Provider/Yandex/Model/YandexAddress.php b/src/Provider/Yandex/Model/YandexAddress.php
index 31bb83670..8919c86bc 100644
--- a/src/Provider/Yandex/Model/YandexAddress.php
+++ b/src/Provider/Yandex/Model/YandexAddress.php
@@ -32,19 +32,21 @@ final class YandexAddress extends Address
private $name;
/**
- * @return null|string
+ * The kind of this location.
+ *
+ * @var string|null
+ */
+ private $kind;
+
+ /**
+ * @return string|null
*/
public function getPrecision()
{
return $this->precision;
}
- /**
- * @param null|string $precision
- *
- * @return YandexAddress
- */
- public function withPrecision(string $precision = null): YandexAddress
+ public function withPrecision(?string $precision = null): self
{
$new = clone $this;
$new->precision = $precision;
@@ -53,22 +55,33 @@ public function withPrecision(string $precision = null): YandexAddress
}
/**
- * @return null|string
+ * @return string|null
*/
public function getName()
{
return $this->name;
}
+ public function withName(?string $name = null): self
+ {
+ $new = clone $this;
+ $new->name = $name;
+
+ return $new;
+ }
+
/**
- * @param null|string $name
- *
- * @return YandexAddress
+ * @return string|null
*/
- public function withName(string $name = null): YandexAddress
+ public function getKind()
+ {
+ return $this->kind;
+ }
+
+ public function withKind(?string $kind = null): self
{
$new = clone $this;
- $new->name = $name;
+ $new->kind = $kind;
return $new;
}
diff --git a/src/Provider/Yandex/Readme.md b/src/Provider/Yandex/README.md
similarity index 73%
rename from src/Provider/Yandex/Readme.md
rename to src/Provider/Yandex/README.md
index c4b0b1a04..7ed0cc7da 100644
--- a/src/Provider/Yandex/Readme.md
+++ b/src/Provider/Yandex/README.md
@@ -8,7 +8,7 @@
[](LICENSE)
This is the Yandex 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.
### Install
@@ -16,15 +16,27 @@ This is the Yandex provider from the PHP Geocoder. This is a **READ ONLY** repos
composer require geocoder-php/yandex-provider
```
+## Usage
+
+The API now requires an API key. [See here for more information](https://yandex.ru/blog/mapsapi/novye-pravila-dostupa-k-api-kart?from=tech_pp).
+
+```php
+$httpClient = new \Http\Discovery\Psr18Client();
+$provider = new \Geocoder\Provider\Yandex\Yandex($httpClient, null, ');
+
+$result = $geocoder->geocodeQuery(GeocodeQuery::create('ул.Ленина, 19, Минск 220030, Республика Беларусь'));
+$result = $geocoder->reverseQuery(ReverseQuery::fromCoordinates(...));
+```
+
### Note
The default language-locale is `ru-RU`, you can choose between `uk-UA`, `be-BY`,
-`en-US`, `en-BR` and `tr-TR`.
+`en-US`, `en-BR` and `tr-TR`.
It's possible to precise the toponym to get more accurate result for reverse geocoding:
`house`, `street`, `metro`, `district` and `locality`.
### 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).
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_1ece1b149ef392c5f0da11797f637e7ae221bd17 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_1ece1b149ef392c5f0da11797f637e7ae221bd17
index bdda06df6..21d48f00a 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_1ece1b149ef392c5f0da11797f637e7ae221bd17
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_1ece1b149ef392c5f0da11797f637e7ae221bd17
@@ -1 +1 @@
-s:4485:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"Copenhagen, Denmark","found":"13","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Данія, Столичная область, Копенгаген","precision":"other","AddressDetails":{"Country":{"AddressLine":"Столичная область, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Копенгаген","Locality":{"LocalityName":"Копенгаген"}}}}}}},"description":"Столичная область, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.45295 55.614999","upperCorner":"12.65075 55.732585"}},"Point":{"pos":"12.567593 55.675676"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Данія, область Южная Дания, Миддельфарт, Копенгаген","precision":"other","AddressDetails":{"Country":{"AddressLine":"область Южная Дания, Миддельфарт, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"область Южная Дания","SubAdministrativeArea":{"SubAdministrativeAreaName":"Миддельфарт","Locality":{"LocalityName":"Копенгаген"}}}}}}},"description":"Миддельфарт, область Южная Дания, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"9.971219 55.454366","upperCorner":"9.974965 55.457398"}},"Point":{"pos":"9.972854 55.455739"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Данія, Столичная область, Копенгаген, Копенгаген","precision":"other","AddressDetails":{"Country":{"AddressLine":"Столичная область, Копенгаген, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Копенгаген","Locality":{"LocalityName":"Копенгаген","DependentLocality":{"DependentLocalityName":"Копенгаген"}}}}}}}},"description":"Копенгаген, Столичная область, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.518464 55.703961","upperCorner":"12.551396 55.722553"}},"Point":{"pos":"12.534930 55.713258"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Данія, Столичная область, Копенгаген, Копенгаген","precision":"other","AddressDetails":{"Country":{"AddressLine":"Столичная область, Копенгаген, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Копенгаген","Locality":{"LocalityName":"Копенгаген","DependentLocality":{"DependentLocalityName":"Копенгаген"}}}}}}}},"description":"Копенгаген, Столичная область, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.561736 55.689578","upperCorner":"12.594668 55.708176"}},"Point":{"pos":"12.578202 55.698878"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Данія, Столичная область, Копенгаген, Копенгаген","precision":"other","AddressDetails":{"Country":{"AddressLine":"Столичная область, Копенгаген, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Копенгаген","Locality":{"LocalityName":"Копенгаген","DependentLocality":{"DependentLocalityName":"Копенгаген"}}}}}}}},"description":"Копенгаген, Столичная область, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.538361 55.681077","upperCorner":"12.571294 55.69968"}},"Point":{"pos":"12.554827 55.690380"}}}]}}}";
\ No newline at end of file
+s:3537:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"Copenhagen, Denmark","found":"3","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Данія, Копенгаген","precision":"other","Address":{"country_code":"DK","formatted":"Данія, Копенгаген","Components":[{"kind":"country","name":"Данія"},{"kind":"province","name":"Столичная область"},{"kind":"area","name":"Фредериксберг"},{"kind":"locality","name":"Копенгаген"}]},"AddressDetails":{"Country":{"AddressLine":"Данія, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Фредериксберг","Locality":{"LocalityName":"Копенгаген"}}}}}}},"description":"Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.453084 55.615421","upperCorner":"12.652043 55.732727"}},"Point":{"pos":"12.585828 55.680661"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Данія, Столичная область, Копенгаген","precision":"other","Address":{"country_code":"DK","formatted":"Данія, Столичная область, Копенгаген","Components":[{"kind":"country","name":"Данія"},{"kind":"province","name":"Столичная область"},{"kind":"area","name":"Копенгаген"},{"kind":"locality","name":"Копенгаген"}]},"AddressDetails":{"Country":{"AddressLine":"Данія, Столичная область, Копенгаген","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Столичная область","SubAdministrativeArea":{"SubAdministrativeAreaName":"Копенгаген","Locality":{"LocalityName":"Копенгаген"}}}}}}},"description":"Столичная область, Данія","name":"Копенгаген","boundedBy":{"Envelope":{"lowerCorner":"12.313989 55.584039","upperCorner":"12.656876 55.833359"}},"Point":{"pos":"12.463837 55.716853"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"airport","text":"Данія, Зеландия, Роскилле, Копенгаген Луфтаун Роскилле","precision":"other","Address":{"country_code":"DK","formatted":"Данія, Зеландия, Роскилле, Копенгаген Луфтаун Роскилле","Components":[{"kind":"country","name":"Данія"},{"kind":"province","name":"Зеландия"},{"kind":"area","name":"Роскилле"},{"kind":"airport","name":"Копенгаген Луфтаун Роскилле"}]},"AddressDetails":{"Country":{"AddressLine":"Данія, Зеландия, Роскилле, Копенгаген Луфтаун Роскилле","CountryNameCode":"DK","CountryName":"Данія","AdministrativeArea":{"AdministrativeAreaName":"Зеландия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Роскилле","Locality":{"DependentLocality":{"DependentLocalityName":"Копенгаген Луфтаун Роскилле"}}}}}}}},"description":"Роскилле, Зеландия, Данія","name":"Копенгаген Луфтаун Роскилле","boundedBy":{"Envelope":{"lowerCorner":"12.107035 55.574747","upperCorner":"12.147244 55.596788"}},"Point":{"pos":"12.130041 55.590338"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_5ded45ea75499ea33bc71c8cbca1ab897dae8164 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_5ded45ea75499ea33bc71c8cbca1ab897dae8164
index 21954716c..8a772679e 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_5ded45ea75499ea33bc71c8cbca1ab897dae8164
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_5ded45ea75499ea33bc71c8cbca1ab897dae8164
@@ -1 +1 @@
-s:4108:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"1600 Pennsylvania Ave, Washington","found":"33","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"United States, District of Columbia, Washington, Pennsylvania Ave NW, 1600","precision":"exact","AddressDetails":{"Country":{"AddressLine":"District of Columbia, Washington, Pennsylvania Ave NW, 1600","CountryNameCode":"US","CountryName":"United States","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","SubAdministrativeArea":{"SubAdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Ave NW","Premise":{"PremiseNumber":"1600"}}}}}}}}},"description":"Washington, District of Columbia, United States","name":"Pennsylvania Ave NW, 1600","boundedBy":{"Envelope":{"lowerCorner":"-77.046921 38.891265","upperCorner":"-77.030464 38.904125"}},"Point":{"pos":"-77.038692 38.897695"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States, District of Columbia, Washington, Pennsylvania Ave SE","precision":"street","AddressDetails":{"Country":{"AddressLine":"District of Columbia, Washington, Pennsylvania Ave SE","CountryNameCode":"US","CountryName":"United States","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","SubAdministrativeArea":{"SubAdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Ave SE"}}}}}}}},"description":"Washington, District of Columbia, United States","name":"Pennsylvania Ave SE","boundedBy":{"Envelope":{"lowerCorner":"-77.003532 38.863739","upperCorner":"-76.946777 38.887825"}},"Point":{"pos":"-76.975235 38.875565"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States, Maryland, Washington, Pennsylvania Ave","precision":"street","AddressDetails":{"Country":{"AddressLine":"Maryland, Washington, Pennsylvania Ave","CountryNameCode":"US","CountryName":"United States","AdministrativeArea":{"AdministrativeAreaName":"Maryland","SubAdministrativeArea":{"SubAdministrativeAreaName":"Washington","Locality":{"Thoroughfare":{"ThoroughfareName":"Pennsylvania Ave"}}}}}}}},"description":"Washington, Maryland, United States","name":"Pennsylvania Ave","boundedBy":{"Envelope":{"lowerCorner":"-77.724152 39.649717","upperCorner":"-77.717513 39.721407"}},"Point":{"pos":"-77.720774 39.685568"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States, Pennsylvania, Washington, Pennsylvania Ave","precision":"street","AddressDetails":{"Country":{"AddressLine":"Pennsylvania, Washington, Pennsylvania Ave","CountryNameCode":"US","CountryName":"United States","AdministrativeArea":{"AdministrativeAreaName":"Pennsylvania","Locality":{"DependentLocality":{"DependentLocalityName":"Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Ave"}}}}}}}},"description":"Washington, Pennsylvania, United States","name":"Pennsylvania Ave","boundedBy":{"Envelope":{"lowerCorner":"-79.890044 40.118144","upperCorner":"-79.867263 40.131418"}},"Point":{"pos":"-79.878914 40.124233"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States, District of Columbia, Washington, Pennsylvania Avenue Rear SE","precision":"street","AddressDetails":{"Country":{"AddressLine":"District of Columbia, Washington, Pennsylvania Avenue Rear SE","CountryNameCode":"US","CountryName":"United States","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","SubAdministrativeArea":{"SubAdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Avenue Rear SE"}}}}}}}},"description":"Washington, District of Columbia, United States","name":"Pennsylvania Avenue Rear SE","boundedBy":{"Envelope":{"lowerCorner":"-76.98341 38.879315","upperCorner":"-76.981649 38.881175"}},"Point":{"pos":"-76.982790 38.879891"}}}]}}}";
\ No newline at end of file
+s:6302:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"1600 Pennsylvania Ave, Washington","found":"5","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Northwest","precision":"street","Address":{"country_code":"US","formatted":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Northwest","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"District of Columbia"},{"kind":"locality","name":"City of Washington"},{"kind":"street","name":"Pennsylvania Avenue Northwest"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Northwest","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"City of Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Avenue Northwest"}}}}}}},"description":"City of Washington, District of Columbia, United States of America","name":"Pennsylvania Avenue Northwest","boundedBy":{"Envelope":{"lowerCorner":"-77.058105 38.890612","upperCorner":"-77.012426 38.905248"}},"Point":{"pos":"-77.033608 38.895512"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Southeast","precision":"street","Address":{"country_code":"US","formatted":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Southeast","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"District of Columbia"},{"kind":"locality","name":"City of Washington"},{"kind":"street","name":"Pennsylvania Avenue Southeast"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Southeast","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"City of Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Avenue Southeast"}}}}}}},"description":"City of Washington, District of Columbia, United States of America","name":"Pennsylvania Avenue Southeast","boundedBy":{"Envelope":{"lowerCorner":"-77.003496 38.878592","upperCorner":"-76.981559 38.887621"}},"Point":{"pos":"-76.992537 38.883092"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Hights","precision":"other","Address":{"country_code":"US","formatted":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Hights","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"District of Columbia"},{"kind":"locality","name":"City of Washington"},{"kind":"district","name":"Pennsylvania Avenue Hights"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, District of Columbia, City of Washington, Pennsylvania Avenue Hights","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"City of Washington","DependentLocality":{"DependentLocalityName":"Pennsylvania Avenue Hights"}}}}}}},"description":"City of Washington, District of Columbia, United States of America","name":"Pennsylvania Avenue Hights","boundedBy":{"Envelope":{"lowerCorner":"-76.975253 38.855325","upperCorner":"-76.942321 38.88107"}},"Point":{"pos":"-76.958787 38.868199"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"United States of America, Maryland, Prince George's County, Washington, Pennsylvania Avenue Southeast","precision":"street","Address":{"country_code":"US","formatted":"United States of America, Maryland, Prince George's County, Washington, Pennsylvania Avenue Southeast","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"Maryland"},{"kind":"area","name":"Prince George's County"},{"kind":"locality","name":"Washington"},{"kind":"street","name":"Pennsylvania Avenue Southeast"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, Maryland, Prince George's County, Washington, Pennsylvania Avenue Southeast","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"Maryland","SubAdministrativeArea":{"SubAdministrativeAreaName":"Prince George's County","Locality":{"LocalityName":"Washington","Thoroughfare":{"ThoroughfareName":"Pennsylvania Avenue Southeast"}}}}}}}},"description":"Washington, Prince George's County, Maryland, United States of America","name":"Pennsylvania Avenue Southeast","boundedBy":{"Envelope":{"lowerCorner":"-76.974508 38.853175","upperCorner":"-76.909955 38.875144"}},"Point":{"pos":"-76.942168 38.862966"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Auvergne-Rhône-Alpes, Isère, Grenoble, Avenue Washington","precision":"street","Address":{"country_code":"FR","formatted":"France, Auvergne-Rhône-Alpes, Isère, Grenoble, Avenue Washington","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Auvergne-Rhône-Alpes"},{"kind":"area","name":"Isère"},{"kind":"locality","name":"Grenoble"},{"kind":"street","name":"Avenue Washington"}]},"AddressDetails":{"Country":{"AddressLine":"France, Auvergne-Rhône-Alpes, Isère, Grenoble, Avenue Washington","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Auvergne-Rhône-Alpes","SubAdministrativeArea":{"SubAdministrativeAreaName":"Isère","Locality":{"LocalityName":"Grenoble","Thoroughfare":{"ThoroughfareName":"Avenue Washington"}}}}}}}},"description":"Grenoble, Isère, Auvergne-Rhône-Alpes, France","name":"Avenue Washington","boundedBy":{"Envelope":{"lowerCorner":"5.746298 45.175579","upperCorner":"5.747915 45.182116"}},"Point":{"pos":"5.747116 45.178851"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_6e6f03453e3d167c8f98f8e81b4967b63c8c7a6f b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_6e6f03453e3d167c8f98f8e81b4967b63c8c7a6f
index f54f3da17..73f653a55 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_6e6f03453e3d167c8f98f8e81b4967b63c8c7a6f
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_6e6f03453e3d167c8f98f8e81b4967b63c8c7a6f
@@ -1 +1 @@
-s:3825:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"2.388772,48.863216","found":"8","results":"5","Point":{"pos":"2.388772 48.863216"}}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"Франция, Иль-Де-Франс, Париж, XX округ, Avenue Gambetta","precision":"street","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж, XX округ, Avenue Gambetta","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-Де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta"}}}}}}}}},"description":"XX округ, Париж, Иль-Де-Франс, Франция","name":"Avenue Gambetta","boundedBy":{"Envelope":{"lowerCorner":"2.387497 48.86294","upperCorner":"2.406587 48.877067"}},"Point":{"pos":"2.388773 48.863212"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Франция, Иль-Де-Франс, Париж, XX округ","precision":"other","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж, XX округ","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-Де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ"}}}}}}}},"description":"Париж, Иль-Де-Франс, Франция","name":"XX округ","boundedBy":{"Envelope":{"lowerCorner":"2.377939 48.846697","upperCorner":"2.416217 48.878252"}},"Point":{"pos":"2.399355 48.864848"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Франция, Иль-Де-Франс, Париж","precision":"other","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-Де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж","Locality":{"LocalityName":"Париж"}}}}}}},"description":"Иль-Де-Франс, Франция","name":"Париж","boundedBy":{"Envelope":{"lowerCorner":"2.251232 48.815727","upperCorner":"2.416235 48.902474"}},"Point":{"pos":"2.341198 48.856929"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"area","text":"Франция, Иль-Де-Франс, Париж","precision":"other","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-Де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж"}}}}}},"description":"Иль-Де-Франс, Франция","name":"Париж","boundedBy":{"Envelope":{"lowerCorner":"2.223824 48.815727","upperCorner":"2.469792 48.902474"}},"Point":{"pos":"2.341198 48.856929"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"area","text":"Франция, Иль-Де-Франс, Париж","precision":"other","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Париж","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж"}}}}}},"description":"Иль-Де-Франс, Франция","name":"Париж","boundedBy":{"Envelope":{"lowerCorner":"2.223824 48.815727","upperCorner":"2.469792 48.902474"}},"Point":{"pos":"2.341198 48.856929"}}}]}}}";
\ No newline at end of file
+s:6547:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"2.388772,48.863216","found":"9","results":"5","Point":{"pos":"2.388772 48.863216"}}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 1","precision":"exact","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 1","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"locality","name":"Париж"},{"kind":"district","name":"XX округ Парижа"},{"kind":"street","name":"Avenue Gambetta"},{"kind":"house","name":"1"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 1","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ Парижа","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta","Premise":{"PremiseNumber":"1"}}}}}}}}},"description":"XX округ Парижа, Париж, Иль-де-Франс, Франция","name":"Avenue Gambetta, 1","boundedBy":{"Envelope":{"lowerCorner":"2.384623 48.860646","upperCorner":"2.392833 48.866063"}},"Point":{"pos":"2.388728 48.863355"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta","precision":"street","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"locality","name":"Париж"},{"kind":"district","name":"XX округ Парижа"},{"kind":"street","name":"Avenue Gambetta"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ Парижа","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta"}}}}}}}},"description":"XX округ Парижа, Париж, Иль-де-Франс, Франция","name":"Avenue Gambetta","boundedBy":{"Envelope":{"lowerCorner":"2.38889 48.86323","upperCorner":"2.406003 48.875722"}},"Point":{"pos":"2.40028 48.866958"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Картье-дю-Пер-Лашез","precision":"other","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Картье-дю-Пер-Лашез","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"locality","name":"Париж"},{"kind":"district","name":"XX округ Парижа"},{"kind":"district","name":"Картье-дю-Пер-Лашез"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Картье-дю-Пер-Лашез","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ Парижа","DependentLocality":{"DependentLocalityName":"Картье-дю-Пер-Лашез"}}}}}}}},"description":"XX округ Парижа, Париж, Иль-де-Франс, Франция","name":"Картье-дю-Пер-Лашез","boundedBy":{"Envelope":{"lowerCorner":"2.382781 48.85642","upperCorner":"2.406344 48.870816"}},"Point":{"pos":"2.392115 48.864084"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"district","text":"Франция, Иль-де-Франс, Париж, XX округ Парижа","precision":"other","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Париж, XX округ Парижа","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"locality","name":"Париж"},{"kind":"district","name":"XX округ Парижа"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Париж, XX округ Парижа","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ Парижа"}}}}}}},"description":"Париж, Иль-де-Франс, Франция","name":"XX округ Парижа","boundedBy":{"Envelope":{"lowerCorner":"2.376861 48.846644","upperCorner":"2.416333 48.878412"}},"Point":{"pos":"2.402266 48.863829"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"area","text":"Франция, Иль-де-Франс, Департамент Париж, Округ Париж","precision":"other","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Департамент Париж, Округ Париж","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"area","name":"Департамент Париж"},{"kind":"area","name":"Округ Париж"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Департамент Париж, Округ Париж","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Департамент Париж"}}}}}},"description":"Департамент Париж, Иль-де-Франс, Франция","name":"Округ Париж","boundedBy":{"Envelope":{"lowerCorner":"2.22422 48.815496","upperCorner":"2.469864 48.902101"}},"Point":{"pos":"2.320052 48.858797"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_80749871012997dfec54797b934863b875718034 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_80749871012997dfec54797b934863b875718034
index 4c538f979..80ac3e531 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_80749871012997dfec54797b934863b875718034
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_80749871012997dfec54797b934863b875718034
@@ -1 +1 @@
-s:6208:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"22.256784,60.453947","found":"70","results":"5","boundedBy":{"Envelope":{"lowerCorner":"22.254288 60.451449","upperCorner":"22.259283 60.456445"}},"Point":{"pos":"22.256784 60.453947"},"kind":"house"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Bangårdsgatan, 36","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Bangårdsgatan, 36","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Юго-Западная Финляндия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Исконная Финляндия","Locality":{"LocalityName":"Турку","DependentLocality":{"DependentLocalityName":"Кескуста","Thoroughfare":{"ThoroughfareName":"Bangårdsgatan","Premise":{"PremiseNumber":"36"}}}}}}}}}},"description":"Кескуста, Турку, Исконная Финляндия, Юго-Западная Финляндия, Фінляндія","name":"Bangårdsgatan, 36","boundedBy":{"Envelope":{"lowerCorner":"22.248557 60.450242","upperCorner":"22.265014 60.458371"}},"Point":{"pos":"22.256785 60.454307"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Ratapihankatu, 36","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Ratapihankatu, 36","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Юго-Западная Финляндия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Исконная Финляндия","Locality":{"LocalityName":"Турку","DependentLocality":{"DependentLocalityName":"Кескуста","Thoroughfare":{"ThoroughfareName":"Ratapihankatu","Premise":{"PremiseNumber":"36"}}}}}}}}}},"description":"Кескуста, Турку, Исконная Финляндия, Юго-Западная Финляндия, Фінляндія","name":"Ratapihankatu, 36","boundedBy":{"Envelope":{"lowerCorner":"22.248557 60.450242","upperCorner":"22.265014 60.458371"}},"Point":{"pos":"22.256785 60.454307"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humalistonkatu, 15b","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humalistonkatu, 15b","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Юго-Западная Финляндия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Исконная Финляндия","Locality":{"LocalityName":"Турку","DependentLocality":{"DependentLocalityName":"Кескуста","Thoroughfare":{"ThoroughfareName":"Humalistonkatu","Premise":{"PremiseNumber":"15b"}}}}}}}}}},"description":"Кескуста, Турку, Исконная Финляндия, Юго-Западная Финляндия, Фінляндія","name":"Humalistonkatu, 15b","boundedBy":{"Envelope":{"lowerCorner":"22.248125 60.449332","upperCorner":"22.264583 60.457461"}},"Point":{"pos":"22.256354 60.453397"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humlegårdsgatan, 15b","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humlegårdsgatan, 15b","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Юго-Западная Финляндия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Исконная Финляндия","Locality":{"LocalityName":"Турку","DependentLocality":{"DependentLocalityName":"Кескуста","Thoroughfare":{"ThoroughfareName":"Humlegårdsgatan","Premise":{"PremiseNumber":"15b"}}}}}}}}}},"description":"Кескуста, Турку, Исконная Финляндия, Юго-Западная Финляндия, Фінляндія","name":"Humlegårdsgatan, 15b","boundedBy":{"Envelope":{"lowerCorner":"22.248125 60.449332","upperCorner":"22.264583 60.457461"}},"Point":{"pos":"22.256354 60.453397"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humalistonkatu, 15a","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Юго-Западная Финляндия, Исконная Финляндия, Турку, Кескуста, Humalistonkatu, 15a","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Юго-Западная Финляндия","SubAdministrativeArea":{"SubAdministrativeAreaName":"Исконная Финляндия","Locality":{"LocalityName":"Турку","DependentLocality":{"DependentLocalityName":"Кескуста","Thoroughfare":{"ThoroughfareName":"Humalistonkatu","Premise":{"PremiseNumber":"15a"}}}}}}}}}},"description":"Кескуста, Турку, Исконная Финляндия, Юго-Западная Финляндия, Фінляндія","name":"Humalistonkatu, 15a","boundedBy":{"Envelope":{"lowerCorner":"22.248889 60.449235","upperCorner":"22.265346 60.457364"}},"Point":{"pos":"22.257118 60.453299"}}}]}}}";
\ No newline at end of file
+s:7433:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"22.256784,60.453947","found":"53","results":"5","boundedBy":{"Envelope":{"lowerCorner":"22.254288 60.451449","upperCorner":"22.259283 60.456445"}},"Point":{"pos":"22.256784 60.453947"},"kind":"house"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 35","precision":"exact","Address":{"country_code":"FI","formatted":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 35","Components":[{"kind":"country","name":"Фінляндія"},{"kind":"province","name":"Исконная Финляндия"},{"kind":"district","name":"Обо"},{"kind":"locality","name":"город Турку"},{"kind":"street","name":"Läntinen Pitkäkatu"},{"kind":"house","name":"35"}]},"AddressDetails":{"Country":{"AddressLine":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 35","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Исконная Финляндия","Locality":{"DependentLocality":{"DependentLocalityName":"Обо","DependentLocality":{"DependentLocalityName":"город Турку","Thoroughfare":{"ThoroughfareName":"Läntinen Pitkäkatu","Premise":{"PremiseNumber":"35"}}}}}}}}}},"description":"город Турку, Исконная Финляндия, Фінляндія","name":"Läntinen Pitkäkatu, 35","boundedBy":{"Envelope":{"lowerCorner":"22.252725 60.451862","upperCorner":"22.260935 60.455917"}},"Point":{"pos":"22.25683 60.45389"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 36","precision":"exact","Address":{"country_code":"FI","formatted":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 36","Components":[{"kind":"country","name":"Фінляндія"},{"kind":"province","name":"Исконная Финляндия"},{"kind":"district","name":"Обо"},{"kind":"locality","name":"город Турку"},{"kind":"street","name":"Ratapihankatu"},{"kind":"house","name":"36"}]},"AddressDetails":{"Country":{"AddressLine":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 36","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Исконная Финляндия","Locality":{"DependentLocality":{"DependentLocalityName":"Обо","DependentLocality":{"DependentLocalityName":"город Турку","Thoroughfare":{"ThoroughfareName":"Ratapihankatu","Premise":{"PremiseNumber":"36"}}}}}}}}}},"description":"город Турку, Исконная Финляндия, Фінляндія","name":"Ratapihankatu, 36","boundedBy":{"Envelope":{"lowerCorner":"22.252761 60.452266","upperCorner":"22.260971 60.456321"}},"Point":{"pos":"22.256866 60.454293"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 33","precision":"exact","Address":{"country_code":"FI","formatted":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 33","Components":[{"kind":"country","name":"Фінляндія"},{"kind":"province","name":"Исконная Финляндия"},{"kind":"district","name":"Обо"},{"kind":"locality","name":"город Турку"},{"kind":"street","name":"Läntinen Pitkäkatu"},{"kind":"house","name":"33"}]},"AddressDetails":{"Country":{"AddressLine":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 33","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Исконная Финляндия","Locality":{"DependentLocality":{"DependentLocalityName":"Обо","DependentLocality":{"DependentLocalityName":"город Турку","Thoroughfare":{"ThoroughfareName":"Läntinen Pitkäkatu","Premise":{"PremiseNumber":"33"}}}}}}}}}},"description":"город Турку, Исконная Финляндия, Фінляндія","name":"Läntinen Pitkäkatu, 33","boundedBy":{"Envelope":{"lowerCorner":"22.253354 60.45207","upperCorner":"22.261564 60.456126"}},"Point":{"pos":"22.257459 60.454098"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 32","precision":"exact","Address":{"country_code":"FI","formatted":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 32","Components":[{"kind":"country","name":"Фінляндія"},{"kind":"province","name":"Исконная Финляндия"},{"kind":"district","name":"Обо"},{"kind":"locality","name":"город Турку"},{"kind":"street","name":"Ratapihankatu"},{"kind":"house","name":"32"}]},"AddressDetails":{"Country":{"AddressLine":"Фінляндія, Исконная Финляндия, город Турку, Ratapihankatu, 32","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Исконная Финляндия","Locality":{"DependentLocality":{"DependentLocalityName":"Обо","DependentLocality":{"DependentLocalityName":"город Турку","Thoroughfare":{"ThoroughfareName":"Ratapihankatu","Premise":{"PremiseNumber":"32"}}}}}}}}}},"description":"город Турку, Исконная Финляндия, Фінляндія","name":"Ratapihankatu, 32","boundedBy":{"Envelope":{"lowerCorner":"22.25374 60.452425","upperCorner":"22.261951 60.456481"}},"Point":{"pos":"22.257845 60.454453"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 31","precision":"exact","Address":{"country_code":"FI","formatted":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 31","Components":[{"kind":"country","name":"Фінляндія"},{"kind":"province","name":"Исконная Финляндия"},{"kind":"district","name":"Обо"},{"kind":"locality","name":"город Турку"},{"kind":"street","name":"Läntinen Pitkäkatu"},{"kind":"house","name":"31"}]},"AddressDetails":{"Country":{"AddressLine":"Фінляндія, Исконная Финляндия, город Турку, Läntinen Pitkäkatu, 31","CountryNameCode":"FI","CountryName":"Фінляндія","AdministrativeArea":{"AdministrativeAreaName":"Исконная Финляндия","Locality":{"DependentLocality":{"DependentLocalityName":"Обо","DependentLocality":{"DependentLocalityName":"город Турку","Thoroughfare":{"ThoroughfareName":"Läntinen Pitkäkatu","Premise":{"PremiseNumber":"31"}}}}}}}}}},"description":"город Турку, Исконная Финляндия, Фінляндія","name":"Läntinen Pitkäkatu, 31","boundedBy":{"Envelope":{"lowerCorner":"22.25374 60.452425","upperCorner":"22.261951 60.456481"}},"Point":{"pos":"22.257845 60.454453"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_a698241420fea1dfed919b3388263423f14a4787 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_a698241420fea1dfed919b3388263423f14a4787
index 4b2978433..133312316 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_a698241420fea1dfed919b3388263423f14a4787
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_a698241420fea1dfed919b3388263423f14a4787
@@ -1 +1 @@
-s:7164:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"30.324285,60.036843","found":"67","results":"5","boundedBy":{"Envelope":{"lowerCorner":"30.074284 59.785893","upperCorner":"30.574286 60.285894"}},"Point":{"pos":"30.324285 60.036843"},"kind":"metro"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Озерки","precision":"other","Address":{"country_code":"RU","formatted":"Санкт-Петербург, 2 линия, метро Озерки","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Озерки"}]},"AddressDetails":{"Country":{"AddressLine":"Санкт-Петербург, 2 линия, метро Озерки","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Озерки"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Озерки","boundedBy":{"Envelope":{"lowerCorner":"30.313649 60.033014","upperCorner":"30.330106 60.041247"}},"Point":{"pos":"30.321878 60.037131"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Проспект Просвещения","precision":"other","Address":{"country_code":"RU","formatted":"Санкт-Петербург, 2 линия, метро Проспект Просвещения","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Проспект Просвещения"}]},"AddressDetails":{"Country":{"AddressLine":"Санкт-Петербург, 2 линия, метро Проспект Просвещения","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Проспект Просвещения"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Проспект Просвещения","boundedBy":{"Envelope":{"lowerCorner":"30.324312 60.047295","upperCorner":"30.340769 60.055524"}},"Point":{"pos":"30.332541 60.05141"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Удельная","precision":"other","Address":{"country_code":"RU","formatted":"Санкт-Петербург, 2 линия, метро Удельная","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Удельная"}]},"AddressDetails":{"Country":{"AddressLine":"Санкт-Петербург, 2 линия, метро Удельная","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Удельная"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Удельная","boundedBy":{"Envelope":{"lowerCorner":"30.307657 60.012602","upperCorner":"30.324115 60.02084"}},"Point":{"pos":"30.315886 60.016721"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Парнас","precision":"other","Address":{"country_code":"RU","formatted":"Санкт-Петербург, 2 линия, метро Парнас","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Парнас"}]},"AddressDetails":{"Country":{"AddressLine":"Санкт-Петербург, 2 линия, метро Парнас","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Парнас"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Парнас","boundedBy":{"Envelope":{"lowerCorner":"30.325768 60.062858","upperCorner":"30.342225 60.071084"}},"Point":{"pos":"30.333996 60.066971"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 1 линия, метро Политехническая","precision":"other","Address":{"country_code":"RU","formatted":"Санкт-Петербург, 1 линия, метро Политехническая","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"1 линия"},{"kind":"metro","name":"метро Политехническая"}]},"AddressDetails":{"Country":{"AddressLine":"Санкт-Петербург, 1 линия, метро Политехническая","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"1 линия","Premise":{"PremiseName":"метро Политехническая"}}}}}}}},"description":"1 линия, Санкт-Петербург, Россия","name":"метро Политехническая","boundedBy":{"Envelope":{"lowerCorner":"30.362679 60.004694","upperCorner":"30.379136 60.012935"}},"Point":{"pos":"30.370908 60.008815"}}}]}}}";
\ No newline at end of file
+s:7300:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"30.324285,60.036843","found":"69","results":"5","boundedBy":{"Envelope":{"lowerCorner":"30.074284 59.785893","upperCorner":"30.574286 60.285894"}},"Point":{"pos":"30.324285 60.036843"},"kind":"metro"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Озерки","precision":"other","Address":{"country_code":"RU","formatted":"Россия, Санкт-Петербург, 2 линия, метро Озерки","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Озерки"}]},"AddressDetails":{"Country":{"AddressLine":"Россия, Санкт-Петербург, 2 линия, метро Озерки","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Озерки"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Озерки","boundedBy":{"Envelope":{"lowerCorner":"30.313649 60.033014","upperCorner":"30.330106 60.041247"}},"Point":{"pos":"30.321878 60.037131"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Проспект Просвещения","precision":"other","Address":{"country_code":"RU","formatted":"Россия, Санкт-Петербург, 2 линия, метро Проспект Просвещения","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Проспект Просвещения"}]},"AddressDetails":{"Country":{"AddressLine":"Россия, Санкт-Петербург, 2 линия, метро Проспект Просвещения","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Проспект Просвещения"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Проспект Просвещения","boundedBy":{"Envelope":{"lowerCorner":"30.324312 60.047295","upperCorner":"30.340769 60.055524"}},"Point":{"pos":"30.332541 60.05141"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Удельная","precision":"other","Address":{"country_code":"RU","formatted":"Россия, Санкт-Петербург, 2 линия, метро Удельная","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Удельная"}]},"AddressDetails":{"Country":{"AddressLine":"Россия, Санкт-Петербург, 2 линия, метро Удельная","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Удельная"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Удельная","boundedBy":{"Envelope":{"lowerCorner":"30.307666 60.012602","upperCorner":"30.324124 60.02084"}},"Point":{"pos":"30.315895 60.016721"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 2 линия, метро Парнас","precision":"other","Address":{"country_code":"RU","formatted":"Россия, Санкт-Петербург, 2 линия, метро Парнас","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"2 линия"},{"kind":"metro","name":"метро Парнас"}]},"AddressDetails":{"Country":{"AddressLine":"Россия, Санкт-Петербург, 2 линия, метро Парнас","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"2 линия","Premise":{"PremiseName":"метро Парнас"}}}}}}}},"description":"2 линия, Санкт-Петербург, Россия","name":"метро Парнас","boundedBy":{"Envelope":{"lowerCorner":"30.325768 60.062858","upperCorner":"30.342225 60.071084"}},"Point":{"pos":"30.333996 60.066971"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"metro","text":"Россия, Санкт-Петербург, 1 линия, метро Политехническая","precision":"other","Address":{"country_code":"RU","formatted":"Россия, Санкт-Петербург, 1 линия, метро Политехническая","Components":[{"kind":"country","name":"Россия"},{"kind":"province","name":"Северо-Западный федеральный округ"},{"kind":"province","name":"Санкт-Петербург"},{"kind":"locality","name":"Санкт-Петербург"},{"kind":"route","name":"1 линия"},{"kind":"metro","name":"метро Политехническая"}]},"AddressDetails":{"Country":{"AddressLine":"Россия, Санкт-Петербург, 1 линия, метро Политехническая","CountryNameCode":"RU","CountryName":"Россия","AdministrativeArea":{"AdministrativeAreaName":"Санкт-Петербург","Locality":{"LocalityName":"Санкт-Петербург","Thoroughfare":{"ThoroughfareName":"1 линия","Premise":{"PremiseName":"метро Политехническая"}}}}}}}},"description":"1 линия, Санкт-Петербург, Россия","name":"метро Политехническая","boundedBy":{"Envelope":{"lowerCorner":"30.36267 60.00469","upperCorner":"30.379127 60.01293"}},"Point":{"pos":"30.370899 60.00881"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b21e6fa348985653ed3827c87beccbe62378ae6b b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b21e6fa348985653ed3827c87beccbe62378ae6b
index 22b844aa9..e5ce4eefc 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b21e6fa348985653ed3827c87beccbe62378ae6b
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b21e6fa348985653ed3827c87beccbe62378ae6b
@@ -1 +1 @@
-s:3314:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"29.198184,40.900640","found":"8","results":"5","boundedBy":{"Envelope":{"lowerCorner":"28.948183 40.650166","upperCorner":"29.448185 41.150162"}},"Point":{"pos":"29.198184 40.900640"},"kind":"locality"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Türkiye, İstanbul, Adalar, Büyükada","precision":"other","AddressDetails":{"Country":{"AddressLine":"İstanbul, Adalar, Büyükada","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","SubAdministrativeArea":{"SubAdministrativeAreaName":"Adalar","Locality":{"LocalityName":"Büyükada"}}}}}}},"description":"Adalar, İstanbul, Türkiye","name":"Büyükada","boundedBy":{"Envelope":{"lowerCorner":"29.10723 40.853544","upperCorner":"29.139021 40.876111"}},"Point":{"pos":"29.129562 40.874652"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Türkiye, İstanbul, Sultanbeyli","precision":"other","AddressDetails":{"Country":{"AddressLine":"İstanbul, Sultanbeyli","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","SubAdministrativeArea":{"SubAdministrativeAreaName":"Sultanbeyli","Locality":{"LocalityName":"Sultanbeyli"}}}}}}},"description":"İstanbul, Türkiye","name":"Sultanbeyli","boundedBy":{"Envelope":{"lowerCorner":"29.244699 40.931768","upperCorner":"29.31192 41.00489"}},"Point":{"pos":"29.262001 40.968417"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Türkiye, Kocaeli, Çayırova, Tuzla","precision":"other","AddressDetails":{"Country":{"AddressLine":"Kocaeli, Çayırova, Tuzla","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"Kocaeli","SubAdministrativeArea":{"SubAdministrativeAreaName":"Çayırova","Locality":{"LocalityName":"Tuzla"}}}}}}},"description":"Çayırova, Kocaeli, Türkiye","name":"Tuzla","boundedBy":{"Envelope":{"lowerCorner":"29.350808 40.807747","upperCorner":"29.422673 40.849022"}},"Point":{"pos":"29.372233 40.824215"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Türkiye, Kocaeli, Darıca, Gebze","precision":"other","AddressDetails":{"Country":{"AddressLine":"Kocaeli, Darıca, Gebze","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"Kocaeli","SubAdministrativeArea":{"SubAdministrativeAreaName":"Darıca","Locality":{"LocalityName":"Gebze"}}}}}}},"description":"Darıca, Kocaeli, Türkiye","name":"Gebze","boundedBy":{"Envelope":{"lowerCorner":"29.333462 40.753098","upperCorner":"29.425512 40.809364"}},"Point":{"pos":"29.384333 40.762176"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Türkiye, İstanbul","precision":"other","AddressDetails":{"Country":{"AddressLine":"İstanbul","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","Locality":{"LocalityName":"İstanbul"}}}}}},"description":"Türkiye","name":"İstanbul","boundedBy":{"Envelope":{"lowerCorner":"28.595549 40.811398","upperCorner":"29.4288 41.199235"}},"Point":{"pos":"28.967111 41.008925"}}}]}}}";
\ No newline at end of file
+s:4922:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"29.198184,40.900640","found":"17","results":"5","boundedBy":{"Envelope":{"lowerCorner":"28.948183 40.650166","upperCorner":"29.448185 41.150162"}},"Point":{"pos":"29.198184 40.900640"},"kind":"locality"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"İstanbul, Türkiye","precision":"other","Address":{"country_code":"TR","formatted":"İstanbul, Türkiye","Components":[{"kind":"country","name":"Türkiye"},{"kind":"province","name":"İstanbul"},{"kind":"locality","name":"İstanbul"}]},"AddressDetails":{"Country":{"AddressLine":"İstanbul, Türkiye","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","Locality":{"LocalityName":"İstanbul"}}}}}},"description":"Türkiye","name":"İstanbul","boundedBy":{"Envelope":{"lowerCorner":"28.401361 40.795964","upperCorner":"29.420984 41.224206"}},"Point":{"pos":"28.978151 41.01117"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"İstanbul, Türkiye","precision":"other","Address":{"country_code":"TR","formatted":"İstanbul, Türkiye","Components":[{"kind":"country","name":"Türkiye"},{"kind":"province","name":"İstanbul"},{"kind":"locality","name":"İstanbul"}]},"AddressDetails":{"Country":{"AddressLine":"İstanbul, Türkiye","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","Locality":{"LocalityName":"İstanbul"}}}}}},"description":"Türkiye","name":"İstanbul","boundedBy":{"Envelope":{"lowerCorner":"28.401361 40.795964","upperCorner":"29.420984 41.224206"}},"Point":{"pos":"28.978151 41.01117"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Emirli Köyü, Pendik, İstanbul, Türkiye","precision":"other","Address":{"country_code":"TR","formatted":"Emirli Köyü, Pendik, İstanbul, Türkiye","Components":[{"kind":"country","name":"Türkiye"},{"kind":"province","name":"İstanbul"},{"kind":"area","name":"Pendik"},{"kind":"district","name":"Emirli Mah."},{"kind":"locality","name":"Emirli Köyü"}]},"AddressDetails":{"Country":{"AddressLine":"Emirli Köyü, Pendik, İstanbul, Türkiye","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","SubAdministrativeArea":{"SubAdministrativeAreaName":"Pendik","Locality":{"DependentLocality":{"DependentLocalityName":"Emirli Mah.","DependentLocality":{"DependentLocalityName":"Emirli Köyü"}}}}}}}}},"description":"Pendik, İstanbul, Türkiye","name":"Emirli Köyü","boundedBy":{"Envelope":{"lowerCorner":"29.307392 40.967682","upperCorner":"29.355371 41.002283"}},"Point":{"pos":"29.333309 40.983681"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Paşaköy Mah., Sancaktepe, İstanbul, Türkiye","precision":"other","Address":{"country_code":"TR","formatted":"Paşaköy Mah., Sancaktepe, İstanbul, Türkiye","Components":[{"kind":"country","name":"Türkiye"},{"kind":"province","name":"İstanbul"},{"kind":"area","name":"Sancaktepe"},{"kind":"district","name":"Paşaköy Mah."},{"kind":"locality","name":"Paşaköy Mah."}]},"AddressDetails":{"Country":{"AddressLine":"Paşaköy Mah., Sancaktepe, İstanbul, Türkiye","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","SubAdministrativeArea":{"SubAdministrativeAreaName":"Sancaktepe","Locality":{"DependentLocality":{"DependentLocalityName":"Paşaköy Mah.","DependentLocality":{"DependentLocalityName":"Paşaköy Mah."}}}}}}}}},"description":"Sancaktepe, İstanbul, Türkiye","name":"Paşaköy Mah.","boundedBy":{"Envelope":{"lowerCorner":"29.283758 41.007299","upperCorner":"29.303997 41.024683"}},"Point":{"pos":"29.294268 41.016185"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"Ballıca Mah., Pendik, İstanbul, Türkiye","precision":"other","Address":{"country_code":"TR","formatted":"Ballıca Mah., Pendik, İstanbul, Türkiye","Components":[{"kind":"country","name":"Türkiye"},{"kind":"province","name":"İstanbul"},{"kind":"area","name":"Pendik"},{"kind":"district","name":"Ballıca Mah."},{"kind":"locality","name":"Ballıca Mah."}]},"AddressDetails":{"Country":{"AddressLine":"Ballıca Mah., Pendik, İstanbul, Türkiye","CountryNameCode":"TR","CountryName":"Türkiye","AdministrativeArea":{"AdministrativeAreaName":"İstanbul","SubAdministrativeArea":{"SubAdministrativeAreaName":"Pendik","Locality":{"DependentLocality":{"DependentLocalityName":"Ballıca Mah.","DependentLocality":{"DependentLocalityName":"Ballıca Mah."}}}}}}}}},"description":"Pendik, İstanbul, Türkiye","name":"Ballıca Mah.","boundedBy":{"Envelope":{"lowerCorner":"29.379958 40.97046","upperCorner":"29.42818 41.007156"}},"Point":{"pos":"29.404707 40.987929"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b235c83d84237612621f3bda35f41e291d265c22 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b235c83d84237612621f3bda35f41e291d265c22
index 81f08a780..81635af11 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b235c83d84237612621f3bda35f41e291d265c22
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b235c83d84237612621f3bda35f41e291d265c22
@@ -1 +1 @@
-s:205:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"-77.036991,38.900206","found":"0","results":"5","Point":{"pos":"-77.036991 38.900206"}}},"featureMember":[]}}}";
\ No newline at end of file
+s:2565:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"-77.036991,38.900206","found":"3","results":"5","Point":{"pos":"-77.036991 38.900206"}}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"locality","text":"United States of America, District of Columbia, City of Washington","precision":"other","Address":{"country_code":"US","formatted":"United States of America, District of Columbia, City of Washington","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"District of Columbia"},{"kind":"locality","name":"City of Washington"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, District of Columbia, City of Washington","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia","Locality":{"LocalityName":"City of Washington"}}}}}},"description":"District of Columbia, United States of America","name":"City of Washington","boundedBy":{"Envelope":{"lowerCorner":"-77.119487 38.817096","upperCorner":"-76.909685 38.994895"}},"Point":{"pos":"-77.036527 38.899513"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"province","text":"United States of America, District of Columbia","precision":"other","Address":{"country_code":"US","formatted":"United States of America, District of Columbia","Components":[{"kind":"country","name":"United States of America"},{"kind":"province","name":"District of Columbia"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America, District of Columbia","CountryNameCode":"US","CountryName":"United States of America","AdministrativeArea":{"AdministrativeAreaName":"District of Columbia"}}}}},"description":"United States of America","name":"District of Columbia","boundedBy":{"Envelope":{"lowerCorner":"-77.119783 38.789999","upperCorner":"-76.909685 38.994895"}},"Point":{"pos":"-76.987291 38.892198"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"country","text":"United States of America","precision":"other","Address":{"country_code":"US","formatted":"United States of America","Components":[{"kind":"country","name":"United States of America"}]},"AddressDetails":{"Country":{"AddressLine":"United States of America","CountryNameCode":"US","CountryName":"United States of America"}}}},"name":"United States of America","boundedBy":{"Envelope":{"lowerCorner":"-193.546079 16.530514","upperCorner":"-66.910205 71.49891"}},"Point":{"pos":"-99.115868 36.952915"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b561a90296204f3883bf43c99f9458b058641a69 b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b561a90296204f3883bf43c99f9458b058641a69
index 7496fbb64..e142e3d28 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b561a90296204f3883bf43c99f9458b058641a69
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_b561a90296204f3883bf43c99f9458b058641a69
@@ -1 +1 @@
-s:943:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"ул.Ленина, 19, Минск 220030, Республика Беларусь","found":"1","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Беларусь, Минск, улица Ленина, 19","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Минск, улица Ленина, 19","CountryNameCode":"BY","CountryName":"Беларусь","AdministrativeArea":{"AdministrativeAreaName":"Минск","Locality":{"LocalityName":"Минск","Thoroughfare":{"ThoroughfareName":"улица Ленина","Premise":{"PremiseNumber":"19"}}}}}}}},"description":"Минск, Беларусь","name":"улица Ленина, 19","boundedBy":{"Envelope":{"lowerCorner":"27.555237 53.893349","upperCorner":"27.571695 53.903069"}},"Point":{"pos":"27.563466 53.898209"}}}]}}}";
\ No newline at end of file
+s:1297:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"ул.Ленина, 19, Минск 220030, Республика Беларусь","found":"1","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Беларусь, Мінск, вуліца Леніна, 19","precision":"exact","Address":{"country_code":"BY","formatted":"Беларусь, Мінск, вуліца Леніна, 19","Components":[{"kind":"country","name":"Беларусь"},{"kind":"province","name":"Мінск"},{"kind":"locality","name":"Мінск"},{"kind":"street","name":"вуліца Леніна"},{"kind":"house","name":"19"}]},"AddressDetails":{"Country":{"AddressLine":"Беларусь, Мінск, вуліца Леніна, 19","CountryNameCode":"BY","CountryName":"Беларусь","AdministrativeArea":{"AdministrativeAreaName":"Мінск","Locality":{"LocalityName":"Мінск","Thoroughfare":{"ThoroughfareName":"вуліца Леніна","Premise":{"PremiseNumber":"19"}}}}}}}},"description":"Мінск, Беларусь","name":"вуліца Леніна, 19","boundedBy":{"Envelope":{"lowerCorner":"27.559361 53.895785","upperCorner":"27.567571 53.900634"}},"Point":{"pos":"27.563466 53.898209"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_d08958bffb2ccfb2ba1f589665429312ee19f4dd b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_d08958bffb2ccfb2ba1f589665429312ee19f4dd
deleted file mode 100644
index 66dbf5a40..000000000
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_d08958bffb2ccfb2ba1f589665429312ee19f4dd
+++ /dev/null
@@ -1 +0,0 @@
-s:199:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"0.000000,0.000000","found":"0","results":"5","Point":{"pos":"0.000000 0.000000"}}},"featureMember":[]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_e4a5a17ff82fb94a41b081c6495045090de216eb b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_e4a5a17ff82fb94a41b081c6495045090de216eb
index e1c153734..8401eaf32 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_e4a5a17ff82fb94a41b081c6495045090de216eb
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_e4a5a17ff82fb94a41b081c6495045090de216eb
@@ -1 +1 @@
-s:4513:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"2.388772,48.863216","found":"46","results":"5","boundedBy":{"Envelope":{"lowerCorner":"2.386276 48.860723","upperCorner":"2.391270 48.865713"}},"Point":{"pos":"2.388772 48.863216"},"kind":"street"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Ile-de-France, Paris, 20e Arrondissement, Avenue Gambetta","precision":"street","AddressDetails":{"Country":{"AddressLine":"Ile-de-France, Paris, 20e Arrondissement, Avenue Gambetta","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Ile-de-France","SubAdministrativeArea":{"SubAdministrativeAreaName":"Paris","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta"}}}}}}}}},"description":"20e Arrondissement, Paris, Ile-de-France, France","name":"Avenue Gambetta","boundedBy":{"Envelope":{"lowerCorner":"2.387497 48.86294","upperCorner":"2.406587 48.877067"}},"Point":{"pos":"2.400370 48.867035"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Ile-de-France, Paris, 20e Arrondissement, Place Auguste Métivier","precision":"street","AddressDetails":{"Country":{"AddressLine":"Ile-de-France, Paris, 20e Arrondissement, Place Auguste Métivier","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Ile-de-France","SubAdministrativeArea":{"SubAdministrativeAreaName":"Paris","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Place Auguste Métivier"}}}}}}}}},"description":"20e Arrondissement, Paris, Ile-de-France, France","name":"Place Auguste Métivier","boundedBy":{"Envelope":{"lowerCorner":"2.387974 48.863029","upperCorner":"2.388468 48.863307"}},"Point":{"pos":"2.388207 48.863189"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Ile-de-France, Paris, 20e Arrondissement, Rue des Amandiers","precision":"street","AddressDetails":{"Country":{"AddressLine":"Ile-de-France, Paris, 20e Arrondissement, Rue des Amandiers","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Ile-de-France","SubAdministrativeArea":{"SubAdministrativeAreaName":"Paris","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Rue des Amandiers"}}}}}}}}},"description":"20e Arrondissement, Paris, Ile-de-France, France","name":"Rue des Amandiers","boundedBy":{"Envelope":{"lowerCorner":"2.387974 48.863029","upperCorner":"2.389815 48.868309"}},"Point":{"pos":"2.389689 48.865927"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Ile-de-France, Paris, 20e Arrondissement, Rue Houdart","precision":"street","AddressDetails":{"Country":{"AddressLine":"Ile-de-France, Paris, 20e Arrondissement, Rue Houdart","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Ile-de-France","SubAdministrativeArea":{"SubAdministrativeAreaName":"Paris","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Rue Houdart"}}}}}}}}},"description":"20e Arrondissement, Paris, Ile-de-France, France","name":"Rue Houdart","boundedBy":{"Envelope":{"lowerCorner":"2.387273 48.863224","upperCorner":"2.388252 48.864599"}},"Point":{"pos":"2.387767 48.863912"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Ile-de-France, Paris, 20e Arrondissement, Square Jacaques Grynberg","precision":"street","AddressDetails":{"Country":{"AddressLine":"Ile-de-France, Paris, 20e Arrondissement, Square Jacaques Grynberg","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Ile-de-France","SubAdministrativeArea":{"SubAdministrativeAreaName":"Paris","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Square Jacaques Grynberg"}}}}}}}}},"description":"20e Arrondissement, Paris, Ile-de-France, France","name":"Square Jacaques Grynberg","boundedBy":{"Envelope":{"lowerCorner":"2.388899 48.863704","upperCorner":"2.389959 48.863929"}},"Point":{"pos":"2.389438 48.863758"}}}]}}}";
\ No newline at end of file
+s:5931:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"2.388772,48.863216","found":"34","results":"5","boundedBy":{"Envelope":{"lowerCorner":"2.386276 48.860723","upperCorner":"2.391270 48.865713"}},"Point":{"pos":"2.388772 48.863216"},"kind":"street"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Île-de-France, Paris, 20e Arrondissement, Avenue Gambetta","precision":"street","Address":{"country_code":"FR","formatted":"France, Île-de-France, Paris, 20e Arrondissement, Avenue Gambetta","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Île-de-France"},{"kind":"locality","name":"Paris"},{"kind":"district","name":"20e Arrondissement"},{"kind":"street","name":"Avenue Gambetta"}]},"AddressDetails":{"Country":{"AddressLine":"France, Île-de-France, Paris, 20e Arrondissement, Avenue Gambetta","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Île-de-France","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta"}}}}}}}},"description":"20e Arrondissement, Paris, Île-de-France, France","name":"Avenue Gambetta","boundedBy":{"Envelope":{"lowerCorner":"2.38889 48.86323","upperCorner":"2.406003 48.875722"}},"Point":{"pos":"2.40028 48.866958"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Île-de-France, Paris, 20e Arrondissement, Rue des Amandiers","precision":"street","Address":{"country_code":"FR","formatted":"France, Île-de-France, Paris, 20e Arrondissement, Rue des Amandiers","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Île-de-France"},{"kind":"locality","name":"Paris"},{"kind":"district","name":"20e Arrondissement"},{"kind":"street","name":"Rue des Amandiers"}]},"AddressDetails":{"Country":{"AddressLine":"France, Île-de-France, Paris, 20e Arrondissement, Rue des Amandiers","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Île-de-France","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Rue des Amandiers"}}}}}}}},"description":"20e Arrondissement, Paris, Île-de-France, France","name":"Rue des Amandiers","boundedBy":{"Envelope":{"lowerCorner":"2.387992 48.863076","upperCorner":"2.389869 48.868309"}},"Point":{"pos":"2.389734 48.865873"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Île-de-France, Paris, 20e Arrondissement, Rue Houdart","precision":"street","Address":{"country_code":"FR","formatted":"France, Île-de-France, Paris, 20e Arrondissement, Rue Houdart","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Île-de-France"},{"kind":"locality","name":"Paris"},{"kind":"district","name":"20e Arrondissement"},{"kind":"street","name":"Rue Houdart"}]},"AddressDetails":{"Country":{"AddressLine":"France, Île-de-France, Paris, 20e Arrondissement, Rue Houdart","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Île-de-France","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Rue Houdart"}}}}}}}},"description":"20e Arrondissement, Paris, Île-de-France, France","name":"Rue Houdart","boundedBy":{"Envelope":{"lowerCorner":"2.387327 48.86326","upperCorner":"2.388288 48.864599"}},"Point":{"pos":"2.387794 48.863923"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Île-de-France, Paris, 20e Arrondissement, Place Auguste Métivier","precision":"street","Address":{"country_code":"FR","formatted":"France, Île-de-France, Paris, 20e Arrondissement, Place Auguste Métivier","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Île-de-France"},{"kind":"locality","name":"Paris"},{"kind":"district","name":"20e Arrondissement"},{"kind":"street","name":"Place Auguste Métivier"}]},"AddressDetails":{"Country":{"AddressLine":"France, Île-de-France, Paris, 20e Arrondissement, Place Auguste Métivier","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Île-de-France","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Place Auguste Métivier"}}}}}}}},"description":"20e Arrondissement, Paris, Île-de-France, France","name":"Place Auguste Métivier","boundedBy":{"Envelope":{"lowerCorner":"2.387947 48.863159","upperCorner":"2.388135 48.863224"}},"Point":{"pos":"2.388045 48.863189"}}},{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"street","text":"France, Île-de-France, Paris, 20e Arrondissement, Rue Jacques Prévert","precision":"street","Address":{"country_code":"FR","formatted":"France, Île-de-France, Paris, 20e Arrondissement, Rue Jacques Prévert","Components":[{"kind":"country","name":"France"},{"kind":"province","name":"Île-de-France"},{"kind":"locality","name":"Paris"},{"kind":"district","name":"20e Arrondissement"},{"kind":"street","name":"Rue Jacques Prévert"}]},"AddressDetails":{"Country":{"AddressLine":"France, Île-de-France, Paris, 20e Arrondissement, Rue Jacques Prévert","CountryNameCode":"FR","CountryName":"France","AdministrativeArea":{"AdministrativeAreaName":"Île-de-France","Locality":{"LocalityName":"Paris","DependentLocality":{"DependentLocalityName":"20e Arrondissement","Thoroughfare":{"ThoroughfareName":"Rue Jacques Prévert"}}}}}}}},"description":"20e Arrondissement, Paris, Île-de-France, France","name":"Rue Jacques Prévert","boundedBy":{"Envelope":{"lowerCorner":"2.388036 48.863835","upperCorner":"2.389105 48.864848"}},"Point":{"pos":"2.388468 48.864054"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_fe183030953fd61ac7624981101f2ddee3a74fce b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_fe183030953fd61ac7624981101f2ddee3a74fce
index 0e973c0b2..3b92e1695 100644
--- a/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_fe183030953fd61ac7624981101f2ddee3a74fce
+++ b/src/Provider/Yandex/Tests/.cached_responses/geocode-maps.yandex.ru_fe183030953fd61ac7624981101f2ddee3a74fce
@@ -1 +1 @@
-s:1114:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"10 avenue Gambetta, Paris, France","found":"1","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Франция, Иль-Де-Франс, Париж, XX округ, Avenue Gambetta, 10","precision":"exact","AddressDetails":{"Country":{"AddressLine":"Иль-Де-Франс, Париж, XX округ, Avenue Gambetta, 10","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-Де-Франс","SubAdministrativeArea":{"SubAdministrativeAreaName":"Париж","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta","Premise":{"PremiseNumber":"10"}}}}}}}}}},"description":"XX округ, Париж, Иль-Де-Франс, Франция","name":"Avenue Gambetta, 10","boundedBy":{"Envelope":{"lowerCorner":"2.380841 48.857747","upperCorner":"2.397298 48.868605"}},"Point":{"pos":"2.389069 48.863177"}}}]}}}";
\ No newline at end of file
+s:1537:"{"response":{"GeoObjectCollection":{"metaDataProperty":{"GeocoderResponseMetaData":{"request":"10 avenue Gambetta, Paris, France","found":"1","results":"5"}},"featureMember":[{"GeoObject":{"metaDataProperty":{"GeocoderMetaData":{"kind":"house","text":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 10","precision":"exact","Address":{"country_code":"FR","formatted":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 10","Components":[{"kind":"country","name":"Франция"},{"kind":"province","name":"Иль-де-Франс"},{"kind":"locality","name":"Париж"},{"kind":"district","name":"XX округ Парижа"},{"kind":"street","name":"Avenue Gambetta"},{"kind":"house","name":"10"}]},"AddressDetails":{"Country":{"AddressLine":"Франция, Иль-де-Франс, Париж, XX округ Парижа, Avenue Gambetta, 10","CountryNameCode":"FR","CountryName":"Франция","AdministrativeArea":{"AdministrativeAreaName":"Иль-де-Франс","Locality":{"LocalityName":"Париж","DependentLocality":{"DependentLocalityName":"XX округ Парижа","Thoroughfare":{"ThoroughfareName":"Avenue Gambetta","Premise":{"PremiseNumber":"10"}}}}}}}}},"description":"XX округ Парижа, Париж, Иль-де-Франс, Франция","name":"Avenue Gambetta, 10","boundedBy":{"Envelope":{"lowerCorner":"2.384794 48.860391","upperCorner":"2.393004 48.865808"}},"Point":{"pos":"2.388899 48.8631"}}}]}}}";
\ No newline at end of file
diff --git a/src/Provider/Yandex/Tests/IntegrationTest.php b/src/Provider/Yandex/Tests/IntegrationTest.php
index 2047d110d..80f69c850 100644
--- a/src/Provider/Yandex/Tests/IntegrationTest.php
+++ b/src/Provider/Yandex/Tests/IntegrationTest.php
@@ -14,34 +14,38 @@
use Geocoder\IntegrationTest\ProviderIntegrationTest;
use Geocoder\Provider\Yandex\Yandex;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Tobias Nyholm
*/
class IntegrationTest extends ProviderIntegrationTest
{
- protected $skippedTests = [
+ protected array $skippedTests = [
'testGeocodeQuery' => 'Wrong cords',
+ 'testReverseQueryWithNoResults' => 'Has Result',
];
- protected $testAddress = true;
- protected $testReverse = true;
- protected $testIpv4 = false;
- protected $testIpv6 = false;
+ protected bool $testAddress = true;
- protected function createProvider(HttpClient $httpClient)
+ protected bool $testReverse = true;
+
+ protected bool $testIpv4 = false;
+
+ protected bool $testIpv6 = false;
+
+ protected function createProvider(ClientInterface $httpClient)
{
return new Yandex($httpClient);
}
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- protected function getApiKey()
+ protected function getApiKey(): string
{
- return null;
+ return '';
}
}
diff --git a/src/Provider/Yandex/Tests/YandexTest.php b/src/Provider/Yandex/Tests/YandexTest.php
index ee681d4f5..8c30784a2 100644
--- a/src/Provider/Yandex/Tests/YandexTest.php
+++ b/src/Provider/Yandex/Tests/YandexTest.php
@@ -14,49 +14,46 @@
use Geocoder\Collection;
use Geocoder\IntegrationTest\BaseTestCase;
-use Geocoder\Location;
use Geocoder\Provider\Yandex\Model\YandexAddress;
+use Geocoder\Provider\Yandex\Yandex;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Provider\Yandex\Yandex;
/**
* @author Antoine Corcy
*/
class YandexTest extends BaseTestCase
{
- protected function getCacheDir()
+ protected function getCacheDir(): string
{
return __DIR__.'/.cached_responses';
}
- public function testGetName()
+ public function testGetName(): void
{
$provider = new Yandex($this->getMockedHttpClient());
$this->assertEquals('yandex', $provider->getName());
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The Yandex provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv4()
+ public function testGeocodeWithLocalhostIPv4(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Yandex provider does not support IP addresses, only street addresses.');
+
$provider = new Yandex($this->getMockedHttpClient());
$provider->geocodeQuery(GeocodeQuery::create('127.0.0.1'));
}
- /**
- * @expectedException \Geocoder\Exception\UnsupportedOperation
- * @expectedExceptionMessage The Yandex provider does not support IP addresses, only street addresses.
- */
- public function testGeocodeWithLocalhostIPv6()
+ public function testGeocodeWithLocalhostIPv6(): void
{
+ $this->expectException(\Geocoder\Exception\UnsupportedOperation::class);
+ $this->expectExceptionMessage('The Yandex provider does not support IP addresses, only street addresses.');
+
$provider = new Yandex($this->getMockedHttpClient());
$provider->geocodeQuery(GeocodeQuery::create('::1'));
}
- public function testGeocodeWithEmpty()
+ public function testGeocodeWithEmpty(): void
{
$provider = new Yandex($this->getMockedHttpClient('{"error":{"status":"400","message":"missing geocode parameter"}}'));
$result = $provider->geocodeQuery(GeocodeQuery::create('xx'));
@@ -65,7 +62,7 @@ public function testGeocodeWithEmpty()
$this->assertEquals(0, $result->count());
}
- public function testGeocodeWithFakeAddress()
+ public function testGeocodeWithFakeAddress(): void
{
$provider = new Yandex($this->getHttpClient());
$result = $provider->geocodeQuery(GeocodeQuery::create('foobar'));
@@ -74,59 +71,59 @@ public function testGeocodeWithFakeAddress()
$this->assertEquals(0, $result->count());
}
- public function testGeocodeWithRealAddress()
+ public function testGeocodeWithRealAddress(): void
{
$provider = new Yandex($this->getHttpClient());
$results = $provider->geocodeQuery(GeocodeQuery::create('10 avenue Gambetta, Paris, France'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.863277, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.389016, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.863277, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.389016, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(48.861926, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(2.386967, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(48.864629, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(2.391064, $result->getBounds()->getEast(), '', 0.01);
+ $this->assertEqualsWithDelta(48.861926, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(2.386967, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(48.864629, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(2.391064, $result->getBounds()->getEast(), 0.01);
$this->assertEquals(10, $result->getStreetNumber());
$this->assertEquals('Avenue Gambetta', $result->getStreetName());
$this->assertEquals('Париж', $result->getLocality());
- $this->assertEquals('XX округ', $result->getSubLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('Париж', $result->getAdminLevels()->get(2)->getName());
- $this->assertEquals('Иль-Де-Франс', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('XX округ Парижа', $result->getSubLocality());
+ $this->assertCount(1, $result->getAdminLevels());
+ $this->assertEquals('Иль-де-Франс', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Франция', $result->getCountry()->getName());
$this->assertEquals('FR', $result->getCountry()->getCode());
+ $this->assertEquals('exact', $result->getPrecision());
+ $this->assertEquals('house', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
- $this->assertNull($result->getAdminLevels()->get(2)->getCode());
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
}
- public function testGeocodeWithRealAddressWithUALocale()
+ public function testGeocodeWithRealAddressWithUALocale(): void
{
$provider = new Yandex($this->getHttpClient());
$results = $provider->geocodeQuery(GeocodeQuery::create('Copenhagen, Denmark')->withLocale('uk-UA'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
- $this->assertCount(5, $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
+ $this->assertCount(3, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(55.675676, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(12.567593, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(55.675676, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(12.585828, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(55.614999, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(12.45295, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(55.73259, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(12.65075, $result->getBounds()->getEast(), '', 0.01);
+ $this->assertEqualsWithDelta(55.614999, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(12.45295, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(55.73259, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(12.65075, $result->getBounds()->getEast(), 0.01);
$this->assertNull($result->getStreetNumber());
$this->assertNull($result->getStreetName());
$this->assertEquals('Копенгаген', $result->getLocality());
@@ -134,6 +131,9 @@ public function testGeocodeWithRealAddressWithUALocale()
$this->assertEquals('Столичная область', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Данія', $result->getCountry()->getName());
$this->assertEquals('DK', $result->getCountry()->getCode());
+ $this->assertEquals('other', $result->getPrecision());
+ $this->assertEquals('Копенгаген', $result->getName());
+ $this->assertEquals('locality', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
@@ -141,57 +141,46 @@ public function testGeocodeWithRealAddressWithUALocale()
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(1);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(55.455739, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(9.972854, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(55.716853, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(12.463837, $result->getCoordinates()->getLongitude(), 0.01);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(2);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(55.713258, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(12.534930, $result->getCoordinates()->getLongitude(), '', 0.01);
-
- /** @var Location $result */
- $result = $results->get(3);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(55.698878, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(12.578211, $result->getCoordinates()->getLongitude(), '', 0.01);
-
- /** @var Location $result */
- $result = $results->get(4);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(55.690380, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(12.554827, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(55.590338, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(12.130041, $result->getCoordinates()->getLongitude(), 0.01);
}
- public function testGeocodeWithRealAddressWithUSLocale()
+ public function testGeocodeWithRealAddressWithUSLocale(): void
{
$provider = new Yandex($this->getHttpClient());
$results = $provider->geocodeQuery(GeocodeQuery::create('1600 Pennsylvania Ave, Washington')->withLocale('en-US'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(38.897695, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(-77.038692, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(38.897695, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(-77.038692, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(38.891265, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(-77.046921, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(38.904125, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(-77.030464, $result->getBounds()->getEast(), '', 0.01);
- $this->assertEquals(1600, $result->getStreetNumber());
- $this->assertEquals('Pennsylvania Ave NW', $result->getStreetName());
- $this->assertEquals('Washington', $result->getLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('District of Columbia', $result->getAdminLevels()->get(2)->getName());
+ $this->assertEqualsWithDelta(38.891265, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(-77.058105, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(38.904125, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(-77.012426, $result->getBounds()->getEast(), 0.01);
+ $this->assertNull($result->getStreetNumber());
+ $this->assertEquals('Pennsylvania Avenue Northwest', $result->getStreetName());
+ $this->assertEquals('City of Washington', $result->getLocality());
+ $this->assertCount(1, $result->getAdminLevels());
$this->assertEquals('District of Columbia', $result->getAdminLevels()->get(1)->getName());
- $this->assertEquals('United States', $result->getCountry()->getName());
+ $this->assertEquals('United States of America', $result->getCountry()->getName());
$this->assertEquals('US', $result->getCountry()->getCode());
+ $this->assertEquals('street', $result->getPrecision());
+ $this->assertEquals('street', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
@@ -200,31 +189,33 @@ public function testGeocodeWithRealAddressWithUSLocale()
$this->assertNull($result->getTimezone());
}
- public function testGeocodeWithRealAddressWithBYLocale()
+ public function testGeocodeWithRealAddressWithBYLocale(): void
{
$provider = new Yandex($this->getHttpClient());
$results = $provider->geocodeQuery(GeocodeQuery::create('ул.Ленина, 19, Минск 220030, Республика Беларусь')->withLocale('be-BY'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(1, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(53.898077, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(27.563673, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(53.898077, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(27.563673, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(53.896867, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(27.561624, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(53.899286, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(27.565721, $result->getBounds()->getEast(), '', 0.01);
+ $this->assertEqualsWithDelta(53.896867, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(27.561624, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(53.899286, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(27.565721, $result->getBounds()->getEast(), 0.01);
$this->assertEquals(19, $result->getStreetNumber());
- $this->assertEquals('улица Ленина', $result->getStreetName());
- $this->assertEquals('Минск', $result->getLocality());
+ $this->assertEquals('вуліца Леніна', $result->getStreetName());
+ $this->assertEquals('Мінск', $result->getLocality());
$this->assertCount(1, $result->getAdminLevels());
- $this->assertEquals('Минск', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('Мінск', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Беларусь', $result->getCountry()->getName());
$this->assertEquals('BY', $result->getCountry()->getCode());
+ $this->assertEquals('exact', $result->getPrecision());
+ $this->assertEquals('house', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
@@ -232,189 +223,195 @@ public function testGeocodeWithRealAddressWithBYLocale()
$this->assertNull($result->getTimezone());
}
- public function testReverseWithRealCoordinates()
+ public function testReverseWithRealCoordinates(): void
{
$provider = new Yandex($this->getHttpClient());
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.863216489553, 2.388771995902061));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.863212, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.388773, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.863212, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.388773, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(48.86294, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(2.387497, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(48.877038, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(2.406587, $result->getBounds()->getEast(), '', 0.01);
- $this->assertNull($result->getStreetNumber());
+ $this->assertEqualsWithDelta(48.86294, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(2.387497, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(48.866063, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(2.392833, $result->getBounds()->getEast(), 0.01);
+ $this->assertEquals(1, $result->getStreetNumber());
$this->assertEquals('Avenue Gambetta', $result->getStreetName());
$this->assertEquals('Париж', $result->getLocality());
- $this->assertEquals('XX округ', $result->getSubLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('Париж', $result->getAdminLevels()->get(2)->getName());
- $this->assertEquals('Иль-Де-Франс', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEquals('XX округ Парижа', $result->getSubLocality());
+ $this->assertCount(1, $result->getAdminLevels());
+ $this->assertEquals('Иль-де-Франс', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Франция', $result->getCountry()->getName());
$this->assertEquals('FR', $result->getCountry()->getCode());
+ $this->assertEquals('exact', $result->getPrecision());
+ $this->assertEquals('Avenue Gambetta, 1', $result->getName());
+ $this->assertEquals('house', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
- $this->assertNull($result->getAdminLevels()->get(2)->getCode());
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(1);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.864848, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.3993549, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.864848, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.3993549, $result->getCoordinates()->getLongitude(), 0.01);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(2);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.856929, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.341197, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.856929, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.392115, $result->getCoordinates()->getLongitude(), 0.01);
}
- public function testReverseWithRealCoordinatesWithUSLocaleAndStreeToponym()
+ public function testReverseWithRealCoordinatesWithUSLocaleAndStreeToponym(): void
{
$provider = new Yandex($this->getHttpClient(), 'street');
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.863216489553, 2.388771995902061)->withLocale('en-US'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.87132, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.404017, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.87132, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.404017, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(48.86294, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(2.387497, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(48.877038, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(2.406587, $result->getBounds()->getEast(), '', 0.01);
+ $this->assertEqualsWithDelta(48.86294, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(2.387497, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(48.877038, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(2.406587, $result->getBounds()->getEast(), 0.01);
$this->assertNull($result->getStreetNumber());
$this->assertEquals('Avenue Gambetta', $result->getStreetName());
$this->assertEquals('20e Arrondissement', $result->getSubLocality());
$this->assertEquals('Paris', $result->getLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('Paris', $result->getAdminLevels()->get(2)->getName());
- $this->assertEquals('Ile-de-France', $result->getAdminLevels()->get(1)->getName());
+ $this->assertCount(1, $result->getAdminLevels());
+ $this->assertEquals('Île-de-France', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('France', $result->getCountry()->getName());
$this->assertEquals('FR', $result->getCountry()->getCode());
+ $this->assertEquals('street', $result->getPrecision());
+ $this->assertEquals('Avenue Gambetta', $result->getName());
+ $this->assertEquals('street', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
- $this->assertNull($result->getAdminLevels()->get(2)->getCode());
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(1);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.863230, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.388261, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.863230, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.388261, $result->getCoordinates()->getLongitude(), 0.01);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(2);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.866022, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.389662, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.866022, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.389662, $result->getCoordinates()->getLongitude(), 0.01);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(3);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.863918, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.387767, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.863918, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.387767, $result->getCoordinates()->getLongitude(), 0.01);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->get(4);
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(48.863787, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(2.389600, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(48.863787, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(2.389600, $result->getCoordinates()->getLongitude(), 0.01);
}
- public function testReverseWithRealCoordinatesWithUALocaleAndHouseToponym()
+ public function testReverseWithRealCoordinatesWithUALocaleAndHouseToponym(): void
{
$provider = new Yandex($this->getHttpClient(), 'house');
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(60.4539471768582, 22.2567842183875)->withLocale('uk-UA'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(60.454462, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(22.256561, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(60.454462, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(22.256561, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(60.45345, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(22.254513, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(60.455474, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(22.258609, $result->getBounds()->getEast(), '', 0.01);
- $this->assertEquals(36, $result->getStreetNumber());
- $this->assertEquals('Bangårdsgatan', $result->getStreetName());
- $this->assertEquals('Турку', $result->getLocality());
- $this->assertEquals('Кескуста', $result->getSubLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('Исконная Финляндия', $result->getAdminLevels()->get(2)->getName());
- $this->assertEquals('Юго-Западная Финляндия', $result->getAdminLevels()->get(1)->getName());
+ $this->assertEqualsWithDelta(60.45345, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(22.254513, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(60.455474, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(22.258609, $result->getBounds()->getEast(), 0.01);
+ $this->assertEquals(35, $result->getStreetNumber());
+ $this->assertEquals('Läntinen Pitkäkatu', $result->getStreetName());
+ $this->assertNull($result->getLocality());
+ $this->assertEquals('город Турку', $result->getSubLocality());
+ $this->assertCount(1, $result->getAdminLevels());
+ $this->assertEquals('Исконная Финляндия', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Фінляндія', $result->getCountry()->getName());
$this->assertEquals('FI', $result->getCountry()->getCode());
+ $this->assertEquals('exact', $result->getPrecision());
+ $this->assertEquals('house', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
- $this->assertNull($result->getAdminLevels()->get(2)->getCode());
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
}
- public function testReverseWithRealCoordinatesWithTRLocaleAndLocalityToponym()
+ public function testReverseWithRealCoordinatesWithTRLocaleAndLocalityToponym(): void
{
$provider = new Yandex($this->getHttpClient(), 'locality');
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(40.900640, 29.198184)->withLocale('tr-TR'));
- $this->assertInstanceOf('Geocoder\Model\AddressCollection', $results);
+ $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results);
$this->assertCount(5, $results);
- /** @var Location $result */
+ /** @var YandexAddress $result */
$result = $results->first();
- $this->assertInstanceOf('Geocoder\Model\Address', $result);
- $this->assertEquals(40.874651, $result->getCoordinates()->getLatitude(), '', 0.01);
- $this->assertEquals(29.129562, $result->getCoordinates()->getLongitude(), '', 0.01);
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEqualsWithDelta(41.01117, $result->getCoordinates()->getLatitude(), 0.01);
+ $this->assertEqualsWithDelta(28.978151, $result->getCoordinates()->getLongitude(), 0.01);
$this->assertNotNull($result->getBounds());
- $this->assertEquals(40.860413, $result->getBounds()->getSouth(), '', 0.01);
- $this->assertEquals(29.107230, $result->getBounds()->getWest(), '', 0.01);
- $this->assertEquals(40.876111, $result->getBounds()->getNorth(), '', 0.01);
- $this->assertEquals(29.139021, $result->getBounds()->getEast(), '', 0.01);
+ $this->assertEqualsWithDelta(40.795964, $result->getBounds()->getSouth(), 0.01);
+ $this->assertEqualsWithDelta(28.401361, $result->getBounds()->getWest(), 0.01);
+ $this->assertEqualsWithDelta(41.224206, $result->getBounds()->getNorth(), 0.01);
+ $this->assertEqualsWithDelta(29.420984, $result->getBounds()->getEast(), 0.01);
$this->assertNull($result->getStreetName());
$this->assertNull($result->getStreetNumber());
- $this->assertEquals('Büyükada', $result->getLocality());
- $this->assertCount(2, $result->getAdminLevels());
- $this->assertEquals('Adalar', $result->getAdminLevels()->get(2)->getName());
+ $this->assertEquals('İstanbul', $result->getLocality());
+ $this->assertCount(1, $result->getAdminLevels());
$this->assertEquals('İstanbul', $result->getAdminLevels()->get(1)->getName());
$this->assertEquals('Türkiye', $result->getCountry()->getName());
$this->assertEquals('TR', $result->getCountry()->getCode());
+ $this->assertEquals('other', $result->getPrecision());
+ $this->assertEquals('İstanbul', $result->getName());
+ $this->assertEquals('locality', $result->getKind());
// not provided
$this->assertNull($result->getPostalCode());
$this->assertNull($result->getSubLocality());
- $this->assertNull($result->getAdminLevels()->get(2)->getCode());
$this->assertNull($result->getAdminLevels()->get(1)->getCode());
$this->assertNull($result->getTimezone());
}
- public function testReverseMetroStationToGetName()
+ public function testReverseMetroStationToGetName(): void
{
$provider = new Yandex($this->getHttpClient(), 'metro');
$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(60.036843, 30.324285));
- /** @var YandexAddress $first */
- $first = $results->first();
- $this->assertEquals('метро Озерки', $first->getName());
+ /** @var YandexAddress $result */
+ $result = $results->first();
+ $this->assertInstanceOf(YandexAddress::class, $result);
+ $this->assertEquals('other', $result->getPrecision());
+ $this->assertEquals('метро Озерки', $result->getName());
+ $this->assertEquals('metro', $result->getKind());
}
}
diff --git a/src/Provider/Yandex/Yandex.php b/src/Provider/Yandex/Yandex.php
index d958769fb..7ca22d3f3 100644
--- a/src/Provider/Yandex/Yandex.php
+++ b/src/Provider/Yandex/Yandex.php
@@ -14,14 +14,14 @@
use Geocoder\Collection;
use Geocoder\Exception\UnsupportedOperation;
-use Geocoder\Model\AddressCollection;
+use Geocoder\Http\Provider\AbstractHttpProvider;
use Geocoder\Model\AddressBuilder;
+use Geocoder\Model\AddressCollection;
+use Geocoder\Provider\Provider;
use Geocoder\Provider\Yandex\Model\YandexAddress;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
-use Geocoder\Http\Provider\AbstractHttpProvider;
-use Geocoder\Provider\Provider;
-use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
/**
* @author Antoine Corcy
@@ -31,12 +31,12 @@ final class Yandex extends AbstractHttpProvider implements Provider
/**
* @var string
*/
- const GEOCODE_ENDPOINT_URL = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=%s';
+ public const GEOCODE_ENDPOINT_URL = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=%s';
/**
* @var string
*/
- const REVERSE_ENDPOINT_URL = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=%F,%F';
+ public const REVERSE_ENDPOINT_URL = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=%F,%F';
/**
* @var string
@@ -44,22 +44,27 @@ final class Yandex extends AbstractHttpProvider implements Provider
private $toponym;
/**
- * @param HttpClient $client an HTTP adapter
- * @param string $toponym toponym biasing only for reverse geocoding (optional)
+ * @var string|null
*/
- public function __construct(HttpClient $client, string $toponym = null)
+ private $apiKey;
+
+ /**
+ * @param ClientInterface $client an HTTP adapter
+ * @param string $toponym toponym biasing only for reverse geocoding (optional)
+ * @param string|null $apiKey API Key
+ */
+ public function __construct(ClientInterface $client, ?string $toponym = null, ?string $apiKey = null)
{
parent::__construct($client);
$this->toponym = $toponym;
+ $this->apiKey = $apiKey;
}
- /**
- * {@inheritdoc}
- */
public function geocodeQuery(GeocodeQuery $query): Collection
{
$address = $query->getText();
+
// This API doesn't handle IPs
if (filter_var($address, FILTER_VALIDATE_IP)) {
throw new UnsupportedOperation('The Yandex provider does not support IP addresses, only street addresses.');
@@ -67,12 +72,9 @@ public function geocodeQuery(GeocodeQuery $query): Collection
$url = sprintf(self::GEOCODE_ENDPOINT_URL, urlencode($address));
- return $this->executeQuery($url, $query->getLocale(), $query->getLimit());
+ return $this->executeQuery($url, $query->getLimit(), $query->getLocale());
}
- /**
- * {@inheritdoc}
- */
public function reverseQuery(ReverseQuery $query): Collection
{
$coordinates = $query->getCoordinates();
@@ -84,36 +86,30 @@ public function reverseQuery(ReverseQuery $query): Collection
$url = sprintf('%s&kind=%s', $url, $toponym);
}
- return $this->executeQuery($url, $query->getLocale(), $query->getLimit());
+ return $this->executeQuery($url, $query->getLimit(), $query->getLocale());
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'yandex';
}
- /**
- * @param string $url
- * @param string|null $locale
- * @param int $limit
- *
- * @return AddressCollection
- */
- private function executeQuery(string $url, string $locale = null, int $limit): AddressCollection
+ private function executeQuery(string $url, int $limit, ?string $locale = null): AddressCollection
{
if (null !== $locale) {
$url = sprintf('%s&lang=%s', $url, str_replace('_', '-', $locale));
}
+ if (null !== $this->apiKey) {
+ $url = sprintf('%s&apikey=%s', $url, $this->apiKey);
+ }
+
$url = sprintf('%s&results=%d', $url, $limit);
$content = $this->getUrlContents($url);
$json = json_decode($content, true);
- if (empty($json) || isset($json['error']) ||
- (isset($json['response']) && '0' === $json['response']['GeoObjectCollection']['metaDataProperty']['GeocoderResponseMetaData']['found'])
+ if (empty($json) || isset($json['error'])
+ || (isset($json['response']) && '0' === $json['response']['GeoObjectCollection']['metaDataProperty']['GeocoderResponseMetaData']['found'])
) {
return new AddressCollection([]);
}
@@ -168,6 +164,7 @@ function ($value, $key) use (&$flatArray) {
$location = $builder->build(YandexAddress::class);
$location = $location->withPrecision(isset($flatArray['precision']) ? $flatArray['precision'] : null);
$location = $location->withName(isset($flatArray['name']) ? $flatArray['name'] : null);
+ $location = $location->withKind($flatArray['kind'] ?? null);
$locations[] = $location;
}
diff --git a/src/Provider/Yandex/composer.json b/src/Provider/Yandex/composer.json
index aa51120ca..e1f0c3c0a 100644
--- a/src/Provider/Yandex/composer.json
+++ b/src/Provider/Yandex/composer.json
@@ -12,34 +12,35 @@
}
],
"require": {
- "php": "^7.0",
- "willdurand/geocoder": "^4.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.1",
+ "php": "^8.0",
"geocoder-php/common-http": "^4.0",
- "geocoder-php/provider-integration-tests": "^1.0",
- "php-http/message": "^1.0",
- "php-http/curl-client": "^1.7",
- "nyholm/psr7": "^0.2.2"
+ "willdurand/geocoder": "^4.0|^5.0"
},
"provide": {
"geocoder-php/provider-implementation": "1.0"
},
+ "require-dev": {
+ "geocoder-php/provider-integration-tests": "^1.6.3",
+ "php-http/message": "^1.0",
+ "phpunit/phpunit": "^9.6.11"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
"autoload": {
- "psr-4": { "Geocoder\\Provider\\Yandex\\": "" },
+ "psr-4": {
+ "Geocoder\\Provider\\Yandex\\": ""
+ },
"exclude-from-classmap": [
"/Tests/"
]
},
+ "minimum-stability": "dev",
+ "prefer-stable": true,
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
}
}
diff --git a/src/Provider/Yandex/phpunit.xml.dist b/src/Provider/Yandex/phpunit.xml.dist
index a158c9a38..c607aa531 100644
--- a/src/Provider/Yandex/phpunit.xml.dist
+++ b/src/Provider/Yandex/phpunit.xml.dist
@@ -1,28 +1,21 @@
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
- ./Tests
- ./vendor
-
-
-
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
+ ./Tests/
+
+