Code

< 40 %
40-60 %
> 60 %
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