PageRenderTime 66ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Provider/Pelias/Pelias.php

http://github.com/willdurand/Geocoder
PHP | 257 lines | 151 code | 36 blank | 70 comment | 13 complexity | b9a564c6bcd4e3fd30d19e1a0fd04807 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the Geocoder package.
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. *
  8. * @license MIT License
  9. */
  10. namespace Geocoder\Provider\Pelias;
  11. use Geocoder\Collection;
  12. use Geocoder\Exception\InvalidCredentials;
  13. use Geocoder\Exception\QuotaExceeded;
  14. use Geocoder\Exception\UnsupportedOperation;
  15. use Geocoder\Model\Address;
  16. use Geocoder\Model\AddressCollection;
  17. use Geocoder\Query\GeocodeQuery;
  18. use Geocoder\Query\ReverseQuery;
  19. use Geocoder\Http\Provider\AbstractHttpProvider;
  20. use Geocoder\Provider\Provider;
  21. use Http\Client\HttpClient;
  22. class Pelias extends AbstractHttpProvider implements Provider
  23. {
  24. /**
  25. * @var string
  26. */
  27. protected $root;
  28. /**
  29. * @var int
  30. */
  31. private $version;
  32. /**
  33. * @param HttpClient $client an HTTP adapter
  34. * @param string $root url of Pelias API
  35. * @param int $version version of Pelias API
  36. */
  37. public function __construct(HttpClient $client, string $root, int $version = 1)
  38. {
  39. $this->root = sprintf('%s/v%d', rtrim($root, '/'), $version);
  40. $this->version = $version;
  41. parent::__construct($client);
  42. }
  43. /**
  44. * @param GeocodeQuery $query
  45. * @param array $query_data additional query data (API key for instance)
  46. *
  47. * @return string
  48. *
  49. * @throws \Geocoder\Exception\Exception
  50. */
  51. protected function getGeocodeQueryUrl(GeocodeQuery $query, array $query_data = []): string
  52. {
  53. $address = $query->getText();
  54. // This API doesn't handle IPs
  55. if (filter_var($address, FILTER_VALIDATE_IP)) {
  56. throw new UnsupportedOperation(sprintf('The %s provider does not support IP addresses, only street addresses.', $this->getName()));
  57. }
  58. $data = [
  59. 'text' => $address,
  60. 'size' => $query->getLimit(),
  61. ];
  62. return sprintf('%s/search?%s', $this->root, http_build_query(array_merge($data, $query_data)));
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function geocodeQuery(GeocodeQuery $query): Collection
  68. {
  69. return $this->executeQuery($this->getGeocodeQueryUrl($query));
  70. }
  71. /**
  72. * @param ReverseQuery $query
  73. * @param array $query_data additional query data (API key for instance)
  74. *
  75. * @return string
  76. *
  77. * @throws \Geocoder\Exception\Exception
  78. */
  79. protected function getReverseQueryUrl(ReverseQuery $query, array $query_data = []): string
  80. {
  81. $coordinates = $query->getCoordinates();
  82. $longitude = $coordinates->getLongitude();
  83. $latitude = $coordinates->getLatitude();
  84. $data = [
  85. 'point.lat' => $latitude,
  86. 'point.lon' => $longitude,
  87. 'size' => $query->getLimit(),
  88. ];
  89. return sprintf('%s/reverse?%s', $this->root, http_build_query(array_merge($data, $query_data)));
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. public function reverseQuery(ReverseQuery $query): Collection
  95. {
  96. return $this->executeQuery($this->getReverseQueryUrl($query));
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function getName(): string
  102. {
  103. return 'pelias';
  104. }
  105. /**
  106. * @param $url
  107. *
  108. * @return Collection
  109. */
  110. protected function executeQuery(string $url): AddressCollection
  111. {
  112. $content = $this->getUrlContents($url);
  113. $json = json_decode($content, true);
  114. if (isset($json['meta'])) {
  115. switch ($json['meta']['status_code']) {
  116. case 401:
  117. case 403:
  118. throw new InvalidCredentials('Invalid or missing api key.');
  119. case 429:
  120. throw new QuotaExceeded('Valid request but quota exceeded.');
  121. }
  122. }
  123. if (
  124. !isset($json['type'])
  125. || 'FeatureCollection' !== $json['type']
  126. || !isset($json['features'])
  127. || [] === $json['features']
  128. ) {
  129. return new AddressCollection([]);
  130. }
  131. $locations = $json['features'];
  132. if (empty($locations)) {
  133. return new AddressCollection([]);
  134. }
  135. $results = [];
  136. foreach ($locations as $location) {
  137. if (isset($location['bbox'])) {
  138. $bounds = [
  139. 'south' => $location['bbox'][3],
  140. 'west' => $location['bbox'][2],
  141. 'north' => $location['bbox'][1],
  142. 'east' => $location['bbox'][0],
  143. ];
  144. } else {
  145. $bounds = [
  146. 'south' => null,
  147. 'west' => null,
  148. 'north' => null,
  149. 'east' => null,
  150. ];
  151. }
  152. $props = $location['properties'];
  153. $adminLevels = [];
  154. foreach (['region', 'county', 'locality', 'macroregion', 'country'] as $i => $component) {
  155. if (isset($props[$component])) {
  156. $adminLevels[] = ['name' => $props[$component], 'level' => $i + 1];
  157. }
  158. }
  159. $results[] = Address::createFromArray([
  160. 'providedBy' => $this->getName(),
  161. 'latitude' => $location['geometry']['coordinates'][1],
  162. 'longitude' => $location['geometry']['coordinates'][0],
  163. 'bounds' => $bounds,
  164. 'streetNumber' => isset($props['housenumber']) ? $props['housenumber'] : null,
  165. 'streetName' => isset($props['street']) ? $props['street'] : null,
  166. 'subLocality' => isset($props['neighbourhood']) ? $props['neighbourhood'] : null,
  167. 'locality' => isset($props['locality']) ? $props['locality'] : null,
  168. 'postalCode' => isset($props['postalcode']) ? $props['postalcode'] : null,
  169. 'adminLevels' => $adminLevels,
  170. 'country' => isset($props['country']) ? $props['country'] : null,
  171. 'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null,
  172. ]);
  173. }
  174. return new AddressCollection($results);
  175. }
  176. /**
  177. * @param array $components
  178. *
  179. * @return string|null
  180. */
  181. protected function guessLocality(array $components)
  182. {
  183. $localityKeys = ['city', 'town', 'village', 'hamlet'];
  184. return $this->guessBestComponent($components, $localityKeys);
  185. }
  186. /**
  187. * @param array $components
  188. *
  189. * @return string|null
  190. */
  191. protected function guessStreetName(array $components)
  192. {
  193. $streetNameKeys = ['road', 'street', 'street_name', 'residential'];
  194. return $this->guessBestComponent($components, $streetNameKeys);
  195. }
  196. /**
  197. * @param array $components
  198. *
  199. * @return string|null
  200. */
  201. protected function guessSubLocality(array $components)
  202. {
  203. $subLocalityKeys = ['neighbourhood', 'city_district'];
  204. return $this->guessBestComponent($components, $subLocalityKeys);
  205. }
  206. /**
  207. * @param array $components
  208. * @param array $keys
  209. *
  210. * @return string|null
  211. */
  212. protected function guessBestComponent(array $components, array $keys)
  213. {
  214. foreach ($keys as $key) {
  215. if (isset($components[$key]) && !empty($components[$key])) {
  216. return $components[$key];
  217. }
  218. }
  219. return null;
  220. }
  221. }