1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /* |
||
6 | * This file is part of the Geocoder package. |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | * |
||
10 | * @license MIT License |
||
11 | */ |
||
12 | |||
13 | namespace Geocoder\Provider\Geonames; |
||
14 | |||
15 | use Geocoder\Collection; |
||
16 | use Geocoder\Exception\InvalidCredentials; |
||
17 | use Geocoder\Exception\InvalidServerResponse; |
||
18 | use Geocoder\Exception\UnsupportedOperation; |
||
19 | use Geocoder\Http\Provider\AbstractHttpProvider; |
||
20 | use Geocoder\Model\AddressBuilder; |
||
21 | use Geocoder\Model\AddressCollection; |
||
22 | use Geocoder\Model\AdminLevelCollection; |
||
23 | use Geocoder\Provider\Geonames\Model\CountryInfo; |
||
24 | use Geocoder\Provider\Geonames\Model\GeonamesAddress; |
||
25 | use Geocoder\Provider\Provider; |
||
26 | use Geocoder\Query\GeocodeQuery; |
||
27 | use Geocoder\Query\ReverseQuery; |
||
28 | use Psr\Http\Client\ClientInterface; |
||
29 | |||
30 | /** |
||
31 | * @author Giovanni Pirrotta <[email protected]> |
||
32 | */ |
||
33 | final class Geonames extends AbstractHttpProvider implements Provider |
||
34 | { |
||
35 | /** |
||
36 | * @var string |
||
37 | */ |
||
38 | public const GEOCODE_ENDPOINT_URL = 'http://api.geonames.org/searchJSON?q=%s&maxRows=%d&style=full&username=%s'; |
||
39 | |||
40 | /** |
||
41 | * @var string |
||
42 | */ |
||
43 | public const REVERSE_ENDPOINT_URL = 'http://api.geonames.org/findNearbyPlaceNameJSON?lat=%F&lng=%F&style=full&maxRows=%d&username=%s'; |
||
44 | |||
45 | /** |
||
46 | * @var string |
||
47 | */ |
||
48 | public const BASE_ENDPOINT_URL = 'http://api.geonames.org/%s?username=%s'; |
||
49 | |||
50 | /** |
||
51 | * @var string |
||
52 | */ |
||
53 | private $username; |
||
54 | |||
55 | /** |
||
56 | * @param ClientInterface $client An HTTP adapter |
||
57 | * @param string $username Username login (Free registration at http://www.geonames.org/login) |
||
58 | */ |
||
59 | 28 | public function __construct(ClientInterface $client, string $username) |
|
60 | { |
||
61 | 28 | if (empty($username)) { |
|
62 | throw new InvalidCredentials('No username provided.'); |
||
63 | } |
||
64 | |||
65 | 28 | $this->username = $username; |
|
66 | 28 | parent::__construct($client); |
|
67 | } |
||
68 | |||
69 | 12 | public function geocodeQuery(GeocodeQuery $query): Collection |
|
70 | { |
||
71 | 12 | $address = $query->getText(); |
|
72 | |||
73 | // This API doesn't handle IPs |
||
74 | 12 | if (filter_var($address, FILTER_VALIDATE_IP)) { |
|
75 | 2 | throw new UnsupportedOperation('The Geonames provider does not support IP addresses.'); |
|
76 | } |
||
77 | |||
78 | 10 | $url = sprintf(self::GEOCODE_ENDPOINT_URL, urlencode($address), $query->getLimit(), $this->username); |
|
79 | |||
80 | 10 | return $this->executeQuery($url, $query->getLocale()); |
|
81 | } |
||
82 | |||
83 | 10 | public function reverseQuery(ReverseQuery $query): Collection |
|
84 | { |
||
85 | 10 | $coordinates = $query->getCoordinates(); |
|
86 | 10 | $longitude = $coordinates->getLongitude(); |
|
87 | 10 | $latitude = $coordinates->getLatitude(); |
|
88 | |||
89 | 10 | $url = sprintf(self::REVERSE_ENDPOINT_URL, $latitude, $longitude, $query->getLimit(), $this->username); |
|
90 | |||
91 | 10 | return $this->executeQuery($url, $query->getLocale()); |
|
92 | } |
||
93 | |||
94 | /** |
||
95 | * @throws \Geocoder\Exception\Exception |
||
96 | */ |
||
97 | 5 | public function getCountryInfo(string $country = null, string $locale = null): array |
|
98 | { |
||
99 | 5 | $url = sprintf(self::BASE_ENDPOINT_URL, 'countryInfoJSON', $this->username); |
|
100 | |||
101 | 5 | if (isset($country)) { |
|
102 | 4 | $url = sprintf('%s&country=%s', $url, $country); |
|
103 | } |
||
104 | |||
105 | 5 | $url = sprintf('%s&style=FULL', $url); |
|
106 | |||
107 | 5 | if (null !== $locale) { |
|
108 | // Locale code transformation: for example from it_IT to it |
||
109 | 1 | $url = sprintf('%s&lang=%s', $url, substr($locale, 0, 2)); |
|
110 | } |
||
111 | |||
112 | 5 | $content = $this->getUrlContents($url); |
|
113 | 5 | if (null === $json = json_decode($content)) { |
|
114 | throw InvalidServerResponse::create($url); |
||
115 | } |
||
116 | |||
117 | 5 | if (!isset($json->geonames)) { |
|
118 | return []; |
||
119 | } |
||
120 | |||
121 | 5 | $data = $json->geonames; |
|
122 | |||
123 | 5 | if (empty($data)) { |
|
124 | 1 | return []; |
|
125 | } |
||
126 | |||
127 | 4 | $results = []; |
|
128 | |||
129 | 4 | foreach ($data as $item) { |
|
130 | 4 | $countryInfo = new CountryInfo(); |
|
131 | |||
132 | 4 | $results[] = $countryInfo |
|
133 | 4 | ->setBounds($item->south, $item->west, $item->north, $item->east) |
|
134 | 4 | ->withContinent($item->continent ?? null) |
|
135 | 4 | ->withCapital($item->capital ?? null) |
|
136 | 4 | ->withLanguages($item->langesuages ?? '') |
|
137 | 4 | ->withGeonameId($item->geonameId ?? null) |
|
138 | 4 | ->withIsoAlpha3($item->isoAlpha3 ?? null) |
|
139 | 4 | ->withFipsCode($item->fipsCode ?? null) |
|
140 | 4 | ->withPopulation($item->population ?? null) |
|
141 | 4 | ->withIsoNumeric($item->isoNumeric ?? null) |
|
142 | 4 | ->withAreaInSqKm($item->areaInSqKm ?? null) |
|
143 | 4 | ->withCountryCode($item->countryCode ?? null) |
|
144 | 4 | ->withCountryName($item->countryName ?? null) |
|
145 | 4 | ->withContinentName($item->continentName ?? null) |
|
146 | 4 | ->withCurrencyCode($item->currencyCode ?? null); |
|
147 | } |
||
148 | |||
149 | 4 | return $results; |
|
150 | } |
||
151 | |||
152 | 7 | public function getName(): string |
|
153 | { |
||
154 | 7 | return 'geonames'; |
|
155 | } |
||
156 | |||
157 | 20 | private function executeQuery(string $url, string $locale = null): AddressCollection |
|
158 | { |
||
159 | 20 | if (null !== $locale) { |
|
160 | // Locale code transformation: for example from it_IT to it |
||
161 | 4 | $url = sprintf('%s&lang=%s', $url, substr($locale, 0, 2)); |
|
162 | } |
||
163 | |||
164 | 20 | $content = $this->getUrlContents($url); |
|
165 | 10 | if (null === $json = json_decode($content)) { |
|
166 | throw InvalidServerResponse::create($url); |
||
167 | } |
||
168 | |||
169 | 10 | if (isset($json->totalResultsCount) && empty($json->totalResultsCount)) { |
|
170 | 2 | return new AddressCollection([]); |
|
171 | } |
||
172 | |||
173 | 8 | if (!isset($json->geonames)) { |
|
174 | return new AddressCollection([]); |
||
175 | } |
||
176 | |||
177 | 8 | $data = $json->geonames; |
|
178 | |||
179 | 8 | if (empty($data)) { |
|
180 | 2 | return new AddressCollection([]); |
|
181 | } |
||
182 | |||
183 | 6 | $results = []; |
|
184 | 6 | foreach ($data as $item) { |
|
185 | 6 | $builder = new AddressBuilder($this->getName()); |
|
186 | |||
187 | 6 | if (isset($item->bbox)) { |
|
188 | 2 | $builder->setBounds($item->bbox->south, $item->bbox->west, $item->bbox->north, $item->bbox->east); |
|
189 | } |
||
190 | |||
191 | 6 | for ($level = 1; $level <= AdminLevelCollection::MAX_LEVEL_DEPTH; ++$level) { |
|
192 | 6 | $adminNameProp = 'adminName'.$level; |
|
193 | 6 | $adminCodeProp = 'adminCode'.$level; |
|
194 | 6 | if (!empty($item->$adminNameProp)) { |
|
195 | 6 | $builder->addAdminLevel($level, $item->$adminNameProp, $item->$adminCodeProp ?? null); |
|
196 | } |
||
197 | } |
||
198 | |||
199 | 6 | $builder->setCoordinates($item->lat ?? null, $item->lng ?? null); |
|
200 | 6 | $builder->setLocality($item->name ?? null); |
|
201 | 6 | $builder->setCountry($item->countryName ?? null); |
|
202 | 6 | $builder->setCountryCode($item->countryCode ?? null); |
|
203 | 6 | $builder->setTimezone($item->timezone->timeZoneId ?? null); |
|
204 | |||
205 | /** @var GeonamesAddress $address */ |
||
206 | 6 | $address = $builder->build(GeonamesAddress::class); |
|
207 | 6 | $address = $address->withName($item->name ?? null); |
|
208 | 6 | $address = $address->withAsciiName($item->asciiName ?? null); |
|
209 | 6 | $address = $address->withFclName($item->fclName ?? null); |
|
210 | 6 | $address = $address->withAlternateNames($item->alternateNames ?? []); |
|
211 | 6 | $address = $address->withPopulation($item->population ?? null); |
|
212 | 6 | $address = $address->withGeonameId($item->geonameId ?? null); |
|
213 | 6 | $address = $address->withFcode($item->fcode ?? null); |
|
214 | |||
215 | 6 | $results[] = $address; |
|
216 | } |
||
217 | |||
218 | 6 | return new AddressCollection($results); |
|
219 | } |
||
220 | } |
||
221 |